Negotiation Model Tutorial#

About This Tutorial#

This tutorial is inspired by the official Mesa-LLM Negotiation Example

The goal here is not to replicate the original example, but to present a simplified and tutorial-friendly version that focuses on reasoning structure and agent interaction.

Key differences from the original example include:

  • A reduced number of agents

  • No enforced negotiation protocol

  • No spatial environment or grid

  • Emphasis on understanding ReAct reasoning output

This makes the example easier to follow for new users while still demonstrating core Mesa-LLM concepts.

Model Description#

This Model simulates a basic negotiation scenario involving:

  • One seller agent with a minimum acceptable price

  • Two buyer agents, each with a different budget

Each buyer:#

  • Has a different budget

  • Reasons independently using ReActReasoning

  • Buyers do not coordinate with each other

Seller Agent#

  • Inherits from LLMAgent

  • Uses ReActReasoning to reason about negotiation decisions

  • Considers its minimum acceptable price and the current simulation step

Tutorial Setup#

Ensure you are using Python 3.12 or later.

The example uses Ollama as the default LLM provider. Ensure Ollama is installed, and the local server is running at http://localhost:11434. For detailed setup instructions, refer to the Tutorial Setup section of the first tutorial.

Install Mesa-LLM and required packages#

Install Mesa-LLM

pip install -U mesa-llm

Why This Model Is Non-Spatial#

Negotiation is a conceptual process rather than a spatial one. Buyers and sellers negotiate based on preferences, constraints, and reasoning—not physical position. To keep this tutorial simple and focused, no grid or physical environment is introduced. Adding a grid would increase complexity without improving the clarity of the negotiation logic.

Model Execution#

At each model step:

  • The model advances one step

  • All agents are activated using shuffle_do("step"). Each agent generates a reasoning plan using ReActReasoning and applies it. The console output displays the internal reasoning traces.

Agent Messaging#

In negotiation scenarios, agents need to exchange information. Mesa-LLM supports agent-to-agent messaging, allowing agents to communicate explicitly rather than relying on shared state.

In this tutorial:

  • The seller sends its minimum acceptable price to buyers

  • Buyers read incoming messages before reasoning

  • Messaging is used only for demonstration purposes

Creating the Seller class (Agent)#

Using the previously imported dependencies, we define the agent class: The Seller agent inherits from LLMAgent and uses ReActReasoning to decide how to respond during negotiation. It reasons about the current simulation step and its minimum acceptable price.

# Import Dependencies
from mesa.model import Model
from mesa_llm.llm_agent import LLMAgent
from mesa_llm.reasoning.react import ReActReasoning
from mesa_llm.memory.st_lt_memory import STLTMemory

# ---------------- SELLER ----------------
class Seller(LLMAgent):
    """
    Seller agent participating in a simple negotiation scenario.

    The seller has a minimum acceptable price and communicates this
    information to other agents using the messaging system.
    """

    def __init__(self, *args, **kwargs):
        # Initialize the agent using Mesa's base Agent initialization.
        # `LLMAgent` is a wrapper around Mesa's Agent, so all arguments
        # are forwarded to ensure proper registration in the model.
        super().__init__(*args, **kwargs)

        # Attach memory to allow the LLM to maintain context
        # across multiple simulation steps.
        self.memory = STLTMemory(
            agent=self,
            llm_model="ollama/llama3",
        )

    def step(self):
        """
        Called once per model step.

        The seller:
        1. Sends its minimum acceptable price to other agents
        2. Builds an observation for the reasoning module
        3. Uses ReActReasoning to generate a reasoning trace
        """

        # Send the seller's minimum acceptable price to all other agents.
        # This demonstrates explicit agent-to-agent communication.
        for agent in self.model.agents:
            if agent is not self:
                self.send_message(
                    f"My minimum acceptable price is {self.internal_state['min_price']}.",
                    [agent]
                    )

        # Observation passed to the reasoning module.
        # Includes the current step and seller-specific constraints.
        observation = {
            "step": self.model.steps,
            "min_price": self.internal_state["min_price"]
        }

        # Prompt describing the seller's role in the negotiation.
        # The LLM is asked to reason about the situation,
        # not to execute any concrete actions.
        prompt = """
        You are a seller negotiating the price of a single item.
        Reason about your position given your minimum acceptable price.
        """

        # Generate a reasoning plan using the configured reasoning strategy.
        plan = self.reasoning.plan(
            prompt=prompt,
            obs=observation
        )

        # Apply the plan.
        # In this tutorial, actions are not executed and appear only
        # as part of the reasoning trace.
        self.apply_plan(plan)

Creating the Buyer Class#

Each buyer:#

  • Has a different budget

  • Reasons independently using ReActReasoning

  • Buyers do not coordinate with each other

