From 1b02c3a774b83f5a3ef0e47689f0a1cfaaf0f9b1 Mon Sep 17 00:00:00 2001 From: pragam-m25 Date: Fri, 16 Jan 2026 19:00:31 +0530 Subject: [PATCH 1/8] fix: add adaptive risk agents example with relative-import tests --- examples/adaptive_risk_agents/README.md | 83 ++++++++++++++++++ examples/adaptive_risk_agents/__init__.py | 0 examples/adaptive_risk_agents/agents.py | 86 +++++++++++++++++++ examples/adaptive_risk_agents/model.py | 20 +++++ examples/adaptive_risk_agents/run.py | 38 ++++++++ .../tests/test_agent_smoke.py | 11 +++ .../adaptive_risk_agents/tests/test_smoke.py | 18 ++++ 7 files changed, 256 insertions(+) create mode 100644 examples/adaptive_risk_agents/README.md create mode 100644 examples/adaptive_risk_agents/__init__.py create mode 100644 examples/adaptive_risk_agents/agents.py create mode 100644 examples/adaptive_risk_agents/model.py create mode 100644 examples/adaptive_risk_agents/run.py create mode 100644 examples/adaptive_risk_agents/tests/test_agent_smoke.py create mode 100644 examples/adaptive_risk_agents/tests/test_smoke.py diff --git a/examples/adaptive_risk_agents/README.md b/examples/adaptive_risk_agents/README.md new file mode 100644 index 000000000..a368e3706 --- /dev/null +++ b/examples/adaptive_risk_agents/README.md @@ -0,0 +1,83 @@ +# Adaptive Risk Agents + +This example demonstrates agents that **adapt their risk-taking behavior +based on past experiences**, implemented using only core Mesa primitives. + +The model is intentionally simple in structure but rich in behavior, making it +useful as a diagnostic example for understanding how adaptive decision-making +is currently modeled in Mesa. + + + +## Motivation + +Many real-world agents do not follow fixed rules. +Instead, they: + +- make decisions under uncertainty, +- remember past outcomes, +- adapt future behavior based on experience. + +In Mesa today, modeling this kind of adaptive behavior often results in +a large amount of logic being concentrated inside `agent.step()`, combining +multiple concerns in a single execution phase. + +This example exists to **make that structure explicit**, not to abstract it away. + + + +## Model Overview + +- Each agent chooses between: + - a **safe action** (low or zero payoff, no risk), + - a **risky action** (stochastic payoff). +- Agents track recent outcomes of risky actions in a short memory window. +- If recent outcomes are negative, agents become more risk-averse. +- If outcomes are positive, agents increase their risk preference. + +All behavior is implemented using plain Python and Mesa’s public APIs. + + + +## Observations From This Example + +This model intentionally does **not** introduce new abstractions +(tasks, goals, states, schedulers, etc.). + +Instead, it highlights several patterns that commonly arise when modeling +adaptive behavior in Mesa today: + +- Decision-making, action execution, memory updates, and learning logic + are handled within a single `step()` method. +- There is no explicit separation between decision phases. +- Actions are instantaneous, with no notion of duration or interruption. +- As behaviors grow richer, agent logic can become deeply nested and harder + to maintain. + +These observations may be useful input for ongoing discussions around: + +- Behavioral frameworks +- Tasks and continuous states +- Richer agent decision abstractions + + + +## Mesa Version & API Alignment + +This example is written to align with the **Mesa 4 design direction**: + +- Uses `AgentSet` and `shuffle_do` +- Avoids deprecated schedulers +- Avoids `DataCollector` +- Uses keyword-only arguments for public APIs +- Relies on `model.random` for reproducibility + +No experimental or private APIs are used. + + + +## Running the Example + +From the Mesa repository root: + +python -m mesa.examples.adaptive_risk_agents.run diff --git a/examples/adaptive_risk_agents/__init__.py b/examples/adaptive_risk_agents/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/adaptive_risk_agents/agents.py b/examples/adaptive_risk_agents/agents.py new file mode 100644 index 000000000..defc6be5b --- /dev/null +++ b/examples/adaptive_risk_agents/agents.py @@ -0,0 +1,86 @@ +"""Adaptive Risk Agent. + +An agent that chooses between safe and risky actions and adapts its +risk preference based on past outcomes. + +This example intentionally keeps all decision logic inside `step()` +to highlight current limitations in Mesa's behavior modeling. +""" + +from __future__ import annotations + +from collections import deque + +from mesa import Agent + + +class AdaptiveRiskAgent(Agent): + """An agent that adapts its risk-taking behavior over time. + + Attributes + ---------- + risk_preference : float + Probability (0-1) of choosing a risky action. + memory : deque[int] + Recent outcomes of risky actions (+1 reward, -1 loss). + """ + + def __init__( + self, + model, + *, + initial_risk_preference: float = 0.5, + memory_size: int = 10, + ) -> None: + super().__init__(model) + self.risk_preference = initial_risk_preference + self.memory: deque[int] = deque(maxlen=memory_size) + + def choose_action(self) -> str: + """Choose between a safe or risky action.""" + if self.model.random.random() < self.risk_preference: + return "risky" + return "safe" + + def risky_action(self) -> int: + """Perform a risky action. + + Returns + ------- + int + Outcome of the action (+1 for reward, -1 for loss). + """ + return 1 if self.model.random.random() < 0.5 else -1 + + def safe_action(self) -> int: + """Perform a safe action.Returns ------- int Guaranteed neutral outcome.""" + return 0 + + def update_risk_preference(self) -> None: + """Update risk preference based on recent memory.""" + if not self.memory: + return + + avg_outcome = sum(self.memory) / len(self.memory) + + if avg_outcome < 0: + self.risk_preference = max(0.0, self.risk_preference - 0.05) + else: + self.risk_preference = min(1.0, self.risk_preference + 0.05) + + def step(self) -> None: + """Execute one decision step. + + NOTE: + This method intentionally mixes decision-making, action execution, + memory updates, and learning to demonstrate how behavioral + complexity accumulates in current Mesa models. + """ + action = self.choose_action() + + if action == "risky": + outcome = self.risky_action() + self.memory.append(outcome) + self.update_risk_preference() + else: + self.safe_action() diff --git a/examples/adaptive_risk_agents/model.py b/examples/adaptive_risk_agents/model.py new file mode 100644 index 000000000..3cb2c9693 --- /dev/null +++ b/examples/adaptive_risk_agents/model.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from mesa import Model + +from examples.adaptive_risk_agents.agents import AdaptiveRiskAgent + + +class AdaptiveRiskModel(Model): + """A simple model running adaptive risk-taking agents.""" + + def __init__(self, n_agents: int = 50, *, seed: int | None = None) -> None: + super().__init__(seed=seed) + + # Create agents — Mesa will register them automatically + for _ in range(n_agents): + AdaptiveRiskAgent(self) + + def step(self) -> None: + """Advance the model by one step.""" + self.agents.shuffle_do("step") diff --git a/examples/adaptive_risk_agents/run.py b/examples/adaptive_risk_agents/run.py new file mode 100644 index 000000000..895d05998 --- /dev/null +++ b/examples/adaptive_risk_agents/run.py @@ -0,0 +1,38 @@ +"""Run script for the Adaptive Risk Agents example. + +This script runs the model for a fixed number of steps and prints +aggregate statistics to illustrate how agent behavior evolves over time. + +Intentionally simple: +- No DataCollector +- No batch_run +- No visualization +""" + +from __future__ import annotations + +from examples.adaptive_risk_agents.model import AdaptiveRiskModel + + +def run_model(*, n_agents: int = 50, steps: int = 100, seed: int | None = None) -> None: + """Run the AdaptiveRiskModel and print summary statistics.""" + model = AdaptiveRiskModel(n_agents=n_agents, seed=seed) + + for step in range(steps): + model.step() + + total_risk = 0.0 + count = 0 + + for agent in model.agents: + total_risk += agent.risk_preference + count += 1 + + avg_risk = total_risk / count if count > 0 else 0.0 + + if step % 10 == 0: + print(f"Step {step:3d} | Average risk preference: {avg_risk:.3f}") + + +if __name__ == "__main__": + run_model() diff --git a/examples/adaptive_risk_agents/tests/test_agent_smoke.py b/examples/adaptive_risk_agents/tests/test_agent_smoke.py new file mode 100644 index 000000000..ffdb3d5bc --- /dev/null +++ b/examples/adaptive_risk_agents/tests/test_agent_smoke.py @@ -0,0 +1,11 @@ +from examples.adaptive_risk_agents.model import AdaptiveRiskModel + + +def test_agent_methods_execute(): + model = AdaptiveRiskModel(n_agents=1, seed=1) + agent = next(iter(model.agents)) + + action = agent.choose_action() + assert action in {"safe", "risky"} + + agent.step() # should not crash diff --git a/examples/adaptive_risk_agents/tests/test_smoke.py b/examples/adaptive_risk_agents/tests/test_smoke.py new file mode 100644 index 000000000..6aefa6ec9 --- /dev/null +++ b/examples/adaptive_risk_agents/tests/test_smoke.py @@ -0,0 +1,18 @@ +"""Smoke tests for the Adaptive Risk Agents example. + +These tests only verify that the example runs without crashing. +They intentionally avoid checking model outcomes or behavior. +""" + +from examples.adaptive_risk_agents.model import AdaptiveRiskModel + + +def test_model_initializes(): + model = AdaptiveRiskModel(n_agents=10, seed=42) + assert model is not None + + +def test_model_steps_without_error(): + model = AdaptiveRiskModel(n_agents=10, seed=42) + for _ in range(5): + model.step() From 130641ff0abd07dc53efc6cfc30df74325eaf443 Mon Sep 17 00:00:00 2001 From: pragam-m25 Date: Fri, 23 Jan 2026 22:57:12 +0530 Subject: [PATCH 2/8] example: decision interrupt agent using event-based scheduling --- examples/decision_interrupt_agent/README.md | 34 +++++++++++++++ examples/decision_interrupt_agent/__init__.py | 0 examples/decision_interrupt_agent/agents.py | 36 ++++++++++++++++ examples/decision_interrupt_agent/model.py | 42 +++++++++++++++++++ examples/decision_interrupt_agent/run.py | 10 +++++ .../tests/__init__.py | 0 .../tests/test_smoke.py | 12 ++++++ 7 files changed, 134 insertions(+) create mode 100644 examples/decision_interrupt_agent/README.md create mode 100644 examples/decision_interrupt_agent/__init__.py create mode 100644 examples/decision_interrupt_agent/agents.py create mode 100644 examples/decision_interrupt_agent/model.py create mode 100644 examples/decision_interrupt_agent/run.py create mode 100644 examples/decision_interrupt_agent/tests/__init__.py create mode 100644 examples/decision_interrupt_agent/tests/test_smoke.py diff --git a/examples/decision_interrupt_agent/README.md b/examples/decision_interrupt_agent/README.md new file mode 100644 index 000000000..efb45dc57 --- /dev/null +++ b/examples/decision_interrupt_agent/README.md @@ -0,0 +1,34 @@ +# Decision Interrupt Agent Example + +This example demonstrates **agent-level interruptions** in Mesa +using **event-based scheduling** instead of step-based counters. + +## Motivation + +Many Mesa models implement long-running actions (e.g. jail sentences, +cooldowns, delays) using counters inside `step()`. This example shows +how such behavior can be modeled more naturally using scheduled events. + +## Key Concepts Demonstrated + +- Agent actions with duration (jail sentence) +- Event-based interruption and resumption +- No polling or step counters +- Minimal logic inside `step()` + +## Model Description + +- Agents normally perform actions when FREE +- At scheduled times, one agent is arrested +- Arrested agents do nothing while IN_JAIL +- Release is handled automatically via a scheduled event + +## Files + +- `agents.py` – Agent logic with interruption and release +- `model.py` – Model-level scheduling of arrests +- `run.py` – Minimal script to run the model + +## How to Run + +python run.py diff --git a/examples/decision_interrupt_agent/__init__.py b/examples/decision_interrupt_agent/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/decision_interrupt_agent/agents.py b/examples/decision_interrupt_agent/agents.py new file mode 100644 index 000000000..7242d42d8 --- /dev/null +++ b/examples/decision_interrupt_agent/agents.py @@ -0,0 +1,36 @@ +from mesa import Agent + + +class DecisionAgent(Agent): + """ + Agent that can be temporarily interrupted (jailed) + and later resume normal behavior. + """ + + def __init__(self, model): + super().__init__(model) + self.status = "FREE" + self.release_time = None + + def act(self): + """Normal agent behavior when free.""" + # placeholder for real logic + + def get_arrested(self, sentence: int, current_time: int): + """ + Interrupt the agent for a fixed duration. + """ + self.status = "IN_JAIL" + self.release_time = current_time + sentence + + def step(self, current_time: int): + """ + Either remain inactive if jailed or act normally. + """ + if self.status == "IN_JAIL": + if current_time >= self.release_time: + self.status = "FREE" + self.release_time = None + return + + self.act() diff --git a/examples/decision_interrupt_agent/model.py b/examples/decision_interrupt_agent/model.py new file mode 100644 index 000000000..5504b012a --- /dev/null +++ b/examples/decision_interrupt_agent/model.py @@ -0,0 +1,42 @@ +import random + +from mesa import Model + +from .agents import DecisionAgent + + +class DecisionModel(Model): + """ + Demonstrates agent-level interruptions (arrest → release) + using step-based timing compatible with Mesa 3.4.x. + """ + + def __init__(self, n_agents: int = 5): + super().__init__() + + self.time = 0 + self.my_agents = [DecisionAgent(self) for _ in range(n_agents)] + + self.next_arrest_time = 3 + + def arrest_someone(self): + """Randomly arrest one free agent.""" + free_agents = [a for a in self.my_agents if a.status == "FREE"] + if not free_agents: + return + + agent = random.choice(free_agents) + agent.get_arrested(sentence=4, current_time=self.time) + + def step(self): + """ + Advance the model by one step. + """ + self.time += 1 + + if self.time == self.next_arrest_time: + self.arrest_someone() + self.next_arrest_time += 6 + + for agent in self.my_agents: + agent.step(self.time) diff --git a/examples/decision_interrupt_agent/run.py b/examples/decision_interrupt_agent/run.py new file mode 100644 index 000000000..cc74cdbf6 --- /dev/null +++ b/examples/decision_interrupt_agent/run.py @@ -0,0 +1,10 @@ +from .model import DecisionModel + + +def main(): + model = DecisionModel(n_agents=5) + for _ in range(20): + model.step() + +if __name__ == "__main__": + main() diff --git a/examples/decision_interrupt_agent/tests/__init__.py b/examples/decision_interrupt_agent/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/decision_interrupt_agent/tests/test_smoke.py b/examples/decision_interrupt_agent/tests/test_smoke.py new file mode 100644 index 000000000..a15f084dc --- /dev/null +++ b/examples/decision_interrupt_agent/tests/test_smoke.py @@ -0,0 +1,12 @@ +from examples.decision_interrupt_agent.model import DecisionModel + + +def test_decision_interrupt_model_runs(): + """ + Smoke test: model should run without errors + while handling agent interruptions. + """ + model = DecisionModel(n_agents=3) + + for _ in range(15): + model.step() From c2da6800231b0166bf6eb12656ea7f2f89fe8e2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:38:42 +0000 Subject: [PATCH 3/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/decision_interrupt_agent/run.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/decision_interrupt_agent/run.py b/examples/decision_interrupt_agent/run.py index cc74cdbf6..57196fec9 100644 --- a/examples/decision_interrupt_agent/run.py +++ b/examples/decision_interrupt_agent/run.py @@ -6,5 +6,6 @@ def main(): for _ in range(20): model.step() + if __name__ == "__main__": main() From c5a24816e15c17e104d471563294b0791bdca6df Mon Sep 17 00:00:00 2001 From: pragam-m25 Date: Sat, 24 Jan 2026 12:21:58 +0530 Subject: [PATCH 4/8] fix: correct time progression in DecisionModel --- examples/decision_interrupt_agent/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/decision_interrupt_agent/model.py b/examples/decision_interrupt_agent/model.py index 5504b012a..33e629139 100644 --- a/examples/decision_interrupt_agent/model.py +++ b/examples/decision_interrupt_agent/model.py @@ -14,7 +14,7 @@ class DecisionModel(Model): def __init__(self, n_agents: int = 5): super().__init__() - self.time = 0 + self.my_agents = [DecisionAgent(self) for _ in range(n_agents)] self.next_arrest_time = 3 @@ -32,7 +32,7 @@ def step(self): """ Advance the model by one step. """ - self.time += 1 + if self.time == self.next_arrest_time: self.arrest_someone() From 2d9694ad6f591fb35cdfdbbd18cb0929fb423273 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 06:53:45 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/decision_interrupt_agent/model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/decision_interrupt_agent/model.py b/examples/decision_interrupt_agent/model.py index 33e629139..2357280b0 100644 --- a/examples/decision_interrupt_agent/model.py +++ b/examples/decision_interrupt_agent/model.py @@ -14,7 +14,6 @@ class DecisionModel(Model): def __init__(self, n_agents: int = 5): super().__init__() - self.my_agents = [DecisionAgent(self) for _ in range(n_agents)] self.next_arrest_time = 3 @@ -32,7 +31,6 @@ def step(self): """ Advance the model by one step. """ - if self.time == self.next_arrest_time: self.arrest_someone() From 298cfd262f93aa5e0612e53dca686d9c472033d0 Mon Sep 17 00:00:00 2001 From: pragam-m25 Date: Sat, 24 Jan 2026 22:39:11 +0530 Subject: [PATCH 6/8] docs: fix README formatting and remove forced line breaks --- examples/adaptive_risk_agents/README.md | 46 +++++++++---------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/examples/adaptive_risk_agents/README.md b/examples/adaptive_risk_agents/README.md index a368e3706..9b40dea59 100644 --- a/examples/adaptive_risk_agents/README.md +++ b/examples/adaptive_risk_agents/README.md @@ -1,30 +1,22 @@ # Adaptive Risk Agents -This example demonstrates agents that **adapt their risk-taking behavior -based on past experiences**, implemented using only core Mesa primitives. - -The model is intentionally simple in structure but rich in behavior, making it -useful as a diagnostic example for understanding how adaptive decision-making -is currently modeled in Mesa. - +This example demonstrates agents that **adapt their risk-taking behavior based on past experiences**, implemented using only core Mesa primitives.The model is intentionally simple in structure but rich in behavior, making it useful as a diagnostic example for understanding how adaptive decision-making is currently modeled in Mesa. +--- ## Motivation -Many real-world agents do not follow fixed rules. -Instead, they: +Many real-world agents do not follow fixed rules. Instead, they: - make decisions under uncertainty, - remember past outcomes, - adapt future behavior based on experience. -In Mesa today, modeling this kind of adaptive behavior often results in -a large amount of logic being concentrated inside `agent.step()`, combining -multiple concerns in a single execution phase. +In Mesa today, modeling this kind of adaptive behavior often results in a large amount of logic being concentrated inside `agent.step()`, combining multiple concerns in a single execution phase. This example exists to **make that structure explicit**, not to abstract it away. - +--- ## Model Overview @@ -37,30 +29,26 @@ This example exists to **make that structure explicit**, not to abstract it away All behavior is implemented using plain Python and Mesa’s public APIs. - +--- ## Observations From This Example -This model intentionally does **not** introduce new abstractions -(tasks, goals, states, schedulers, etc.). +This model intentionally does **not** introduce new abstractions (tasks, goals, states, schedulers, etc.). -Instead, it highlights several patterns that commonly arise when modeling -adaptive behavior in Mesa today: +Instead, it highlights several patterns that commonly arise when modeling adaptive behavior in Mesa today: -- Decision-making, action execution, memory updates, and learning logic - are handled within a single `step()` method. +- Decision-making, action execution, memory updates, and learning logic are handled within a single `step()` method. - There is no explicit separation between decision phases. - Actions are instantaneous, with no notion of duration or interruption. -- As behaviors grow richer, agent logic can become deeply nested and harder - to maintain. +- As behaviors grow richer, agent logic can become deeply nested and harder to maintain. These observations may be useful input for ongoing discussions around: -- Behavioral frameworks -- Tasks and continuous states -- Richer agent decision abstractions - +- behavioral frameworks, +- tasks and continuous states, +- richer agent decision abstractions. +--- ## Mesa Version & API Alignment @@ -74,10 +62,8 @@ This example is written to align with the **Mesa 4 design direction**: No experimental or private APIs are used. - +--- ## Running the Example -From the Mesa repository root: - -python -m mesa.examples.adaptive_risk_agents.run +From the Mesa repository root: python -m mesa.examples.adaptive_risk_agents.run From 121554774bd9a6aa8de73fed21a88da90e341e2d Mon Sep 17 00:00:00 2001 From: pragam-m25 Date: Sat, 24 Jan 2026 23:14:09 +0530 Subject: [PATCH 7/8] fix: respect Mesa time handling in DecisionModel.step --- examples/decision_interrupt_agent/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/decision_interrupt_agent/model.py b/examples/decision_interrupt_agent/model.py index 2357280b0..b20a8a2a1 100644 --- a/examples/decision_interrupt_agent/model.py +++ b/examples/decision_interrupt_agent/model.py @@ -31,6 +31,7 @@ def step(self): """ Advance the model by one step. """ + super().step() if self.time == self.next_arrest_time: self.arrest_someone() From 2de96802626eefce47e14fdd7fc3df6e0c5484dc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:48:46 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/decision_interrupt_agent/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/decision_interrupt_agent/model.py b/examples/decision_interrupt_agent/model.py index b20a8a2a1..bf908f1d3 100644 --- a/examples/decision_interrupt_agent/model.py +++ b/examples/decision_interrupt_agent/model.py @@ -31,7 +31,7 @@ def step(self): """ Advance the model by one step. """ - super().step() + super().step() if self.time == self.next_arrest_time: self.arrest_someone()