Skip to content

feat: Add multi-agent infrastructure (Phase 4 Deliverable 1) - #108

Merged
chrisjz merged 33 commits into
mainfrom
feat/add-multi-agent-infrastructure
Apr 5, 2026
Merged

feat: Add multi-agent infrastructure (Phase 4 Deliverable 1)#108
chrisjz merged 33 commits into
mainfrom
feat/add-multi-agent-infrastructure

Conversation

@chrisjz

@chrisjz chrisjz commented Apr 5, 2026

Copy link
Copy Markdown
Member

Summary

  • Add multi-agent infrastructure enabling 2-10 independent agents in a shared environment with synchronous stepping, food competition, and social proximity sensing
  • Extract per-agent state into AgentState dataclass with backward-compatible property delegation — all existing single-agent code works unchanged
  • New MultiAgentSimulation orchestrator with configurable food competition (FIRST_ARRIVAL/RANDOM), termination policies (freeze/remove/end_all), and per-agent + aggregate metrics (Gini coefficient, competition events)

Key changes

Environment (env/env.py): AgentState dataclass, agents: dict[str, AgentState], 20+ *_for(agent_id) methods, add_agent(), multi-target predator pursuit (chase nearest alive agent)

Orchestrator (agent/multi_agent.py): MultiAgentSimulation with synchronous step loop (perception → movement → food competition → predators → effects → learning), FoodCompetitionPolicy, MultiAgentEpisodeResult with Gini coefficient

Brain (brain/arch/_brain.py, brain/modules.py): nearby_agents_count field in BrainParams, SOCIAL_PROXIMITY sensory module (classical_dim=1)

Config (utils/config_loader.py): AgentConfig, MultiAgentConfig with count/agents mutual exclusion validator, multi_agent field on SimulationConfig

Script (scripts/run_simulation.py): Multi-agent branch with per-agent brain creation, shared env management, episode reset, per-agent weight persistence (final_{agent_id}.pt)

Tests: 50 new tests (19 multi-agent orchestrator, 14 social proximity module, 17 env/AgentState)

Configs: 5 scenario YAMLs in configs/scenarios/multi_agent_foraging/ (2-agent, 5-agent, 10-agent, mixed brains, competition stress test)

Functional verification (4 seeds x 5 scenarios)

Scenario Result
Single-agent backward compat (100 eps) 94.3% success — matches baselines
2-agent foraging 50x50 (500 eps) Agents independent, seed 512 shows +32% learning
5-agent foraging 50x50 (500 eps) Linear food scaling, 4 competition events observed
Competition stress 20x20 (200 eps) 3 competition events, higher food rate on small grid
Mixed brains mlpppo+lstmppo+qrh (200 eps) All architectures coexist, 0 errors

Deferred to follow-up PR

  • CSV export with agent_id column (simulation_results.csv, multi_agent_summary.csv)
  • Weight save/load round-trip test
  • End-of-session summary table

Test plan

  • uv run pytest -m "not nightly" — 1984 passed
  • uv run pre-commit run -a — all 10 hooks pass (ruff, pyright, mdformat, markdownlint, etc.)
  • Backward compat: existing single-agent configs produce identical results
  • 2/5-agent foraging runs complete without errors across 4 seeds
  • Food competition events fire on small grid
  • Mixed brain architectures (mlpppo + lstmppo + qrh) coexist
  • Per-agent weight files saved correctly
  • 10-agent scaling test (config created, deferred to post-merge evaluation)

ðŸĪ– Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Multi‑agent foraging (2–10 agents): synchronous episodes, configurable competition policies, social‑proximity sensing, satiety mechanics, per‑agent weight save/load, termination modes, CLI multi‑agent execution, and several ready‑to‑run multi‑agent scenarios.
  • Documentation

    • Roadmap updated to IN PROGRESS; comprehensive multi‑agent design, specs, config guidance, and task plan added.
  • Tests

    • New unit and integration tests for orchestration, competition resolution, social sensing, environment behavior, and config validation.
  • Chores

    • Lint/config updates and minor script cleanups.

chrisjz and others added 21 commits April 5, 2026 10:47
Extract per-agent mutable state (position, body, direction, HP, visited_cells,
wall_collision_occurred, comfort zone counters) into AgentState dataclass.
Environment maintains agents dict keyed by agent_id. Single-agent mode uses
"default" agent with backward-compatible property accessors. All 188 existing
tests pass unchanged.

Phase 4 Deliverable 1, Phase 1: AgentState Extraction (tasks 1.1-1.6)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķarams

Add SOCIAL_PROXIMITY module (classical_dim=1) for multi-agent population
density sensing. Normalized count of nearby agents: min(count, 10) / 10.0.
Add nearby_agents_count field to BrainParams (None in single-agent mode).
Handle classical_dim=1 in SensoryModule.to_classical(). 14 new tests.

