How the Multi-Agent Architecture in MathModelAgent Works: Coordinator, Modeler, Coder, and Writer

MathModelAgent orchestrates a four-stage pipeline where CoordinatorAgent, ModelerAgent, CoderAgent, and WriterAgent communicate through typed Pydantic DTOs to transform raw mathematical competition problems into executable code and polished academic papers.

The multi-agent architecture in MathModelAgent (from the jihe520/mathmodelagent repository) implements a structured workflow that mimics the human approach to mathematical modeling competitions. Each specialized LLM-driven agent handles a distinct phase of the problem-solving process, passing structured data through well-defined interfaces to ensure type-safe handoffs and deterministic execution.

The Four-Agent Pipeline Architecture

The system processes every problem through a sequential pipeline where each agent inherits from a common Agent base class defined in backend/app/core/agents/agent.py. This base class provides shared functionality including chat history management, memory compression, and a unified run contract.

CoordinatorAgent: Problem Decomposition

The CoordinatorAgent (backend/app/core/agents/coordinator_agent.py) serves as the entry point. It parses the raw user problem description, extracts individual sub-questions, and returns a structured CoordinatorToModeler DTO containing the questions dictionary and ques_count. This agent injects the COORDINATOR_PROMPT system prompt to guide the LLM in proper problem decomposition.

ModelerAgent: Strategy Design

The ModelerAgent (backend/app/core/agents/modeler_agent.py) receives the parsed questions via the DTO and designs a mathematical modeling strategy. Using the MODELER_PROMPT, it generates a modeling plan that maps to the ModelerToCoder DTO, which contains the questions_solution field carrying the solution sketch for the next stage.

CoderAgent: Code Generation and Execution

The CoderAgent (backend/app/core/agents/coder_agent.py) takes the modeling sketch and generates executable Python code within a sandboxed Jupyter-like interpreter. This agent implements a tool-calling loop using execute_code to iterate until the code runs without errors. It produces a CoderToWriter DTO containing the coder_response (final code) and created_images (any generated visualizations).

WriterAgent: Academic Paper Composition

The WriterAgent (backend/app/core/agents/writer_agent.py) consumes the code and images alongside a writing prompt generated by get_writer_prompt. It queries scholarly sources via the search_papers tool when needed and returns a WriterResponse DTO containing response_content (written text) and footnotes for academic citations.

Inter-Agent Communication and DTOs

Agents exchange data through typed Pydantic models defined in backend/app/schemas/A2A.py. These Data-Transfer Objects (DTOs) enforce type safety and make the workflow deterministic:

  • CoordinatorToModeler: Carries questions (dict of sub-questions) and ques_count
  • ModelerToCoder: Carries questions_solution (the modeling plan)
  • CoderToWriter: Carries coder_response (generated code) and created_images
  • WriterResponse: Carries response_content (written sections) and footnotes

This DTO-based architecture ensures that each agent receives exactly the data structure it expects, eliminating schema mismatches between pipeline stages.

Workflow Orchestration with MathModelWorkFlow

The central orchestrator lives in backend/app/core/workflow.py within the MathModelWorkFlow class. Its execute coroutine sequences the agents and manages the sandboxed interpreter lifecycle:


# 1. Coordinator stage

coordinator_response = await coordinator_agent.run(problem.ques_all)

# 2. Modeler stage

modeler_response = await modeler_agent.run(coordinator_response)

# 3. Sandbox preparation

code_interpreter = await create_interpreter(...)

# 4. Coder stage (iterates over sub-questions)

coder_response = await coder_agent.run(
    prompt=value["coder_prompt"], 
    subtask_title=key
)

# 5. Writer stage

writer_response = await writer_agent.run(
    writer_prompt, 
    available_images=coder_response.created_images, 
    sub_title=key
)

The workflow publishes real-time status updates to a Redis channel via redis_manager.publish_message (implemented in backend/app/services/redis_manager.py), allowing the frontend to display progress as each agent completes its task.

Agent Communication Patterns

System Prompt Injection

Each agent injects its specific system prompt before the first user message. The prompts are defined in backend/app/core/prompts.py:

  • COORDINATOR_PROMPT for problem decomposition
  • MODELER_PROMPT for strategy design
  • CODER_PROMPT for code generation
  • get_writer_prompt() for academic writing

Chat History and Memory Management

