Skip to content

Add needs-based village example: homeostatic agents with competing drives#439

Open
darraghmoran2025 wants to merge 2 commits intomesa:mainfrom
darraghmoran2025:add-needs-village-example
Open

Add needs-based village example: homeostatic agents with competing drives#439
darraghmoran2025 wants to merge 2 commits intomesa:mainfrom
darraghmoran2025:add-needs-village-example

Conversation

@darraghmoran2025
Copy link
Copy Markdown

@darraghmoran2025 darraghmoran2025 commented Mar 25, 2026

What this adds

A population of villagers on a Moore grid each manage four competing homeostatic needs — HUNGER, REST, SOCIAL, and SAFETY. Each step, each villager acts on whichever need is most urgent, moving toward the relevant resource or partner and satisfying it on arrival.

This demonstrates a needs-based (drive-reduction) decision-making architecture — a distinct paradigm from both the reactive agents (Schelling, WolfSheep) and BDI deliberation examples already in the repo.

New classes

Class Role
NeedSpec Dataclass configuring one homeostatic need (decay rate, threshold, satisfy amount)
NeedsAgent Abstract CellAgent subclass: need decay, urgency ordering, preemption counting
VillagerAgent Four-need concrete agent with greedy Manhattan movement
FoodSource Regenerating food patch (finite, competitive resource)
HomePatch Stationary rest site
ThreatAgent Random-walk predator; spikes SAFETY need via perception
VillageModel OrthogonalMooreGrid, DataCollector, Solara visualisation

Architecture contrast

Property Reactive BDI NeedsAgent (this PR)
Decision driver World state Explicit goals (pull) Internal drives (push)
Priority source Rule conditions Deliberation over desires Urgency ordering of floats
Plan horizon Single step Multi-step intention queue Single next action
Interruption N/A Plan invalidation Continuous preemption on priority flip

Running

pip install -r requirements.txt
solara run app.py

Test plan

  • solara run app.py opens and grid renders with colour-coded villagers
  • Sliders update the model on reset
  • Need level chart shows oscillation between HUNGER and REST
  • Setting Threats = 0 runs without error
  • from needs_village import VillageModel; m = VillageModel(); m.step() runs cleanly

Note: PR #432 also explores needs-based drives, but extends the existing Wolf-Sheep model with two drives (hunger, fear) and no shared base class. This PR takes a different approach: an original scenario, four competing needs including a perception-driven SAFETY need and a mutual SOCIAL need, and a reusable abstract NeedsAgent / NeedSpec architecture that any model could subclass.

Darragh Moran and others added 2 commits March 25, 2026 12:02
…ives

N villagers on a Moore grid each manage four competing homeostatic needs
(HUNGER, REST, SOCIAL, SAFETY). Each step the villager acts on whichever
need is most urgent, moving toward the relevant resource or partner and
satisfying it on arrival.

Demonstrates a third decision-making paradigm alongside the reactive and
BDI examples already in the repo: internal drives that push agents rather
than goals that pull them, with continuous priority preemption.

New classes:
  NeedSpec        — dataclass configuring one homeostatic need
  NeedsAgent      — abstract CellAgent with decay, urgency ordering,
                    preemption counting (Pain Point mesa#16)
  VillagerAgent   — four-need concrete agent; greedy Manhattan movement
  FoodSource      — regenerating food patch (resource competition)
  HomePatch       — stationary rest site
  ThreatAgent     — random-walk predator; spikes SAFETY via perception
  VillageModel    — OrthogonalMooreGrid, DataCollector, Solara app
Copy link
Copy Markdown

@TomRodger TomRodger left a comment

Choose a reason for hiding this comment

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

Overall, the model logic is sound and runs without errors. The architecture is genuinely interesting, the NeedSpec dataclass, and NeedsAgent abstraction are clean and reusable, and the Pain Points documentation is well written and directly useful for the Behavioural Framework project.

# ──────────────────────────────────────────────────────────────────────── #


class FoodSource(CellAgent):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FoodSource is a stationary environment object that never moves after placement. The Mesa convention for stationary agents is FixedAgent, rather than CellAgent (same as GrassPatch in Mesa's built-in Sugarscape example). Using CellAgent works but semantically, it is incorrect and could affect Mesa's internal agent tracking.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the feedback. I've updated both FoodSource and
HomePatch to inherit from FixedAgent instead of CellAgent.

You're right that CellAgent worked functionally, but the
semantics were wrong — both are environment objects placed
once at initialisation and never moved, which is exactly what
FixedAgent is designed for. Using CellAgent implied they were
mobile agents, which could cause issues with Mesa's internal
agent tracking down the line.

The fix mirrors the convention used in Mesa's own Sugarscape
example (GrassPatch inherits from FixedAgent), so it aligns
with the established pattern in the framework.

return self.food == 0


class HomePatch(CellAgent):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same fix as FoodSource

# Solara app #
# ──────────────────────────────────────────────────────────────────────── #

page = SolaraViz(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SolaraViz on Mesa 3.5.1 expects a model instance rather than the class. Passing VillageModel directly causes the visualisation to fail with [AttributeError: type object 'VillageModel' has no attribute 'datacollector']. Fix: you instantiate the model first and then pass it in e.g. model = VillageModel(), then pass model to SolaraViz

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. I've updated app.py to instantiate VillageModel before passing it to SolaraViz:

model = VillageModel()

page = SolaraViz(
model,
components=[SpaceComponent, NeedsChart, ActiveNeedChart, PreemptionChart],
model_params=model_params,
name="Needs-Based Village",
)

Passing the class directly caused the AttributeError because SolaraViz in Mesa 3.5.1 immediately tries to access instance attributes like datacollector on whatever is passed in. The
interactive sliders in model_params still work — SolaraViz handles re-instantiation when parameters change.

@@ -0,0 +1 @@
mesa[viz]>=3.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

mesa[viz]>=3.0 is unspecific. When running the Solara visualisation in Mesa 3.5.1 it crashes with an AttributeError in mesa_signals/core.py. The model logic itself runs cleanly. Naming a specific model or stricter constraints would help improve reproducibility.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed. I've tightened the constraint in requirements.txt to mesa[viz]>=3.5.1,<4.0.

The >=3.0 bound was too loose — any version in the 3.x range would satisfy it, but earlier releases hit the AttributeError in mesa_signals/core.py you flagged. Pinning to >=3.5.1
targets the version the example was developed and tested against, and <4.0 guards against breaking changes in a future major release.



@dataclass
class NeedSpec:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The NeedSpec dataclass is a clean design choice here. Keeping need configuration separate from the agent logic will make it easy for someone to extend the code, only needing to touch one place when tweaking decay rates, or adding a fifth. It also makes VillagerAgent read much cleaner as a result.

paradigm alongside reactive agents and BDI deliberation. Needs are
*pushed* by autonomous decay; they can only be suppressed, never eliminated.

Pain Points Surfaced
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good pain points documentation, labelling the workarounds with numbers and connecting them to specific limitations is very useful evidence for the Behavioural Framework case.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Hi Tom sorry for the inactivity on my end. College has been exceptionally busy. Hopefully with exams over soon I will have more time. I have responded to all of your queries and pushed the relevant changes. Hope that is acceptable. Thank you!

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.

2 participants