Problem Summary
We have identified three interrelated issues in the core simulation and data management flow that violate the defined skills and economy data contracts:
- Missing Weekly Position Skill Settlement: The weekly settlement process (
_before_week_start) successfully processes weekly income, but entirely misses applying the weekly_delta_skills configured for the agent's current position.
- Split Skill Keys (No Normalization): Skill gains from activity outcomes are appended directly to the state using raw keys returned by the model/environment. If an agent has a Chinese initial skill (e.g.,
观察与共情) but an activity returns an English counterpart (e.g., observation_and_empathy), they are persisted as two separate keys in the agent's state instead of being mapped and aggregated.
- Non-Idempotent Re-execution on Last-Week Resume: When the simulation resumes from a checkpoint at the final week of a year (e.g.,
Y2020-W10), the resume logic resolves the starting point to that same week, triggering a re-run. Because the weekly settlement stage (_before_week_start) is not idempotent, the agents receive duplicate weekly income and decay calculations.
Implications on Research & Essay Findings
These issues directly impact some of the key analytical findings presented in the essay:
- In Striving vs. Leisurely Agent Profiles (
essay/Sections/appendix.tex:1420-1468), a striving score is defined based on extra_earning_count and skill_advance_count to support the conclusion that "Striving agents accumulate more wealth but do not lead to greater well-being."
- Because:
- Position-based skill growth is never added to the agent's state weekly, and
- Activity-based skill gains suffer from split keys (preventing proper accumulation under a unified skill canonical name),
- The underlying skill metrics (
total_skills, skill_improvement_count) used for these calculations may be distorted. The striving-to-leisurely grouping and its corresponding correlations might require a recalculation once these data contract issues are resolved.
As for what the simulation results will look like after fixing these bugs—we believe it will be highly intriguing! Unfortunately, our server's compute budget is quite limited, so we'll leave that exciting discovery to the maintainers QwQ.
Minimal Isolated Reproduction
The following Python script reproduces all three issues locally using memory-only mocks (no external API calls or LLM dependency). Run it from the repository root:
from types import SimpleNamespace
from src.config import load_config
# Load default config template
load_config("config.example.json")
from src.agents.data_manager import DataManager
from src.world.world import World
class Logger:
def info(self, *args, **kwargs): pass
def warning(self, *args, **kwargs): pass
# --- Case 1: Missing Weekly Position Skill Settlement ---
dm = SimpleNamespace()
dm.state = {
"vitality": 70,
"fulfillment": {"mood": 50, "material": 50, "social": 50, "esteem": 50},
"skills": {"观察与共情": 10},
"assets": {"deposit": 100, "possessions": []}
}
dm.read_profile = lambda: {
"position": {
"weekly_income": 20,
"weekly_delta_skills": {"观察与共情": 3}
},
"extra_income": 0
}
dm.get_fulfillment = lambda: dm.state["fulfillment"]
dm.apply_fulfillment_decay = lambda decays: None
dm.get_deposit = lambda: dm.state["assets"]["deposit"]
dm.update_deposit = lambda val: dm.state["assets"].update(deposit=val)
world = World.__new__(World)
world.config = {"fulfillment_decay_min_ratio": {k: 0 for k in dm.state["fulfillment"]}}
world.agents = [SimpleNamespace(name="TestAgent", dm=dm)]
world.logger = Logger()
# Run the weekly pre-start settlement
world._before_week_start()
print("--- Case 1 ---")
print(f"Expected Deposit: 120 | Actual: {dm.state['assets']['deposit']}")
print(f"Expected Skills: {{'观察与共情': 13}} | Actual: {dm.state['skills']}")
# --- Case 2: Split Skill Keys (No Normalization) ---
state = {
"vitality": 70,
"fulfillment": {},
"skills": {"观察与共情": 10},
"assets": {"deposit": 0, "possessions": []}
}
manager = DataManager.__new__(DataManager)
manager.read_state = lambda **kwargs: state
manager.save_state = lambda value: None
outcome = SimpleNamespace(
delta_vitality=0,
delta_fulfillment={},
delta_skills={"observation_and_empathy": 2},
delta_money=0,
gain_items=[]
)
# Apply activity outcome
manager.apply_activity_outcome(outcome)
print("\n--- Case 2 ---")
print("Persisted Skills in State:")
print(state["skills"])
print(f"Is same skill split into two keys? {'Yes' if len(state['skills']) == 2 else 'No'}")
# --- Case 3: Non-Idempotent Last-Week Resume ---
resume_world = World.__new__(World)
resume_world.config = {"time": {"start_year": 2020, "n_week": 10}}
resume_world._read_checkpoint = lambda: {"year": 2020, "week": 10}
print("\n--- Case 3 ---")
print(f"Checkpoint at Y2020-W10 resolves to starting week: {resume_world._resolve_resume_point(None)}")
Output of the Repro:
--- Case 1 ---
Expected Deposit: 120 | Actual: 120
Expected Skills: {'观察与共情': 13} | Actual: {'观察与共情': 10} # Skill growth is missing!
--- Case 2 ---
Persisted Skills in State:
{'观察与共情': 10, 'observation_and_empathy': 2} # Key split!
Is same skill split into two keys? Yes
--- Case 3 ---
Checkpoint at Y2020-W10 resolves to starting week: (2020, 10) # Week 10 will re-run settlements!
Technical Root Cause Analysis
1. Position Skills Omitted in Weekly Settlement
- Location:
src/world/world.py:448-451
- Analysis:
_before_week_start() only triggers _apply_fulfillment_decay() and _settle_weekly_income(). There is no execution path that reads weekly_delta_skills from the agent's current-year profile position data and adds them to skills in state.jsonl.
2. No Skill Name Normalization / Mapping
- Location:
src/agents/data_manager.py:2548-2550
- Analysis: In
apply_activity_outcome(), skill increments are merged directly using outcome.delta_skills.items() without passing them through a canonical dictionary translation or alias-merging function.
3. Checkpoint Resume Resolves to Non-Idempotent Week
- Location:
src/world/world.py:259-282
- Analysis: When
week == n_week, the resume logic returns (year, week) (e.g., (2020, 10)), which forces a complete re-execution of the final week of that year. During step() (src/world/world.py:662-664), _before_week_start() is called unconditionally, causing another round of deposits and decay to accumulate.
Suggested Fixes
- Implement Position Skill Settlement: Extend
_before_week_start (or introduce a sister method in world.py) to fetch weekly_delta_skills from each agent's active profile position, apply them to the current state, and persist via save_state().
- Canonical Skill Mapping: Implement a mapping dictionary (supporting English/Chinese aliases) inside
DataManager. Ensure any skill key written to the state (during profile initialization, weekly position settlement, and activity outcomes) goes through a normalization step to merge synonym keys.
- Idempotence or Checkpoint Refinement: Ensure weekly settlements check if they have already run for the current
(year, week) before modifying states. Alternatively, adjust _resolve_resume_point so that resuming from week == n_week resumes directly to the year-end transition stage without re-running the last week's non-idempotent pre-week settlement.
Problem Summary
We have identified three interrelated issues in the core simulation and data management flow that violate the defined
skillsandeconomydata contracts:_before_week_start) successfully processes weekly income, but entirely misses applying theweekly_delta_skillsconfigured for the agent's current position.观察与共情) but an activity returns an English counterpart (e.g.,observation_and_empathy), they are persisted as two separate keys in the agent's state instead of being mapped and aggregated.Y2020-W10), the resume logic resolves the starting point to that same week, triggering a re-run. Because the weekly settlement stage (_before_week_start) is not idempotent, the agents receive duplicate weekly income and decay calculations.Implications on Research & Essay Findings
These issues directly impact some of the key analytical findings presented in the essay:
essay/Sections/appendix.tex:1420-1468), a striving score is defined based onextra_earning_countandskill_advance_countto support the conclusion that "Striving agents accumulate more wealth but do not lead to greater well-being."total_skills,skill_improvement_count) used for these calculations may be distorted. The striving-to-leisurely grouping and its corresponding correlations might require a recalculation once these data contract issues are resolved.As for what the simulation results will look like after fixing these bugs—we believe it will be highly intriguing! Unfortunately, our server's compute budget is quite limited, so we'll leave that exciting discovery to the maintainers QwQ.
Minimal Isolated Reproduction
The following Python script reproduces all three issues locally using memory-only mocks (no external API calls or LLM dependency). Run it from the repository root:
Output of the Repro:
Technical Root Cause Analysis
1. Position Skills Omitted in Weekly Settlement
src/world/world.py:448-451_before_week_start()only triggers_apply_fulfillment_decay()and_settle_weekly_income(). There is no execution path that readsweekly_delta_skillsfrom the agent's current-year profile position data and adds them toskillsinstate.jsonl.2. No Skill Name Normalization / Mapping
src/agents/data_manager.py:2548-2550apply_activity_outcome(), skill increments are merged directly usingoutcome.delta_skills.items()without passing them through a canonical dictionary translation or alias-merging function.3. Checkpoint Resume Resolves to Non-Idempotent Week
src/world/world.py:259-282week == n_week, the resume logic returns(year, week)(e.g.,(2020, 10)), which forces a complete re-execution of the final week of that year. Duringstep()(src/world/world.py:662-664),_before_week_start()is called unconditionally, causing another round of deposits and decay to accumulate.Suggested Fixes
_before_week_start(or introduce a sister method inworld.py) to fetchweekly_delta_skillsfrom each agent's active profile position, apply them to the current state, and persist viasave_state().DataManager. Ensure any skill key written to the state (during profile initialization, weekly position settlement, and activity outcomes) goes through a normalization step to merge synonym keys.(year, week)before modifying states. Alternatively, adjust_resolve_resume_pointso that resuming fromweek == n_weekresumes directly to the year-end transition stage without re-running the last week's non-idempotent pre-week settlement.