# ---------------- BUYER ----------------
class Buyer(LLMAgent):
    """
    Buyer agent participating in the negotiation.

    The buyer has a fixed budget and reasons about whether a potential
    purchase is worthwhile based on its constraints and any messages
    received from other agents.
    """

    def __init__(self, *args, **kwargs):
        # Standard LLMAgent initialization.
        # This ensures the buyer is properly registered in the Mesa model.
        super().__init__(*args, **kwargs)

        # Attach memory so the LLM can retain context across steps.
        # This is optional, but useful for observing evolving reasoning.
        self.memory = STLTMemory(
            agent=self,
            llm_model="ollama/llama3",

        )

    def step(self):
        """
        Called once per model step.

        The buyer reasons about the negotiation using its budget
        and any information stored in memory (including messages
        sent by other agents).

        """
        # Observation passed to the reasoning module.
        # Messages sent by other agents are already stored in memory
        # and implicitly available to the reasoning process.

        observation = {

            "step": self.model.steps,
            "budget": self.internal_state["budget"],
        }

        prompt = """
        You are a buyer negotiating to purchase a single item.
        You have a fixed budget.

        Use any relevant information you have received so far
        when reasoning about the negotiation.

        """

        plan = self.reasoning.plan(
            prompt=prompt,
            obs=observation
        )
        self.apply_plan(plan)

Creating Negotiation Model#

The NegotiationModel sets up and runs the negotiation between buyers and a seller. It creates the agents, assigns their roles, and controls when each agent acts.

How it works:#

  • Inherits from Mesa’s Model class.

  • Creates one seller agent with a minimum acceptable price.

  • Creates two buyer agents with different budgets.

  • Assigns ReActReasoning to all agents so they can reason using an LLM.

  • Uses shuffle_do("step") to let all agents act once per model step.

  • Prints the current step number to track simulation progress.

  • Repeats this process for a fixed number of steps in the main loop.

# ---------------- MODEL ----------------
class NegotiationModel(Model):
    """
    Model coordinating a simple negotiation scenario.

    The model:
    - Creates one seller and two buyers
    - Assigns different constraints to each agent
    - Advances the simulation step by step
    """

    def __init__(self, llm_model="ollama/llama3", seed=None):
        # Initialize the Mesa model.
        # The seed is optional and can be used for reproducibility.
        super().__init__(seed=seed)

        # Create a single seller agent with a minimum acceptable price.
        # Agents are created using Mesa's create_agents() helper.
        Seller.create_agents(
            model=self,
            n=1,
            reasoning=ReActReasoning,
            llm_model=llm_model,
            system_prompt="You are a seller.",
            internal_state={"min_price": 60},
        )

        # Create the first buyer with a higher budget.
        Buyer.create_agents(
            model=self,
            n=1,
            reasoning=ReActReasoning,
            llm_model=llm_model,
            system_prompt="You are a buyer.",
            internal_state={"budget": 100},
        )

        # Create the second buyer with a lower budget.
        Buyer.create_agents(
            model=self,
            n=1,
            reasoning=ReActReasoning,
            llm_model=llm_model,
            system_prompt="You are a buyer.",
            internal_state={"budget": 70},
        )

    def step(self):
        """
        Advance the model by one step.

        At each step:
        - All agents are activated once
        - Activation order is randomized using `shuffle_do`
        - Each agent performs its reasoning for this step
        """

        # Print the current step number for clarity in the output
        print(f"\n--- Model step {self.steps} ---")

        # Activate all agents in random order
        self.agents.shuffle_do("step")

Running the Model#

  • The Model runs for a few steps using a loop.

  • In each step, all agents think and act once.

  • The reasoning output is printed to the console.

# ---------------- RUN ----------------
if __name__ == "__main__":
    # Create an instance of the negotiation model
    model = NegotiationModel()

    # Run the model for a fixed number of steps
    # Each step activates all agents once and prints their reasoning output
    for _ in range(3):
        model.step()

Understanding the Output#

Below is an example of the reasoning output produced by ReActReasoning:

╭─ Message ─────────────────────────────────────────────────────────────────────────────╮
│                                                                                       │
│    └── message : My minimum acceptable price is 60.                                   │
│    └── sender : LLMAgent 1                                                            │
│    └── recipients : [<__main__.Buyer object at 0x13d015450>]                          │
│                                                                                       │
│ [Plan]                                                                                │
│    └── reasoning : As I am at step 1, I don't have any information about the          │
│ environment or my position. Since my short-term memory is empty and my long-term      │
│ memory doesn't provide any relevant context, I will focus on getting more             │
│ information about my surroundings. The current observation suggests that the          │
│ minimum price is 60, but this might not be directly related to my actions.            │
│ Therefore, I decide to move one step in a random direction to gather more             │
│ information about the environment and potentially find some clues or objects.         │
│    └── action : move_one_step                                                         │
╰───────────────────────────────────────────────────────────────────────────────────────╯

About Actions in the Output#

ReActReasoning produces both a reasoning trace and an action suggestion.

In this tutorial:

  • Actions are not executed

  • Actions appear only as part of the reasoning trace

  • Action execution is introduced in later tutorials

Exercises#

Try the following exercises to better understand agent communication and reasoning:

  1. Modify the seller’s message Change the content of the message sent by the seller and observe how buyer reasoning changes.

  2. Add another buyer Create a third buyer agent with a different budget and compare its reasoning with the existing buyers.

  3. Change buyer budgets Adjust buyer budgets and observe how this affects negotiation-related reasoning.

  4. Increase the number of steps Run the Model for more steps and observe how agent messaging influences reasoning over time.