The base Agent class maintains conversation state in self.chat_history. After every LLM response, the history appends the new messages. A memory-compression mechanism (clear_memory) summarizes older turns to stay within the max_memory token limit, preventing context window overflow during long modeling sessions.

Tool Calling Loops

CoderAgent and WriterAgent declare tool schemas (coder_tools, writer_tools) that enable the LLM to request external actions:

  1. The agent publishes a status message to Redis indicating tool execution
  2. The tool executes (e.g., execute_code in the sandbox or search_papers for literature)
  3. The result appends as a role: "tool" message to the chat history
  4. The agent continues the chat loop until no tool calls remain

Error Handling and Reflection

When tool execution fails, CoderAgent triggers a reflection mechanism using get_reflection_prompt. It retries the operation up to max_retries, appending error context to guide the LLM toward a correct solution. The WriterAgent implements similar retry logic for failed scholarly searches.

Practical Implementation Examples

Running the Complete Pipeline

import asyncio
from backend.app.schemas.request import Problem
from backend.app.core.workflow import MathModelWorkFlow

async def run_example():
    problem = Problem(
        task_id="demo-001",
        ques_all="请用微分方程描述某城市的交通流量,并提供数值仿真代码。",
        comp_template="default",
        format_output="Markdown",
    )
    workflow = MathModelWorkFlow()
    await workflow.execute(problem)

asyncio.run(run_example())

Direct Agent Invocation

from backend.app.core.agents.coordinator_agent import CoordinatorAgent
from backend.app.core.llm.llm_factory import LLMFactory

# Build LLM for coordinator

llm_factory = LLMFactory(task_id="demo-001")
coordinator_llm = llm_factory.get_llm("coordinator")

# Instantiate and run

coordinator = CoordinatorAgent(
    task_id="demo-001", 
    model=coordinator_llm
)
dto = await coordinator.run("请对以下问题进行分解……")
print(dto.questions)   # Structured sub-questions

print(dto.ques_count)  # Total count

Coder Agent with Tool Execution

from backend.app.core.agents.coder_agent import CoderAgent
from backend.app.core.llm.llm_factory import LLMFactory
from backend.app.tools.interpreter_factory import create_interpreter

llm = LLMFactory(task_id="demo-001").get_llm("coder")
interpreter = await create_interpreter(
    kind="local", 
    task_id="demo-001", 
    work_dir="/tmp/work"
)

coder = CoderAgent(
    task_id="demo-001",
    model=llm,
    work_dir="/tmp/work",
)

response = await coder.run(
    prompt="请在 Jupyter 环境实现上述微分方程的数值求解代码。",
    subtask_title="traffic_flow_simulation",
)

print(response.coder_response)  # Generated Python code

print(response.created_images)  # Visualization paths

Summary

Frequently Asked Questions

How does the CoordinatorAgent decide how to split a problem into sub-questions?

The CoordinatorAgent uses the COORDINATOR_PROMPT system prompt (defined in backend/app/core/prompts.py) to guide the LLM in parsing the raw problem text. It analyzes the semantic structure of the input to identify distinct mathematical questions, then returns a CoordinatorToModeler DTO containing a dictionary of sub-questions indexed by task keys and a total count. This structured decomposition ensures downstream agents receive discrete, manageable units of work.

What happens if the CoderAgent generates code that fails to execute?

When the execute_code tool returns an error, the CoderAgent invokes its reflection mechanism using get_reflection_prompt. It appends the error message to the chat history as a tool response and retries the generation, incorporating the failure context to guide the LLM toward a corrected solution. This loop continues until the code executes successfully or reaches the configured max_retries limit.

Can additional agents be inserted into the pipeline without modifying existing code?

Yes. The architecture supports extensibility through inheritance from the base Agent class and the DTO pattern. To add a new stage, create a new agent class, define a corresponding DTO in backend/app/schemas/A2A.py for handoff data, and insert the agent call into the MathModelWorkFlow.execute method in backend/app/core/workflow.py. The existing agents remain unchanged due to the loose coupling provided by the DTO-based communication protocol.

How does the WriterAgent handle academic citations and references?

The WriterAgent (backend/app/core/agents/writer_agent.py) declares search_papers in its writer_tools schema. When the LLM determines that scholarly support is needed, it invokes this tool to query academic databases. The results append to the chat history, and the agent incorporates the findings into the paper text, tracking sources in the footnotes field of the WriterResponse DTO returned to the workflow.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →