Core Concepts

This section describes the system structure and terminology of AgentSociety 2. The AgentSociety 2 section “Research Background and Platform Positioning” covers motivation from paradigms and collaboration; here the focus is implementation-level component relationships.

Architecture Overview

AgentSociety 2 is built around three main components:

  • Agents: Autonomous entities that use LLMs to interact with environments

  • Environment Modules: Composable components that define simulation rules

  • AgentSociety: Coordinator that manages agents and environments

Agents do not directly own runtime objects such as env, LLM, trace, or replay. Instead, they receive shared service handles through a ServiceProxy container; environment routing runs in a dedicated Ray actor. See Architecture and Scalability.

Interrupt recovery: agent, env-module, and society state needed for recovery is written atomically into the workspace. Agents use AGENT.json; env modules usually use state/ENV_STATE.json; society uses immutable SOCIETY.json plus the per-step SOCIETY_STEP.json. The CLI --resume option can continue an experiment from the interruption point; see Command Line Interface. This mechanism is independent from replay. Replay is append-only time-series data for analysis; see Storage and Result Access.

digraph agentsociety2 {
    rankdir=TB;
    node [shape=box, style=rounded];

    Agent [label="Agent"];
    CodeGenRouter [label="CodeGenRouter"];
    EnvModule [label="Env Module"];
    Tool [label="@tool()"];

    Agent -> CodeGenRouter [label="ask/intervene"];
    CodeGenRouter -> EnvModule [label="calls tools"];
    EnvModule -> Tool [label="decorated with"];
}

Full System Architecture

digraph full_architecture {
    rankdir=TB;
    node [shape=box, style=rounded];
    edge [fontsize=10];

    subgraph cluster_ui {
        label = "用户界面层";
        style=filled;
        color=lightgrey;
        CLI [label="CLI 命令行"];
        WebUI [label="Web 前端"];
        API [label="REST API"];
    }

    subgraph cluster_core {
        label = "核心模拟层";
        style=filled;
        color=lightblue;
        Society [label="AgentSociety\n协调器 (driver)"];
        Router [label="Router\n(EnvRouterActor)"];
        Storage [label="ReplayWriter (Actor)\n/ Workspace 存储"];
        Trace [label="Trace Actor\n(sharded writer)"];
    }

    subgraph cluster_agents {
        label = "智能体层 (无状态 record, Ray Task 流式)";
        style=filled;
        color=lightgreen;
        Proxy [label="ServiceProxy\n(env/llm/trace/replay)", shape=note];
        Agent1 [label="PersonAgent 1"];
        Agent2 [label="PersonAgent 2"];
        AgentN [label="... PersonAgent N"];
    }

    subgraph cluster_env {
        label = "环境层";
        style=filled;
        color=lightyellow;
        Env1 [label="SocialSpace"];
        Env2 [label="EconomySpace"];
        EnvN [label="... 自定义模块"];
    }

    subgraph cluster_external {
        label = "外部服务";
        style=filled;
        color=lavender;
        LLM [label="LLM Client\n(per-process dispatcher)"];
    }

    CLI -> Society;
    WebUI -> API;
    API -> Society;

    Society -> Router;
    Society -> Storage;
    Society -> Trace;
    Society -> Proxy;

    Proxy -> Agent1;
    Proxy -> Agent2;
    Proxy -> AgentN;

    Router -> Env1;
    Router -> Env2;
    Router -> EnvN;

    Agent1 -> Router [label="ask_env"];
    Agent2 -> Router;
    AgentN -> Router;

    Agent1 -> LLM [label="dispatcher"];
    Society -> LLM;
}

Agent-Environment Interface

Agents interact with environments through two main methods:

  • ask(): Query or observe environment state

  • intervene(): Modify environment state

This unified interface allows agents to communicate naturally with any environment module.

The @tool Decorator

Environment modules expose their functionality through the @tool decorator:

from agentsociety2.env import EnvBase, tool

class MyEnvironment(EnvBase):
    @tool(readonly=True, kind="observe")
    def get_weather(self, agent_id: int) -> str:
        """Get current weather for agent."""
        return f"Weather for agent {agent_id}"

    @tool(readonly=False)
    def set_temperature(self, temp: int) -> str:
        """Set temperature."""
        self._temperature = temp
        return f"Temperature set to {temp}"

Parameters:

  • readonly (bool): Whether the function modifies state * True = read-only, can be used in queries * False = modifies state, can be used in interventions

  • kind (str): The type of tool * "observe": Single-parameter observations (requires readonly=True) * "statistics": Aggregate queries (no parameters, requires readonly=True) * None or omitted: Regular tool (any signature, any readonly value)

CodeGenRouter

CodeGenRouter connects agents to environment modules by:

  1. Extracting tool signatures from environment modules

  2. Generating code to call appropriate tools based on agent input

  3. Safely executing code in a sandboxed environment

  4. Returning results to the agent

This approach allows agents to interact with any combination of environment modules without code changes.

Router Selection

RouterBase has multiple implementations that can be swapped as needed:

  • CodeGenRouter (default): generates call code and executes it in a sandbox, with AST guards and caching.

  • ReActRouter: ReAct-style tool selection.

  • PlanExecuteRouter: plan first, then execute.

  • TwoTierReActRouter / TwoTierPlanExecuteRouter: two-tier routing for large tool sets.

  • SearchToolRouter: selects tools through retrieval.

In production, routing runs in a dedicated Ray actor (EnvRouterProxy); see Architecture and Scalability.

Tool Categories

Observation Tools (readonly=True, kind="observe")

Agent-specific observations with a single agent_id parameter:

@tool(readonly=True, kind="observe")
def get_agent_location(self, agent_id: int) -> str:
    """Get current location of agent."""
    return f"Agent {agent_id} is at location X"

Statistics Tools (readonly=True, kind="statistics")

Aggregate queries with no parameters (except self):

@tool(readonly=True, kind="statistics")
def get_average_happiness(self) -> str:
    """Get average happiness of all agents."""
    avg = sum(self.happiness.values()) / len(self.happiness)
    return f"Average happiness: {avg}"

Regular Tools

General-purpose tools with any signature:

@tool(readonly=False)
def set_happiness(self, agent_id: int, value: float) -> str:
    """Set happiness level for agent."""
    self.happiness[agent_id] = value
    return f"Set agent {agent_id}'s happiness to {value}"

Tool Category Hierarchy

digraph tool_hierarchy {
    rankdir=TB;
    node [shape=box, style=rounded];

    Root [label="@tool 装饰器", shape=ellipse];

    Readonly [label="readonly=True", shape=diamond];
    Readwrite [label="readonly=False", shape=diamond];

    Observe [label="观察工具(kind=observe)\n单参数 agent_id"];
    Statistics [label="统计工具(kind=statistics)\n除 self 外无参数"];
    Regular [label="常规工具(readonly=False)\n任意签名"];

    Root -> Readonly;
    Root -> Readwrite;

    Readonly -> Observe;
    Readonly -> Statistics;
    Readwrite -> Regular;
}

Agent-Environment Interaction Flow

digraph interaction_flow {
    rankdir=TB;
    node [shape=box, style=rounded];

    Agent [label="智能体"];
    Ask [label="ask/intervene()"];
    Router [label="Router"];
    Tools [label="@tool 方法"];
    Env [label="环境状态"];
    Response [label="响应"];

    Agent -> Ask;
    Ask -> Router;
    Router -> Tools [label="提取工具签名"];
    Router -> Tools [label="生成调用代码"];
    Tools -> Env [label="执行工具"];
    Env -> Tools [label="返回结果"];
    Tools -> Router;
    Router -> Response;
    Response -> Agent;
}