Phase 4 Deliverable 1, Phase 4: BrainParams Social Field (tasks 4.1-4.4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor movement into _apply_movement(agent_state, action) with
_get_new_position_from(position, direction) for multi-agent support.
Add *_for variants for: move_agent, reached_goal, consume_food,
is_agent_in_danger, is_agent_in_damage_radius, is_agent_at_boundary,
apply_predator_damage, apply_temperature_effects, apply_oxygen_effects.
Add thin wrappers for methods already accepting position parameter:
get_separated_gradients, get_food_concentration, get_predator_concentration,
get_temperature, get_oxygen, etc. Add add_agent() method. All 394
env/agent tests pass unchanged.

Phase 4 Deliverable 1, Phase 2: Position-Parameterized Methods (tasks 2.1-2.8)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pursuit predators chase nearest alive agent via new agent_positions parameter.
update_predators() passes all alive agent positions. Single-agent behavior
unchanged. 17 new tests: AgentState properties, add_agent, independent
movement, predator multi-target pursuit, target switching, stationary
predator unaffected. 411 env/agent tests pass.

Phase 4 Deliverable 1, Phase 3: Predator Multi-Target (tasks 3.1-3.3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķ awareness

New multi_agent.py: MultiAgentSimulation orchestrator with synchronous step loop
(perception -> movement -> food competition -> predators -> effects -> learning).
FoodCompetitionPolicy (FIRST_ARRIVAL, RANDOM), resolve_food_competition(),
validate_multi_agent_grid(), MultiAgentEpisodeResult with Gini coefficient.
Agent gets agent_id param and nearby_agents_count injection in _create_brain_params.
17 new tests covering food competition, grid validation, Gini, 2/5-agent episodes,
termination policies, and per-agent food tracking. 428 env/agent tests pass.

Phase 4 Deliverable 1, Phases 5-8 (tasks 5.1-8.3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AgentConfig, MultiAgentConfig with count/agents mutual exclusion validator.
multi_agent field on SimulationConfig. Export MultiAgentSimulation,
FoodCompetitionPolicy, MultiAgentEpisodeResult from agent __init__.
4 scenario configs: 2-agent, 5-agent, 10-agent homogeneous MLP PPO,
and 3-agent mixed brains (mlpppo + lstmppo + qrh). 1982 tests pass.

Phase 4 Deliverable 1, Phases 9+12 (tasks 9.1-9.5, 12.1-12.5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķm_container

Multi-agent branch in run_simulation.py: detects multi_agent.enabled, creates
N agents with independent brains in shared env, runs episodes via
MultiAgentSimulation, per-agent weight save as final_{agent_id}.pt.
configure_brain_from_container() helper for per-agent brain config from YAML.
_configure_brain_for_agent() applies sensing mode translation per agent.
Supports both homogeneous (count) and heterogeneous (agents list) configs.

Phase 4 Deliverable 1, Phases 10+11 (tasks 10.1-11.4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķntation

All 1982 tests pass. Ruff lint clean. Update roadmap Phase 4 status to
IN PROGRESS with Deliverable 1 complete. Strike "Single-agent only" from
Known Gaps. Add multi_agent_foraging to AGENTS.md scenario list.
Fix test lint: keyword-only bool arg, match pattern on raises, unused var.
Ruff format applied to multi_agent.py.

Phase 4 Deliverable 1, Phase 13: Verification and Documentation (tasks 13.1-13.12)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use ActionData type instead of object for action dicts in multi_agent.py
- Fix tuple[int, ...] → tuple[int, int] in AgentState body/position assignments
- Use ModuleName enum in test fixtures instead of string literals
- Add BrainConfigType import and proper return types in run_simulation.py
- Import AdamLearningRate/PerformanceBasedLearningRate for full union type
- Guard config.brain None check with explicit ValueError for multi_agent.count
- Initialize config variable before conditional block to fix possibly-unbound
- 0 pyright errors, 442 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķcursion

B1: Replace agent.reset_environment() in multi-agent loop with proper
shared env recreation — all agents now point to the same fresh env.
B2: _alive_agents property safely handles removed agents (checks agent_id
in env.agents before accessing .alive).
S1: end_all termination uses iterative _terminate_single() instead of
recursive _handle_termination() to avoid stack depth issues.
S2: Validate termination_policy in __post_init__ against allowed values.
S3: Guard predator damage loop against removed agents.
Clean up redundant noqa: SLF001 directives across scripts (now per-file).
2 new tests: invalid policy raises, remove policy no KeyError. 19/19 pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķomment

I1: Update openspec/config.yaml Architecture section to mention multi-agent
support and MultiAgentSimulation orchestrator.
I2: Remove unused reward_config param from _resolve_food_step (was suppressed
with noqa: ARG002 — cleaner to remove entirely).
M1: Add clarifying comment on DEFAULT_AGENT_ID skip in proximity count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
agents_alive_at_end was off by 1 because the backward-compat 'default'
AgentState (always alive=True) was counted. Now excludes DEFAULT_AGENT_ID.
Found during sanity check: 2-agent runs showed alive=1/2 when both starved.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small grid forces agents into food competition events. Reduced
min_food_distance=2, agent_exclusion_radius=2, gradient_decay=4.0,
max_steps=300 for faster episodes. For functional verification of
food competition resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configs with multi_agent.agents (per-agent brains, no top-level brain)
crashed with "No brain configuration found" because configure_brain()
was called before the multi-agent branch. Now detects heterogeneous
multi-agent early and skips single-agent brain/sensing configuration.
Found during mixed_brains evaluation run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 67 tasks audited against implementation. 64 fully done, 3 deferred:
- 10.3: Weight save/load round-trip test (verified via sanity check)
- 11.3: CSV export with agent_id column (console output sufficient)
- 11.4: End-of-session summary table (per-episode output works)
Preparing for archival.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 4 Deliverable 1 complete. 64/67 tasks done, 3 deferred (CSV export,
weight round-trip test, session summary table). Infrastructure validated
across 20 experiment runs (5 scenarios x 4 seeds): backward compat 94.3%,
2/5-agent foraging, competition stress test, mixed brains (mlpppo+lstmppo+qrh).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds multi-agent infrastructure: per-agent AgentState and APIs, SOCIAL_PROXIMITY sensory module and BrainParams.nearby_agents_count, deterministic/random food-competition, MultiAgentSimulation orchestrator, configuration/schema and CLI integration, scenario YAMLs, tests, and exports for 2–10 agents sharing a DynamicForagingEnvironment.

Changes

Cohort / File(s) Summary
Docs & OpenSpec
AGENTS.md, docs/roadmap.md, openspec/config.yaml, openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/...
Added multi-agent design/specs/proposal/tasks, roadmap update and openspec metadata describing multi-agent architecture and requirements.
Scenario Configs
configs/scenarios/multi_agent_foraging/mixed_brains_medium_3agents_oracle.yml, configs/scenarios/multi_agent_foraging/mlpppo_small_5agents_competition_oracle.yml, configs/scenarios/multi_agent_foraging/mlpppo_medium_2agents_oracle.yml, configs/scenarios/multi_agent_foraging/mlpppo_medium_5agents_oracle.yml, configs/scenarios/multi_agent_foraging/mlpppo_large_10agents_oracle.yml
Added multiple multi-agent scenario YAMLs (2–10 agents) defining brains, multi-agent settings, reward shaping, satiety, and environment/grid parameters.
Environment Core
packages/quantum-nematode/quantumnematode/env/env.py, packages/quantum-nematode/quantumnematode/env/__init__.py
Introduced DEFAULT_AGENT_ID and AgentState; migrated single-agent state to agents: dict[str, AgentState]; added add_agent, per-agent *_for APIs, predator multi-target updates, copy/deep-copy behavior, and backward-compatible delegating properties.
Agent & Orchestrator
packages/quantum-nematode/quantumnematode/agent/agent.py, packages/quantum-nematode/quantumnematode/agent/multi_agent.py, packages/quantum-nematode/quantumnematode/agent/__init__.py
QuantumNematodeAgent accepts agent_id and forwards nearby_agents_count into BrainParams; added FoodCompetitionPolicy, competition resolution, grid validation, MultiAgentSimulation, MultiAgentEpisodeResult, and exported symbols at package top level.
Brain & Modules
packages/quantum-nematode/quantumnematode/brain/arch/_brain.py, packages/quantum-nematode/quantumnematode/brain/modules.py
Added nearby_agents_count to BrainParams; introduced ModuleName.SOCIAL_PROXIMITY, _social_proximity_core, registered module with classical_dim=1, and extended SensoryModule.to_classical() to support 1D outputs.
Config Loader
packages/quantum-nematode/quantumnematode/utils/config_loader.py
Added Pydantic AgentConfig and MultiAgentConfig, extended SimulationConfig with multi_agent, and added population validation and brain-container resolution helper.
CLI & Scripts
scripts/run_simulation.py, scripts/*_analysis.py, scripts/run_plasticity_test.py, scripts/export_screenshot.py
Integrated multi-agent CLI flow (_run_multi_agent, per-agent brain setup, deterministic per-agent seeding, per-agent weight load/save); removed several inline lint suppressions; added related imports and validations.
Tests
packages/quantum-nematode/tests/.../agent/test_multi_agent.py, .../brain/test_social_module.py, .../env/test_env.py
Added unit/integration tests for competition policies, grid validation, Gini calc, MultiAgentSimulation flows and termination policies, social-proximity module, env agent registry/movement, and predator multi-target pursuit.
Packaging / Lint
packages/quantum-nematode/quantumnematode/agent/__init__.py, packages/quantum-nematode/quantumnematode/env/__init__.py, pyproject.toml
Exported multi-agent symbols at package top level and added Ruff per-file ignore entries for multi-agent orchestrator and scripts.
Minor Lint Cleanup
scripts/*.py
Removed several # noqa: SLF001 inline suppressions; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    participant MA as MultiAgentSimulation
    participant Env as DynamicForagingEnvironment
    participant Agent as QuantumNematodeAgent
    participant Pred as Predator

    MA->>Env: reset() / add agents
    MA->>Agent: prepare_episode() each

    loop timestep
        par per-agent perception & action
            MA->>Env: get_sensory_for(agent_id)
            Env-->>MA: sensory + nearby_agents_count
            MA->>Agent: get_action(agent_id, sensory)
            Agent-->>MA: action
        end

        par apply movements
            MA->>Env: move_agent_for(agent_id, action) each
        end

        MA->>MA: collect contested food cells
        MA->>MA: resolve_food_competition(contested, policy)
        MA->>Env: consume_food_for(winning_agent)

        MA->>Pred: update_predators(agent_positions)
        Pred-->>Env: pursue nearest-agent

        par per-agent effects & termination
            MA->>Env: apply_predator_damage_for(agent_id)
            MA->>Env: apply_temperature_effects_for(agent_id)
            MA->>MA: handle_termination(agent_id)
        end

        MA->>Agent: learn/post_process per agent as needed
    end

    MA->>MA: build MultiAgentEpisodeResult (per-agent + aggregate metrics)
Loading

Estimated code review effort

ðŸŽŊ 4 (Complex) | ⏱ïļ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped with friends across the grid so wide,
we sniffed for crumbs with neighbors by our side.
First-arrival feasts, a scramble and a cheer,
predators chased — we bounded without fear.
Multi-agent munchies — carrots all near!

ðŸšĨ Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "feat: Add multi-agent infrastructure (Phase 4 Deliverable 1)" clearly and specifically describes the main change: adding multi-agent support infrastructure to the project as part of Phase 4. It is concise, follows conventional commit semantics, and accurately reflects the primary purpose of this substantial changeset.
Docstring Coverage ✅ Passed Docstring coverage is 93.04% which is sufficient. The required threshold is 80.00%.

✏ïļ Tip: You can configure your own custom pre-merge checks in the settings.

âœĻ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧊 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-multi-agent-infrastructure

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

âĪïļ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Apr 5, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

ðŸ§đ Nitpick comments (2)
packages/quantum-nematode/quantumnematode/agent/agent.py (1)

159-159: Use the shared default agent identifier constant instead of a string literal.

Line 159 hardcodes "default". Prefer reusing DEFAULT_AGENT_ID to avoid drift between environment and agent defaults.

â™ŧïļ Proposed refactor
 from quantumnematode.env import (
+    DEFAULT_AGENT_ID,
     DynamicForagingEnvironment,
 )
@@
-        agent_id: str = "default",
+        agent_id: str = DEFAULT_AGENT_ID,
@@
-            Unique identifier for multi-agent mode. Defaults to "default".
+            Unique identifier for multi-agent mode. Defaults to DEFAULT_AGENT_ID.

Also applies to: 183-183

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/agent/agent.py` at line 159,
Replace the hardcoded "default" agent id string with the shared constant
DEFAULT_AGENT_ID: update the agent_id parameter default in the relevant function
signatures (the occurrence at agent.py where agent_id: str = "default" and the
other occurrence around the second location) to use DEFAULT_AGENT_ID, and ensure
DEFAULT_AGENT_ID is imported or referenced in that module so both places use the
same canonical default identifier.
packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py (1)

58-70: Add a negative-count regression test for social proximity normalization.

Current tests cover upper clamping but not lower-bound safety. Add one case (nearby_agents_count=-1) to lock in [0, 1] normalization behavior.

🧊 Suggested test addition
 class TestSocialProximityModule:
@@
     def test_clamped_above_ten(self) -> None:
         """Test that counts above 10 are clamped to 1.0."""
         module = SENSORY_MODULES[ModuleName.SOCIAL_PROXIMITY]
         params = BrainParams(nearby_agents_count=15)
         features = module.to_classical(params)
         assert features[0] == pytest.approx(1.0)
+
+    def test_clamped_below_zero(self) -> None:
+        """Test that negative counts are clamped to 0.0."""
+        module = SENSORY_MODULES[ModuleName.SOCIAL_PROXIMITY]
+        params = BrainParams(nearby_agents_count=-1)
+        features = module.to_classical(params)
+        assert features[0] == pytest.approx(0.0)
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py`
around lines 58 - 70, Add a regression test that ensures negative
nearby_agents_count values are clamped to the [0,1] range: create a test similar
to test_one_nearby_agent but instantiate BrainParams(nearby_agents_count=-1),
call SENSORY_MODULES[ModuleName.SOCIAL_PROXIMITY].to_classical(params) and
assert the returned feature at index 0 equals pytest.approx(0.0); reference
SENSORY_MODULES, ModuleName.SOCIAL_PROXIMITY, BrainParams, and to_classical to
locate where to add the test.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/quantum-nematode/quantumnematode/agent/multi_agent.py`:
- Around line 306-320: The code computes nearby_agents_count (nearby) when
selecting actions but later rebuilds BrainParams without it before calling
learn(), causing SOCIAL_PROXIMITY to be missing during updates; fix by threading
the cached nearby value into every call that constructs BrainParams for learning
(use agent._create_brain_params(..., nearby_agents_count=nearby) rather than
reconstructing params without that arg) in the termination code paths
(specifically in _handle_termination() and _terminate_single() where
episode_done=True learns are invoked and where action_per_agent and nearby were
originally computed) so that the same nearby_agents_count used during action
selection is passed to learn() via BrainParams.
- Around line 202-234: In __post_init__, after validating each agent.env is
self.env and before initializing _per_agent_food, add a guard that verifies
every agent.agent_id exists in self.env.agents and raise a ValueError if any are
missing; e.g., compute missing_ids = [a.agent_id for a in self.agents if
a.agent_id not in self.env.agents] and if missing_ids: raise ValueError with a
clear message listing missing_ids and the full list of provided agent IDs so
MultiAgentSimulation fails fast on misregistered env IDs (refer to symbols:
__post_init__, self.agents, self.env.agents, _per_agent_food).

In `@packages/quantum-nematode/quantumnematode/brain/modules.py`:
- Around line 778-780: The current computation for social proximity uses count =
params.nearby_agents_count and normalized = min(count, 10) / 10.0 which only
clamps the upper bound; if nearby_agents_count is negative strength can be <0.
Replace that logic in the block around params.nearby_agents_count so that you
clamp both bounds (e.g. clamp count to [0,10] using max/min) before computing
normalized and returning CoreFeatures(strength=normalized, angle=0.0,
binary=0.0).

In `@packages/quantum-nematode/quantumnematode/utils/config_loader.py`:
- Around line 750-770: MultiAgentConfig's _validate_population currently only
checks presence of count vs agents and allows invalid sizes; update
_validate_population to enforce that when enabled is True either count is set to
an int in the inclusive range 2..10 or agents is a non-empty list whose length
is in the inclusive range 2..10, otherwise raise ValueError with clear messages
(e.g. "multi_agent.count must be between 2 and 10" or "multi_agent.agents must
contain between 2 and 10 entries"); ensure you check for non-positive counts
(<=0) and also validate that if agents is provided its length meets the same
2..10 constraint before returning self.

In `@scripts/run_simulation.py`:
- Around line 1553-1559: The environment is being recreated with the global
simulation_seed instead of the per-episode run_seed so derive_run_seed() has no
effect on environment RNG; change all calls that build or rebuild the env (calls
to create_env_from_config) to pass the per-episode run_seed (the value returned
by derive_run_seed or the local run_seed variable) instead of simulation_seed so
agent spawn, food/predator placement and any RANDOM policies are seeded per-run;
update every occurrence where create_env_from_config is invoked during episode
setup/rebuild (including the initial creation and in the per-episode
reset/rebuild code paths) to use run_seed.
- Around line 1487-1510: The multi-agent path hardcodes perf_mgmt=None when
calling setup_brain_model inside _run_multi_agent, so the --optimize flag is
ignored; fix this by adding a perf_mgmt parameter to _run_multi_agent (matching
the object passed by main), update its signature to accept the same perf_mgmt
type used by main, propagate that parameter through any callers of
_run_multi_agent, and pass the live perf_mgmt instance into setup_brain_model
instead of None so multi-agent QPU runs use the same optimization management as
the single-agent path (see how single-agent passes perf_mgmt into
setup_brain_model for reference).
- Around line 1565-1566: Replace the salted, interpreter-dependent Python hash
usage for per-agent seed derivation with a stable cryptographic hash: add
"import hashlib" to the stdlib imports, then compute a blake2s (or similar)
digest over the string or bytes representation of (simulation_seed, ac.id),
convert the digest to an integer (e.g., from hex or bytes) and take modulo 2**32
to produce agent_seed; update the code path that sets agent_seed (the current
line using hash((simulation_seed, ac.id)) % (2**32)) to use this deterministic
blake2s-based conversion so seeds are reproducible across runs.

---

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/agent/agent.py`:
- Line 159: Replace the hardcoded "default" agent id string with the shared
constant DEFAULT_AGENT_ID: update the agent_id parameter default in the relevant
function signatures (the occurrence at agent.py where agent_id: str = "default"
and the other occurrence around the second location) to use DEFAULT_AGENT_ID,
and ensure DEFAULT_AGENT_ID is imported or referenced in that module so both
places use the same canonical default identifier.

In
`@packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py`:
- Around line 58-70: Add a regression test that ensures negative
nearby_agents_count values are clamped to the [0,1] range: create a test similar
to test_one_nearby_agent but instantiate BrainParams(nearby_agents_count=-1),
call SENSORY_MODULES[ModuleName.SOCIAL_PROXIMITY].to_classical(params) and
assert the returned feature at index 0 equals pytest.approx(0.0); reference
SENSORY_MODULES, ModuleName.SOCIAL_PROXIMITY, BrainParams, and to_classical to
locate where to add the test.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4927c653-ccb3-4222-a37e-ca5e8ee60cf2

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 9792168 and 454d231.

📒 Files selected for processing (33)
  • AGENTS.md
  • configs/scenarios/multi_agent_foraging/mixed_brains_medium_3agents_oracle.yml
  • configs/scenarios/multi_agent_foraging/mlpppo_large_10agents_oracle.yml
  • configs/scenarios/multi_agent_foraging/mlpppo_medium_2agents_oracle.yml
  • configs/scenarios/multi_agent_foraging/mlpppo_medium_5agents_oracle.yml
  • configs/scenarios/multi_agent_foraging/mlpppo_small_5agents_competition_oracle.yml
  • docs/roadmap.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/.openspec.yaml
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/design.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/proposal.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/specs/brain-architecture/spec.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/specs/configuration-system/spec.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/specs/environment-simulation/spec.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/specs/multi-agent/spec.md
  • openspec/changes/archive/2026-04-05-add-multi-agent-infrastructure/tasks.md
  • openspec/config.yaml
  • packages/quantum-nematode/quantumnematode/agent/__init__.py
  • packages/quantum-nematode/quantumnematode/agent/agent.py
  • packages/quantum-nematode/quantumnematode/agent/multi_agent.py
  • packages/quantum-nematode/quantumnematode/brain/arch/_brain.py
  • packages/quantum-nematode/quantumnematode/brain/modules.py
  • packages/quantum-nematode/quantumnematode/env/__init__.py
  • packages/quantum-nematode/quantumnematode/env/env.py
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py
  • packages/quantum-nematode/tests/quantumnematode_tests/agent/test_multi_agent.py
  • packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
  • pyproject.toml
  • scripts/export_screenshot.py
  • scripts/qef_mi_analysis.py
  • scripts/qrh_mi_analysis.py
  • scripts/run_plasticity_test.py
  • scripts/run_simulation.py

Comment thread packages/quantum-nematode/quantumnematode/agent/multi_agent.py
Comment thread packages/quantum-nematode/quantumnematode/agent/multi_agent.py
Comment thread packages/quantum-nematode/quantumnematode/brain/modules.py Outdated
Comment thread packages/quantum-nematode/quantumnematode/utils/config_loader.py
Comment thread scripts/run_simulation.py
Comment thread scripts/run_simulation.py Outdated
Comment thread scripts/run_simulation.py Outdated
â€Ķ perf_mgmt

Fix nearby_agents_count missing in BrainParams during learn() and termination:
cache per-agent nearby count and thread through all _create_brain_params calls
in learning phase, _handle_termination, and _terminate_single.

Add agent_id registration validation in __post_init__: verify every agent's
agent_id exists in env.agents, fail fast with clear message listing missing IDs.

Clamp social proximity count to [0,10] (was only upper-bounded; negative
values could produce negative strength). Add regression test.

Add count/agents range validation (2-10) in MultiAgentConfig validator.

Fix env reset seed: use per-episode run_seed instead of simulation_seed so
food/predator/agent placement varies per episode.

Pass perf_mgmt through _run_multi_agent instead of hardcoding None, so
--optimize flag works for multi-agent QPU runs.

Replace Python hash() with hashlib.blake2b for stable per-agent seed
derivation (hash() is non-deterministic with PYTHONHASHSEED randomization).

Use DEFAULT_AGENT_ID constant in agent.py instead of "default" string literal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

â™ŧïļ Duplicate comments (1)
scripts/run_simulation.py (1)

1556-1562: ⚠ïļ Potential issue | 🟠 Major

Seed the environment for the episode that is actually running.

The environment created at Line 1557 still uses simulation_seed, and the rebuild at Line 1656 uses the current episode's run_seed only after that episode finishes. The first run therefore executes on the wrong environment seed, and every later run is one episode behind.

🛠ïļ Suggested fix
-    # Create shared environment
+    # Create shared environment for run 0
+    initial_run_seed = derive_run_seed(simulation_seed, 0)
     env = create_env_from_config(
         environment_config,
-        seed=simulation_seed,
+        seed=initial_run_seed,
         max_body_length=body_length,
         theme=theme,
     )
-        # Reset for next episode: create fresh shared env with per-episode seed
-        env = create_env_from_config(
-            environment_config,
-            seed=run_seed,
-            max_body_length=body_length,
-            theme=theme,
-        )
+        # Prepare the next episode with its own derived seed
+        if run + 1 >= runs:
+            continue
+        next_run_seed = derive_run_seed(simulation_seed, run + 1)
+        env = create_env_from_config(
+            environment_config,
+            seed=next_run_seed,
+            max_body_length=body_length,
+            theme=theme,
+        )

Also applies to: 1631-1661

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1556 - 1562, The environment is being
created with the global simulation_seed (env = create_env_from_config(...,
seed=simulation_seed,...)) instead of the per-episode seed, causing the first
episode to run with the wrong seed and subsequent episodes to be one behind;
update the create_env_from_config calls (and any rebuild logic that currently
uses simulation_seed) to pass the current episode's run_seed (seed=run_seed)
when initializing or rebuilding the env so each episode uses its intended seed
(also apply the same change to the repeated block referenced around the rebuild
in the 1631-1661 region).
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/run_simulation.py`:
- Around line 399-421: The multi-agent branch early-circuits before any weight
load/save handling, causing --load-weights/--save-weights to be ignored; update
the conditional where multi_agent_config is checked so you either (A) propagate
the weight flags into _run_multi_agent by adding parameters for load_weights and
save_weights (and any related paths/flags) to the _run_multi_agent(...) call and
update the _run_multi_agent signature to handle loading/saving, or (B) perform a
fast-fail: check if load_weights or save_weights are set when
multi_agent_config.enabled is true and raise a clear error before calling
_run_multi_agent; reference multi_agent_config, _run_multi_agent, and the CLI
flags --load-weights/--save-weights when making the change.
- Around line 1664-1674: The multi-agent initialization loop sets env, path,
food_history, _food_handler.env and resets
_satiety_manager/_food_handler/_episode_tracker but omits clearing agent
recurrent state; call agent.reset_brain() for each agent inside the for agent in
agents: loop (after or immediately after agent._episode_tracker.reset()) so
recurrent state, episode buffers and history are cleared between episodes;
ensure you add this call alongside the existing per-agent resets to mirror the
single-agent logic that invokes reset_brain().

---

Duplicate comments:
In `@scripts/run_simulation.py`:
- Around line 1556-1562: The environment is being created with the global
simulation_seed (env = create_env_from_config(..., seed=simulation_seed,...))
instead of the per-episode seed, causing the first episode to run with the wrong
seed and subsequent episodes to be one behind; update the create_env_from_config
calls (and any rebuild logic that currently uses simulation_seed) to pass the
current episode's run_seed (seed=run_seed) when initializing or rebuilding the
env so each episode uses its intended seed (also apply the same change to the
repeated block referenced around the rebuild in the 1631-1661 region).
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d10e9bbf-fcf0-4438-adf6-e043006bc57d

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 454d231 and 917d7c9.

📒 Files selected for processing (6)
  • packages/quantum-nematode/quantumnematode/agent/agent.py
  • packages/quantum-nematode/quantumnematode/agent/multi_agent.py
  • packages/quantum-nematode/quantumnematode/brain/modules.py
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py
  • packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py
  • scripts/run_simulation.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py
  • packages/quantum-nematode/quantumnematode/agent/agent.py
  • packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py
  • packages/quantum-nematode/quantumnematode/agent/multi_agent.py

Comment thread scripts/run_simulation.py
Comment thread scripts/run_simulation.py Outdated
â€Ķtween episodes

CLI --load-weights/--save-weights are single-agent concepts (one brain).
Multi-agent uses per-agent weights_path in config. Raise clear ValueError
if these flags are used with multi_agent.enabled.

Add agent.reset_brain() to multi-agent episode reset loop to clear
brain.history_data between episodes, matching single-agent behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

ðŸ§đ Nitpick comments (1)
scripts/run_simulation.py (1)

1692-1695: Consider catching more specific exceptions.

The bare Exception catch works for graceful degradation during cleanup, but narrowing to expected failure modes (e.g., OSError | TypeError) would avoid masking unexpected errors.

â™ŧïļ Optional refinement
-            except Exception:
+            except (OSError, TypeError):
                 logger.exception("Failed to save weights for %s", agent.agent_id)
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1692 - 1695, The current bare "except
Exception" around save_weights(agent.brain, weights_dir / weight_file) can mask
unexpected errors; replace it with a specific exception handler (e.g., "except
(OSError, TypeError) as e") to catch the expected failure modes when writing
files or serializing the brain, call logger.exception("Failed to save weights
for %s: %s", agent.agent_id, e) (or keep logger.exception with the exception)
and remove the broad catch so other unexpected exceptions will propagate (or add
an explicit re-raise for non-expected exceptions).
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/run_simulation.py`:
- Around line 1639-1682: The environment seed is lagging by one run because env
is recreated after the episode; fix by creating the per-episode env at the start
of each loop using derive_run_seed(simulation_seed, run) and passing that
run_seed into create_env_from_config before calling set_global_seed and
sim.run_episode; also change the initial env creation to use
derive_run_seed(simulation_seed, 0) instead of simulation_seed; remove the env
recreation block at the end of the loop and ensure agent.env, agent.path,
agent.food_history, agent._food_handler.env, and resets
(agent._satiety_manager.reset(), agent._food_handler.reset(),
agent._episode_tracker.reset(), agent.reset_brain()) are applied right after the
new env is created (use the same symbols: derive_run_seed, set_global_seed,
create_env_from_config, sim.run_episode, agent.reset_brain).

---

Nitpick comments:
In `@scripts/run_simulation.py`:
- Around line 1692-1695: The current bare "except Exception" around
save_weights(agent.brain, weights_dir / weight_file) can mask unexpected errors;
replace it with a specific exception handler (e.g., "except (OSError, TypeError)
as e") to catch the expected failure modes when writing files or serializing the
brain, call logger.exception("Failed to save weights for %s: %s",
agent.agent_id, e) (or keep logger.exception with the exception) and remove the
broad catch so other unexpected exceptions will propagate (or add an explicit
re-raise for non-expected exceptions).
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f02cb790-810d-4112-a781-f914d519bd4b

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 917d7c9 and 083f7c0.

📒 Files selected for processing (1)
  • scripts/run_simulation.py

Comment thread scripts/run_simulation.py Outdated
Env was recreated at the end of each episode with the current run_seed,
but used for the next episode — causing run N to use run N-1's seed.
Now create fresh env at the start of each iteration (run > 0) with the
correct run_seed. Initial env uses derive_run_seed(simulation_seed, 0)
instead of bare simulation_seed for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/run_simulation.py`:
- Around line 1603-1607: The runner ignores MultiAgentConfig.min_agent_distance
because env.add_agent is called with position=None; update both the initial
placement (around the env.add_agent call at lines ~1603) and the per-run rebuild
(around lines ~1652-1653) to honor config.min_agent_distance: compute a concrete
spawn position that is at least config.min_agent_distance from all existing
agents (using any existing helper like find_spawn_position/select_spawn_position
or by sampling/validating positions against env.get_agent_positions()) and pass
that position into env.add_agent(position=spawn_pos,
max_body_length=body_length); if env.add_agent already supports a min_distance
parameter, pass config.min_agent_distance directly instead. Ensure you reference
MultiAgentConfig.min_agent_distance, env.add_agent, and the initial
placement/rebuild code paths when making the change.
- Around line 1598-1600: The multi-agent branch calls load_weights(agent_brain,
Path(ac.weights_path)) without verifying the agent_brain supports weight
persistence; update the branch to mirror the single-agent guard by checking
isinstance(agent_brain, WeightPersistence) before calling load_weights so
heterogeneous agents won't raise unclear errors—use the ac.weights_path,
agent_brain, load_weights, and WeightPersistence symbols to locate and modify
the conditional.
- Around line 1556-1557: The multi-agent branch only calls
validate_multi_agent_grid(...) and skips the qubit/classical checks in
validate_simulation_parameters(...), allowing non-default qubits into classical
brains; update the multi-agent path to also run the same validation by invoking
validate_simulation_parameters(...) (or extract and call the specific
qubit/classical validation helper used by validate_simulation_parameters) after
validate_multi_agent_grid(...) so the qubit vs classical brain guard is enforced
for multi-agent configs as well.
- Around line 399-429: The multi-agent early-return currently only rejects
--load-weights/--save-weights but lets single-agent-only CLI flags silently
noop; update the conditional just before calling _run_multi_agent to also check
args.manyworlds, args.track_per_run, args.track_experiment, and
args.validate_chemotaxis and raise a ValueError with a clear message (same style
as the existing msg) telling the user these flags cannot be used with
multi-agent mode and to use per-agent equivalents in multi_agent.agents; keep
the error handling co-located with the existing load/save weights check so all
single-agent-only flags fail fast before calling _run_multi_agent.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b85bb1b4-65ca-4287-a385-c53f3d8edd5f

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 083f7c0 and f848754.

📒 Files selected for processing (1)
  • scripts/run_simulation.py

Comment thread scripts/run_simulation.py
Comment thread scripts/run_simulation.py
Comment on lines +1556 to +1557
# Validate grid size
validate_multi_agent_grid(grid_size, num_agents)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠ïļ Potential issue | ðŸŸĄ Minor

Preserve the existing qubit/classical validation in multi-agent mode.

Because this path returns before validate_simulation_parameters() runs, the only validation here is grid capacity. A multi-agent config can now pass non-default qubits into classical brains without the guard the single-agent path already has.

🔧 Minimal fix
     # Validate grid size
     validate_multi_agent_grid(grid_size, num_agents)
+    for ac in agent_configs:
+        validate_simulation_parameters(
+            maze_grid_size=grid_size,
+            brain_type=BrainType(ac.brain.name),
+            qubits=qubits,
+        )
 
     logger.info(f"Multi-agent mode: {num_agents} agents, grid {grid_size}x{grid_size}")
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1556 - 1557, The multi-agent branch
only calls validate_multi_agent_grid(...) and skips the qubit/classical checks
in validate_simulation_parameters(...), allowing non-default qubits into
classical brains; update the multi-agent path to also run the same validation by
invoking validate_simulation_parameters(...) (or extract and call the specific
qubit/classical validation helper used by validate_simulation_parameters) after
validate_multi_agent_grid(...) so the qubit vs classical brain guard is enforced
for multi-agent configs as well.

Comment thread scripts/run_simulation.py
Comment thread scripts/run_simulation.py
Comment on lines +1603 to +1607
env.add_agent(
agent_id=ac.id,
position=None, # Random valid position
max_body_length=body_length,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠ïļ Potential issue | 🟠 Major

min_agent_distance is effectively a no-op right now.

MultiAgentConfig exposes min_agent_distance, but both the initial placement and the per-run rebuild still call env.add_agent(..., position=None, max_body_length=body_length) without consuming it. Configs that rely on separated spawns will be ignored in this runner.

Also applies to: 1652-1653

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1603 - 1607, The runner ignores
MultiAgentConfig.min_agent_distance because env.add_agent is called with
position=None; update both the initial placement (around the env.add_agent call
at lines ~1603) and the per-run rebuild (around lines ~1652-1653) to honor
config.min_agent_distance: compute a concrete spawn position that is at least
config.min_agent_distance from all existing agents (using any existing helper
like find_spawn_position/select_spawn_position or by sampling/validating
positions against env.get_agent_positions()) and pass that position into
env.add_agent(position=spawn_pos, max_body_length=body_length); if env.add_agent
already supports a min_distance parameter, pass config.min_agent_distance
directly instead. Ensure you reference MultiAgentConfig.min_agent_distance,
env.add_agent, and the initial placement/rebuild code paths when making the
change.

@chrisjz chrisjz changed the title Add multi-agent infrastructure (Phase 4 Deliverable 1) feat: Add multi-agent infrastructure (Phase 4 Deliverable 1) Apr 5, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠ïļ Outside diff range comments (2)
packages/quantum-nematode/quantumnematode/env/env.py (2)

3022-3080: ⚠ïļ Potential issue | 🟠 Major

copy() does not preserve multi-agent state.

The copy() method only transfers the default agent's state via backward-compatible properties (lines 3054-3067). In multi-agent mode, all non-default agents would be lost. If copy() is called during multi-agent simulation (e.g., for many-worlds branching), this could silently corrupt the environment.

🛠ïļ Suggested fix
         new_env.body = self.body.copy()
         new_env.current_direction = self.current_direction
         new_env.foods = self.foods.copy()
         new_env.visited_cells = self.visited_cells.copy()
+        # Copy all agent states (multi-agent support)
+        new_env.agents = {
+            agent_id: AgentState(
+                agent_id=state.agent_id,
+                position=state.position,
+                body=state.body.copy(),
+                direction=state.direction,
+                hp=state.hp,
+                visited_cells=state.visited_cells.copy(),
+                wall_collision_occurred=state.wall_collision_occurred,
+                alive=state.alive,
+                steps_in_comfort_zone=state.steps_in_comfort_zone,
+                total_thermotaxis_steps=state.total_thermotaxis_steps,
+                steps_in_oxygen_comfort_zone=state.steps_in_oxygen_comfort_zone,
+                total_aerotaxis_steps=state.total_aerotaxis_steps,
+            )
+            for agent_id, state in self.agents.items()
+        }
         # Copy RNG state for reproducibility
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 3022 -
3080, The copy() implementation currently only copies the default agent's
runtime state (body, current_direction, agent_pos, agent_hp, visited_cells) and
thus drops any additional agents in multi-agent mode; update
DynamicForagingEnvironment.copy to detect and deep-copy the multi-agent
container (e.g., self.agents or whatever structure holds non-default agents) and
for each agent clone its runtime fields (position, body, current_direction, hp,
visited_cells/visited map entries, any per-agent thermotaxis/aerotaxis counters)
into the new_env so multi-agent branches are preserved; keep the existing
single-agent backward-compatible assignments (body, agent_pos, agent_hp, etc.)
when only the default agent exists and ensure predators/rng are still copied as
before.

1257-1286: ⚠ïļ Potential issue | ðŸŸĄ Minor

Food validation only checks default agent position.

_is_valid_food_position() at line 1274 uses self.agent_pos (default agent only). During multi-agent episodes, when food respawns after consumption (via spawn_food() called from consume_food_for()), the validation does not check distances from non-default agents. Food could spawn on top of non-default agents. Consider checking all agent positions for proper multi-agent food placement.

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 1257 -
1286, The _is_valid_food_position function currently only measures distance to
self.agent_pos, causing food to potentially spawn on non-default agents in
multi-agent episodes; update _is_valid_food_position to iterate over all agent
positions (e.g., self.agent_positions or the container used for agents in the
environment) and check Euclidean distance against each agent's position (use the
same exclusion radius self.foraging.agent_exclusion_radius) before validating
food placement; also ensure spawn_food() / consume_food_for() continue to call
this method so respawned food respects multi-agent exclusion rules and add a
unit check to cover multi-agent scenarios if one exists.
â™ŧïļ Duplicate comments (1)
scripts/run_simulation.py (1)

1567-1572: ⚠ïļ Potential issue | ðŸŸĄ Minor

Missing qubit/classical brain validation for multi-agent.

The multi-agent path calls validate_multi_agent_grid() but skips validate_simulation_parameters(). This means a multi-agent config could specify non-default qubits for classical brain types without triggering the validation error that the single-agent path would catch.

🛠ïļ Suggested fix
     # Validate grid size
     validate_multi_agent_grid(grid_size, num_agents)
+    # Validate brain/qubit compatibility for each agent
+    for ac in agent_configs:
+        validate_simulation_parameters(
+            maze_grid_size=grid_size,
+            brain_type=BrainType(ac.brain.name),
+            qubits=qubits,
+        )

     logger.info(f"Multi-agent mode: {num_agents} agents, grid {grid_size}x{grid_size}")
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1567 - 1572, The multi-agent path
currently calls validate_multi_agent_grid(grid_size, num_agents) but never runs
validate_simulation_parameters, allowing invalid combinations like non-default
qubits with a classical brain to slip through; update the multi-agent startup to
also call validate_simulation_parameters(multi_agent_config) (or pass the
relevant fields such as multi_agent_config.brain_type and
multi_agent_config.qubits) immediately alongside validate_multi_agent_grid so
the same qubit/classical-brain checks applied in the single-agent flow are
enforced for multi-agent runs.
ðŸ§đ Nitpick comments (2)
packages/quantum-nematode/quantumnematode/env/env.py (1)

2070-2158: Consider extracting shared logic to reduce duplication.

The apply_temperature_effects_for() and apply_oxygen_effects_for() methods duplicate the zone-effect logic from the original single-agent methods (lines 2360-2407, 2529-2574). Consider extracting the core logic into a private helper that both variants call.

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 2070 -
2158, Both methods apply_temperature_effects_for and apply_oxygen_effects_for
duplicate the same zone->reward/hp logic; extract that core logic into a private
helper (e.g., _compute_and_apply_zone_effects or _apply_zone_effects) that takes
the agent_state, zone, and the relevant config object (self.thermotaxis or
self.aerotaxis) and returns (reward_delta, hp_damage); keep only zone lookup and
per-agent counters in apply_temperature_effects_for/apply_oxygen_effects_for,
call the new helper to compute rewards and damage, and let the helper handle
mapping enums to comfort/danger/lethal rewards and applying hp clamping to
agent_state.hp.
scripts/run_simulation.py (1)

1619-1625: min_agent_distance is best-effort; consider documenting this limitation.

The env.add_agent() call correctly passes min_distance, but as noted in the environment code, this is best-effort Poisson sampling that may fall back to closer placement. If strict separation is required for certain experiments, users should be aware of this limitation.

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/run_simulation.py` around lines 1619 - 1625, The call to
env.add_agent(...) passes min_distance (multi_agent_config.min_agent_distance)
but the environment's placement is best-effort Poisson sampling and may place
agents closer; update the code and/or docs to make this explicit: add a concise
inline comment or user-facing warning near the env.add_agent invocation (or in
the surrounding function) stating that min_distance is best-effort and may be
violated, and update any public docstring or configuration help text (where
multi_agent_config is defined) to document the limitation so users running
experiments that require strict separation know to enforce it externally or add
a post-placement check.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 2219-2232: The fallback assignment of position (where position is
set via self.rng.integers) can place an agent on an occupied cell; change the
fallback in the block that handles Poisson sampling exhaustion so that after
generating a candidate (using self.rng.integers) you check against the occupied
set and retry until you find a free cell (or a small bounded number of retries),
then assign position; update the logger.warning to keep the same context
(agent_id, min_distance, MAX_POISSON_ATTEMPTS) but only log after a true
fallback decision, and reference the symbols position, self.rng.integers,
occupied, min_distance, MAX_POISSON_ATTEMPTS, and agent_id when making the fix.

---

Outside diff comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 3022-3080: The copy() implementation currently only copies the
default agent's runtime state (body, current_direction, agent_pos, agent_hp,
visited_cells) and thus drops any additional agents in multi-agent mode; update
DynamicForagingEnvironment.copy to detect and deep-copy the multi-agent
container (e.g., self.agents or whatever structure holds non-default agents) and
for each agent clone its runtime fields (position, body, current_direction, hp,
visited_cells/visited map entries, any per-agent thermotaxis/aerotaxis counters)
into the new_env so multi-agent branches are preserved; keep the existing
single-agent backward-compatible assignments (body, agent_pos, agent_hp, etc.)
when only the default agent exists and ensure predators/rng are still copied as
before.
- Around line 1257-1286: The _is_valid_food_position function currently only
measures distance to self.agent_pos, causing food to potentially spawn on
non-default agents in multi-agent episodes; update _is_valid_food_position to
iterate over all agent positions (e.g., self.agent_positions or the container
used for agents in the environment) and check Euclidean distance against each
agent's position (use the same exclusion radius
self.foraging.agent_exclusion_radius) before validating food placement; also
ensure spawn_food() / consume_food_for() continue to call this method so
respawned food respects multi-agent exclusion rules and add a unit check to
cover multi-agent scenarios if one exists.

---

Duplicate comments:
In `@scripts/run_simulation.py`:
- Around line 1567-1572: The multi-agent path currently calls
validate_multi_agent_grid(grid_size, num_agents) but never runs
validate_simulation_parameters, allowing invalid combinations like non-default
qubits with a classical brain to slip through; update the multi-agent startup to
also call validate_simulation_parameters(multi_agent_config) (or pass the
relevant fields such as multi_agent_config.brain_type and
multi_agent_config.qubits) immediately alongside validate_multi_agent_grid so
the same qubit/classical-brain checks applied in the single-agent flow are
enforced for multi-agent runs.

---

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 2070-2158: Both methods apply_temperature_effects_for and
apply_oxygen_effects_for duplicate the same zone->reward/hp logic; extract that
core logic into a private helper (e.g., _compute_and_apply_zone_effects or
_apply_zone_effects) that takes the agent_state, zone, and the relevant config
object (self.thermotaxis or self.aerotaxis) and returns (reward_delta,
hp_damage); keep only zone lookup and per-agent counters in
apply_temperature_effects_for/apply_oxygen_effects_for, call the new helper to
compute rewards and damage, and let the helper handle mapping enums to
comfort/danger/lethal rewards and applying hp clamping to agent_state.hp.

In `@scripts/run_simulation.py`:
- Around line 1619-1625: The call to env.add_agent(...) passes min_distance
(multi_agent_config.min_agent_distance) but the environment's placement is
best-effort Poisson sampling and may place agents closer; update the code and/or
docs to make this explicit: add a concise inline comment or user-facing warning
near the env.add_agent invocation (or in the surrounding function) stating that
min_distance is best-effort and may be violated, and update any public docstring
or configuration help text (where multi_agent_config is defined) to document the
limitation so users running experiments that require strict separation know to
enforce it externally or add a post-placement check.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c0ea847-2aca-4c7f-b022-d60a47b058cd

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between f848754 and 3dd1b6b.

📒 Files selected for processing (2)
  • packages/quantum-nematode/quantumnematode/env/env.py
  • scripts/run_simulation.py

Comment thread packages/quantum-nematode/quantumnematode/env/env.py
chrisjz and others added 2 commits April 5, 2026 23:27
add_agent fallback now retries against occupied set instead of placing
blindly. _is_valid_food_position checks distance from ALL agents (not
just default), so food respawn won't land on non-default agents.
Document min_agent_distance as best-effort in config field comment.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

ðŸ§đ Nitpick comments (1)
packages/quantum-nematode/quantumnematode/utils/config_loader.py (1)

753-755: Use Literal types for food_competition and termination_policy to catch invalid values at config load time.

Both fields accept arbitrary strings but the runtime (MultiAgentOrchestrator) only accepts specific values. A typo like termination_policy: "delete" will pass config loading but fail at orchestrator instantiation with a less helpful error.

â™ŧïļ Suggested validation improvement
+from typing import Literal
+
+# At top of file or before MultiAgentConfig
+FoodCompetitionPolicyStr = Literal["first_arrival", "random"]
+TerminationPolicyStr = Literal["freeze", "remove", "end_all"]
+
 class MultiAgentConfig(BaseModel):
     ...
-    food_competition: str = "first_arrival"
+    food_competition: FoodCompetitionPolicyStr = "first_arrival"
     social_detection_radius: int = 5
-    termination_policy: str = "freeze"
+    termination_policy: TerminationPolicyStr = "freeze"
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/utils/config_loader.py` around
lines 753 - 755, Replace the loose str annotations on the config fields with
typing.Literal so invalid values are caught at load time: import Literal from
typing and change the annotations for food_competition and termination_policy
(the config fields named "food_competition" and "termination_policy" in this
module) from str to Literal[...] listing the exact allowed string values used by
MultiAgentOrchestrator (e.g., termination_policy: Literal["freeze","delete"] and
food_competition: Literal["first_arrival", "closest", "random"]—or whatever
exact set the orchestrator accepts); ensure the Literal options exactly match
MultiAgentOrchestrator's accepted values so type checking and config validation
fail early.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 642-688: The render output currently only reads the
backward-compatible properties (agent_pos, body, current_direction) which point
at DEFAULT_AGENT_ID, so render()/render_full() omit non-default agents; update
render and render_full to iterate self.agents.values() (AgentState entries) and
render each agent's position, body, and direction rather than reading the
single-agent accessors; keep the existing agent_pos/body/current_direction
properties for backwards compatibility but stop using them inside
render/render_full and ensure rendering logic handles multiple AgentState
instances from self.agents and labels/draws them distinctly.
- Around line 2248-2257: copy() on DynamicForagingEnvironment currently
reconstructs only the default agent and drops any additional AgentState entries;
update DynamicForagingEnvironment.copy() to iterate over self.agents and rebuild
each AgentState into new_env.agents (instead of only building the default),
copying mutable fields (use state.body.copy(), state.visited_cells.copy()) and
preserving primitives/flags (state.position, state.direction, state.hp,
state.wall_collision_occurred, state.alive, steps_in_comfort_zone,
total_thermotaxis_steps, steps_in_oxygen_comfort_zone, total_aerotaxis_steps) so
the copied environment retains all agents and their full state.
- Around line 2161-2196: The add_agent method currently accepts a
caller-supplied position without validation; update add_agent to validate a
provided position before registering: check that position is within the
environment bounds (use the environment dimensions e.g. self.width/self.height
or existing bounds-checking helpers) and that the cell is not already occupied
(use the existing occupancy check logic—e.g. scan self.agents' positions or call
an existing self.is_occupied / is_cell_free helper); if the position is out of
bounds or occupied, raise ValueError with a clear message instead of inserting
the agent directly. Ensure this validation runs only when position is not None
and keep the existing random-placement/min_distance behavior unchanged.

---

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/utils/config_loader.py`:
- Around line 753-755: Replace the loose str annotations on the config fields
with typing.Literal so invalid values are caught at load time: import Literal
from typing and change the annotations for food_competition and
termination_policy (the config fields named "food_competition" and
"termination_policy" in this module) from str to Literal[...] listing the exact
allowed string values used by MultiAgentOrchestrator (e.g., termination_policy:
Literal["freeze","delete"] and food_competition: Literal["first_arrival",
"closest", "random"]—or whatever exact set the orchestrator accepts); ensure the
Literal options exactly match MultiAgentOrchestrator's accepted values so type
checking and config validation fail early.
🊄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51ac3085-fa38-4488-9ac8-cb9249cd1dde

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 3dd1b6b and bc07e14.

📒 Files selected for processing (2)
  • packages/quantum-nematode/quantumnematode/env/env.py
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py

Comment thread packages/quantum-nematode/quantumnematode/env/env.py
Comment thread packages/quantum-nematode/quantumnematode/env/env.py Outdated
Comment thread packages/quantum-nematode/quantumnematode/env/env.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ðŸ§đ Nitpick comments (1)
packages/quantum-nematode/quantumnematode/agent/agent.py (1)

477-481: Consider exposing a public method for creating BrainParams with social context.

The _create_brain_params method is a private API (leading underscore), but based on the context snippets, MultiAgentSimulation calls it directly from outside this class. While this works, it couples the orchestrator to an internal implementation detail.

This is acceptable for now given the tight coupling between the agent and orchestrator in this multi-agent infrastructure, but consider adding a public wrapper method in a future iteration if the API surface stabilizes.

Also applies to: 607-609

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/agent/agent.py` around lines 477 -
481, The code currently exposes the private method _create_brain_params which
MultiAgentSimulation calls directly; add a public wrapper method
create_brain_params(self, action: ActionData | None = None, nearby_agents_count:
int | None = None) -> BrainParams that simply delegates to _create_brain_params
(preserving behavior and docstring) so external orchestrators call the stable
public API (create_brain_params) while keeping the original private
implementation for internal use; update any external callers (e.g.,
MultiAgentSimulation) to use create_brain_params instead of
_create_brain_params.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/agent/agent.py`:
- Around line 477-481: The code currently exposes the private method
_create_brain_params which MultiAgentSimulation calls directly; add a public
wrapper method create_brain_params(self, action: ActionData | None = None,
nearby_agents_count: int | None = None) -> BrainParams that simply delegates to
_create_brain_params (preserving behavior and docstring) so external
orchestrators call the stable public API (create_brain_params) while keeping the
original private implementation for internal use; update any external callers
(e.g., MultiAgentSimulation) to use create_brain_params instead of
_create_brain_params.

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1bde0b73-0571-4641-9875-064c4ff9362a

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between bc07e14 and 12477c5.

📒 Files selected for processing (7)
  • packages/quantum-nematode/quantumnematode/agent/agent.py
  • packages/quantum-nematode/quantumnematode/agent/multi_agent.py
  • packages/quantum-nematode/quantumnematode/brain/arch/_brain.py
  • packages/quantum-nematode/quantumnematode/brain/modules.py
  • packages/quantum-nematode/tests/quantumnematode_tests/agent/test_multi_agent.py
  • packages/quantum-nematode/tests/quantumnematode_tests/brain/test_social_module.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
✅ Files skipped from review due to trivial changes (2)
  • packages/quantum-nematode/quantumnematode/brain/arch/_brain.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py

chrisjz and others added 2 commits April 5, 2026 23:42
Mark SOCIAL_PROXIMITY module and nearby_agents_count as oracle sensing:
exact agent count is biologically dishonest — real C. elegans detects
conspecifics via ascaroside pheromone concentration gradients (ASK, ADL,
ASI neurons), not by counting individuals. Pheromone-based temporal
sensing planned for Phase 4 Deliverable 2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
â€Ķonfig types

copy() now deep-copies all AgentState entries (not just default), preserving
multi-agent state including per-agent body, visited_cells, HP, and comfort
counters.

add_agent() validates explicit positions: bounds check against grid_size,
occupancy check against existing agent positions. Raises ValueError for
out-of-bounds or occupied cells.

MultiAgentConfig.food_competition and termination_policy use Literal types
instead of bare str, catching invalid values at config parse time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chrisjz

chrisjz commented Apr 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Reviews resumed.

@chrisjz

chrisjz commented Apr 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

â™ŧïļ Duplicate comments (1)
packages/quantum-nematode/quantumnematode/env/env.py (1)

2196-2205: ⚠ïļ Potential issue | ðŸŸĄ Minor

Explicit position validation doesn't check food/predator occupancy.

When position is explicitly provided, validation only checks against other agent positions (line 2202-2203). However, random placement (line 2209-2211) also excludes food and predator positions. This inconsistency could allow placing an agent directly on a food source or predator position.

Consider whether this is intentional. If not, you may want to add food/predator position checks:

ðŸ›Ąïļ Suggested fix to validate against food/predator positions
         if position is not None:
             # Validate explicit position
             x, y = position
             if not (0 <= x < self.grid_size and 0 <= y < self.grid_size):
                 msg = f"Position {position} out of bounds for grid size {self.grid_size}."
                 raise ValueError(msg)
             occupied_positions = {a.position for a in self.agents.values()}
+            occupied_positions.update(self.foods)
+            if self.predator.enabled:
+                occupied_positions.update(p.position for p in self.predators)
             if position in occupied_positions:
-                msg = f"Position {position} already occupied by another agent."
+                msg = f"Position {position} is already occupied."
                 raise ValueError(msg)
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 2196 -
2205, The explicit-position validation block currently only checks bounds and
other agents (variables: position, self.grid_size, self.agents,
occupied_positions); update it to also reject positions that collide with food
and predators by computing the sets of food and predator positions (e.g., from
self.food and self.predators or their position attributes) and checking
membership before accepting the position so it matches the random-placement
exclusion logic.
ðŸ§đ Nitpick comments (3)
packages/quantum-nematode/quantumnematode/utils/config_loader.py (1)

911-932: Consolidate duplicated brain-resolution logic to reduce drift risk.

configure_brain_from_container repeats lookup/validation already present in configure_brain. Consider a shared helper so errors and behavior stay aligned.

â™ŧïļ Suggested refactor
+def _configure_brain_by_name_and_config(
+    brain_name: str,
+    brain_config: BrainConfigType,
+) -> BrainConfigType:
+    if brain_name not in BRAIN_CONFIG_MAP:
+        msg = f"Unknown brain type: {brain_name}."
+        raise ValueError(msg)
+    config_cls = BRAIN_CONFIG_MAP[brain_name]
+    return _resolve_brain_config(brain_config, config_cls, brain_name)
+
 def configure_brain(
     config: SimulationConfig,
 ) -> BrainConfigType:
@@
-    brain_name = config.brain.name
-
-    if brain_name not in BRAIN_CONFIG_MAP:
-        error_message = f"Unknown brain type: {config.brain.name}."
-        logger.error(error_message)
-        raise ValueError(error_message)
-
-    config_cls = BRAIN_CONFIG_MAP[brain_name]
-    return _resolve_brain_config(config.brain.config, config_cls, brain_name)
+    return _configure_brain_by_name_and_config(config.brain.name, config.brain.config)
@@
 def configure_brain_from_container(
     brain_container: BrainContainerConfig,
 ) -> BrainConfigType:
@@
-    brain_name = brain_container.name
-    if brain_name not in BRAIN_CONFIG_MAP:
-        msg = f"Unknown brain type: {brain_name}."
-        raise ValueError(msg)
-    config_cls = BRAIN_CONFIG_MAP[brain_name]
-    return _resolve_brain_config(brain_container.config, config_cls, brain_name)
+    return _configure_brain_by_name_and_config(brain_container.name, brain_container.config)
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/utils/config_loader.py` around
lines 911 - 932, configure_brain_from_container duplicates the brain-name lookup
and validation logic in configure_brain; extract the shared lookup/validation
into a small helper (e.g., get_brain_config_class or reuse an existing helper
inside configure_brain) that takes a brain_name and returns the config class or
raises the same ValueError, then have both configure_brain and
configure_brain_from_container call that helper and continue to call
_resolve_brain_config with the returned class; reference BRAIN_CONFIG_MAP,
configure_brain_from_container, configure_brain, and _resolve_brain_config when
making the change so behavior and error messages remain identical.
packages/quantum-nematode/quantumnematode/env/env.py (2)

2241-2254: Consider expanding warning to cover full-grid scenario.

The warning at lines 2247-2254 only triggers when min_distance > 0, but the truly random fallback (lines 2242-2246) can also occur when the grid is nearly full regardless of min_distance. Consider logging a warning in both cases to alert callers that placement may overlap with food/predators.

📝 Suggested improvement
                 if position is None:
                     # Last resort: truly random (grid is nearly full)
                     position = (
                         int(self.rng.integers(0, self.grid_size)),
                         int(self.rng.integers(0, self.grid_size)),
                     )
-                if min_distance > 0:
                     logger.warning(
-                        "Agent '%s' placed without min_distance=%d guarantee "
-                        "(Poisson sampling exhausted %d attempts)",
+                        "Agent '%s' placed at potentially occupied position %s "
+                        "(exhausted %d attempts, min_distance=%d)",
                         agent_id,
+                        position,
+                        MAX_POISSON_ATTEMPTS,
                         min_distance,
-                        MAX_POISSON_ATTEMPTS,
                     )
ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 2241 -
2254, The fallback that sets position via self.rng.integers(...) can occur even
when min_distance == 0, but the existing logger.warning only runs inside the if
min_distance > 0 block; update the placement logic so that whenever the Poisson
sampler is exhausted and the code falls back to the truly random assignment (the
position = (int(self.rng.integers(0, self.grid_size)), ...) branch), you emit a
logger.warning referencing agent_id, MAX_POISSON_ATTEMPTS and that the grid was
nearly full (and that placement may overlap food/predators), rather than only
logging when min_distance > 0 — e.g. move or duplicate the logger.warning to run
immediately after the random fallback so it executes regardless of min_distance.

3044-3108: Note: copy() overhead scales with agent count in hot paths.

Based on the relevant code snippets, env.copy() is called in hot paths during many-worlds visualization (runners.py:908, runners.py:960). The new implementation deep-copies all AgentState entries including their visited_cells sets and body lists. For the expected 2-10 agents, this overhead should be acceptable, but consider profiling if scaling to larger agent counts becomes necessary.

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 3044 -
3108, The copy() method on DynamicForagingEnvironment currently deep-copies each
AgentState including visited_cells (set) and body (list), causing overhead in
hot paths; change copy() to accept an optional parameter (e.g.,
share_agent_state: bool = False) and, when True, avoid deep-copying agent
collections by either assigning AgentState objects directly or performing a
shallow copy that reuses visited_cells and body references (modify the loop
creating new_env.agents to check share_agent_state and either
new_env.agents[aid] = state or create a lightweight AgentState that copies
scalars but reuses state.visited_cells and state.body); keep existing behavior
by default (share_agent_state=False) to preserve correctness and update callers
in hot paths (runners.py visualization) to pass share_agent_state=True where
safe.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 2196-2205: The explicit-position validation block currently only
checks bounds and other agents (variables: position, self.grid_size,
self.agents, occupied_positions); update it to also reject positions that
collide with food and predators by computing the sets of food and predator
positions (e.g., from self.food and self.predators or their position attributes)
and checking membership before accepting the position so it matches the
random-placement exclusion logic.

---

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 2241-2254: The fallback that sets position via
self.rng.integers(...) can occur even when min_distance == 0, but the existing
logger.warning only runs inside the if min_distance > 0 block; update the
placement logic so that whenever the Poisson sampler is exhausted and the code
falls back to the truly random assignment (the position =
(int(self.rng.integers(0, self.grid_size)), ...) branch), you emit a
logger.warning referencing agent_id, MAX_POISSON_ATTEMPTS and that the grid was
nearly full (and that placement may overlap food/predators), rather than only
logging when min_distance > 0 — e.g. move or duplicate the logger.warning to run
immediately after the random fallback so it executes regardless of min_distance.
- Around line 3044-3108: The copy() method on DynamicForagingEnvironment
currently deep-copies each AgentState including visited_cells (set) and body
(list), causing overhead in hot paths; change copy() to accept an optional
parameter (e.g., share_agent_state: bool = False) and, when True, avoid
deep-copying agent collections by either assigning AgentState objects directly
or performing a shallow copy that reuses visited_cells and body references
(modify the loop creating new_env.agents to check share_agent_state and either
new_env.agents[aid] = state or create a lightweight AgentState that copies
scalars but reuses state.visited_cells and state.body); keep existing behavior
by default (share_agent_state=False) to preserve correctness and update callers
in hot paths (runners.py visualization) to pass share_agent_state=True where
safe.

In `@packages/quantum-nematode/quantumnematode/utils/config_loader.py`:
- Around line 911-932: configure_brain_from_container duplicates the brain-name
lookup and validation logic in configure_brain; extract the shared
lookup/validation into a small helper (e.g., get_brain_config_class or reuse an
existing helper inside configure_brain) that takes a brain_name and returns the
config class or raises the same ValueError, then have both configure_brain and
configure_brain_from_container call that helper and continue to call
_resolve_brain_config with the returned class; reference BRAIN_CONFIG_MAP,
configure_brain_from_container, configure_brain, and _resolve_brain_config when
making the change so behavior and error messages remain identical.

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff895464-bb85-459f-bff6-5a09c2f7ce34

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 12477c5 and 67c7630.

📒 Files selected for processing (6)
  • packages/quantum-nematode/quantumnematode/brain/arch/_brain.py
  • packages/quantum-nematode/quantumnematode/brain/modules.py
  • packages/quantum-nematode/quantumnematode/env/env.py
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py
  • packages/quantum-nematode/tests/quantumnematode_tests/agent/test_multi_agent.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
✅ Files skipped from review due to trivial changes (2)
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
  • packages/quantum-nematode/tests/quantumnematode_tests/agent/test_multi_agent.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/quantum-nematode/quantumnematode/brain/arch/_brain.py
  • packages/quantum-nematode/quantumnematode/brain/modules.py

â€Ķwarning

add_agent() explicit-position validation now checks food and predator
positions in addition to agents, matching the random-placement exclusion
logic. Last-resort random placement (grid nearly full) now always warns
regardless of min_distance setting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chrisjz

chrisjz commented Apr 5, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ðŸ§đ Nitpick comments (1)
packages/quantum-nematode/quantumnematode/env/env.py (1)

2455-2458: Consider adding *_for variants for reset methods.

reset_thermotaxis() and reset_aerotaxis() only reset the default agent's counters. For multi-agent episode resets, the orchestrator would need to manually reset each agent's counters via direct AgentState access.

If the orchestrator handles this externally (which it likely does given the design), this is fine. Otherwise, adding reset_thermotaxis_for(agent_id) and reset_aerotaxis_for(agent_id) would provide a cleaner API.

Also applies to: 2622-2625

ðŸĪ– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/quantum-nematode/quantumnematode/env/env.py` around lines 2455 -
2458, Add per-agent reset variants so multi-agent orchestrators can reset
counters without touching AgentState directly: implement
reset_thermotaxis_for(agent_id) and reset_aerotaxis_for(agent_id) methods that
locate the AgentState for the given agent_id and reset the same fields that
reset_thermotaxis() / reset_aerotaxis() do (e.g., steps_in_comfort_zone,
total_thermotaxis_steps, and corresponding aerotaxis counters), keep the
existing no-arg methods as convenience wrappers that call the *_for methods for
the default agent, and update the docstrings to reflect multi-agent usage.
ðŸĪ– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 2455-2458: Add per-agent reset variants so multi-agent
orchestrators can reset counters without touching AgentState directly: implement
reset_thermotaxis_for(agent_id) and reset_aerotaxis_for(agent_id) methods that
locate the AgentState for the given agent_id and reset the same fields that
reset_thermotaxis() / reset_aerotaxis() do (e.g., steps_in_comfort_zone,
total_thermotaxis_steps, and corresponding aerotaxis counters), keep the
existing no-arg methods as convenience wrappers that call the *_for methods for
the default agent, and update the docstrings to reflect multi-agent usage.

â„đïļ Review info
⚙ïļ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb00a8e4-4a5e-4dea-a831-8b59f5aeba41

ðŸ“Ĩ Commits

Reviewing files that changed from the base of the PR and between 67c7630 and 995c685.

📒 Files selected for processing (1)
  • packages/quantum-nematode/quantumnematode/env/env.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant