Skip to content

Commit d50a445

Browse files
committed
Fix(viewer): Don't assume p1, p2, use real player names
1 parent f3586d2 commit d50a445

3 files changed

Lines changed: 149 additions & 7 deletions

File tree

.cursor/rules/viewer.mdc

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,116 @@ Here's how a `.traj.log` file looks:
7979
}
8080
```
8181

82+
Here's how the metadata.json file looks like:
83+
84+
```
85+
{
86+
"name": "PvpTournament",
87+
"tournament_id": "PvpTournament.BattleSnake.250915203207",
88+
"config": {
89+
"tournament": {
90+
"rounds": 10
91+
},
92+
"game": {
93+
"name": "BattleSnake",
94+
"sims_per_round": 1000,
95+
"args": {
96+
"width": 11,
97+
"height": 11,
98+
"browser": false
99+
}
100+
},
101+
"players": [
102+
{
103+
"agent": "mini",
104+
"name": "gpt-5-mini-more-prescriptive",
105+
"config": {
106+
"agent": {
107+
"system_template": "",
108+
"instance_template": "",
109+
"step_limit": 30,
110+
"cost_limit": 1.0
111+
},
112+
"model": {
113+
"model_name": "openai/gpt-5-mini"
114+
}
115+
}
116+
},
117+
{
118+
"agent": "mini",
119+
"name": "gpt-5-mini-default",
120+
"config": {
121+
"agent": {...},
122+
"model": {
123+
"model_name": "openai/gpt-5-mini"
124+
}
125+
}
126+
}
127+
],
128+
"prompts": {
129+
"game_description": "..."}
130+
},
131+
"created_timestamp": 1757982727,
132+
"game": {
133+
"name": "BattleSnake",
134+
"config": {
135+
"name": "BattleSnake",
136+
"sims_per_round": 1000,
137+
"args": {
138+
"width": 11,
139+
"height": 11,
140+
"browser": false
141+
}
142+
},
143+
"game_id": "PvpTournament.BattleSnake.250915203207",
144+
"created_timestamp": 1757982727
145+
},
146+
"agents": [
147+
{
148+
"name": "gpt-5-mini-more-prescriptive",
149+
"player_unique_id": "38f5a945-2a14-4cfb-9a41-e3ccceb7d3e2",
150+
"created_timestamp": 1757982729,
151+
"config": {
152+
"agent": "mini",
153+
"name": "gpt-5-mini-more-prescriptive",
154+
"config": {
155+
"agent": {
156+
"system_template": "...,
157+
"instance_template": "...",
158+
"step_limit": 30,
159+
"cost_limit": 1.0
160+
},
161+
"model": {
162+
"model_name": "openai/gpt-5-mini"
163+
}
164+
}
165+
},
166+
"initial_commit_hash": "dc7bdb934a2f1bba88d19136ff5caa6ff789dda7",
167+
"branch_name": "PvpTournament.BattleSnake.250915203207.gpt-5-mini-more-prescriptive",
168+
"round_tags": {}
169+
},
170+
{
171+
"name": "gpt-5-mini-default",
172+
"player_unique_id": "646cdd66-7cd5-4318-988f-3858ef3bd860",
173+
"created_timestamp": 1757982730,
174+
"config": {
175+
"agent": "mini",
176+
"name": "gpt-5-mini-default",
177+
"config": {
178+
"agent": {...},
179+
"model": {
180+
"model_name": "openai/gpt-5-mini"
181+
}
182+
}
183+
},
184+
"initial_commit_hash": "dc7bdb934a2f1bba88d19136ff5caa6ff789dda7",
185+
"branch_name": "PvpTournament.BattleSnake.250915203207.gpt-5-mini-default",
186+
"round_tags": {}
187+
}
188+
]
189+
}
190+
```
191+
82192
## Application Structure
83193

84194
The application has two main pages:
@@ -187,3 +297,7 @@ Then, show all the messages. Each message should be its own block together with
187297
Because the content can be long, make sure to have a foldout for the content.
188298

189299
**Message Role Badges**: Each message displays a small role badge (e.g., "User #1", "Assistant #2") in the top-right corner. These badges automatically hide on hover over the message block to improve readability and allow users to see text that might be obscured by the badge.
300+
301+
### Important
302+
303+
* Always use the player name from the metadata.json file, do not assume players are called p1, p2, etc.

codeclash/games/game.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def get_environment(self, branch_name: str | None = None) -> DockerEnvironment:
168168
"TQDM_DISABLE": "1",
169169
},
170170
container_timeout="3h",
171+
logger=self.logger,
171172
)
172173
# Logger setting will likely not take effect for initial container creation logs
173174
environment.logger = get_logger("environment", emoji="🪴")

codeclash/viewer/app.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,26 +88,53 @@ def get_models_from_metadata(log_dir: Path) -> list[str]:
8888
def get_agent_info_from_metadata(metadata: dict[str, Any]) -> list[AgentInfo]:
8989
"""Extract detailed agent information from metadata"""
9090
agents = []
91+
92+
# First, try to get agent info from the agents field (runtime metadata)
93+
if "agents" in metadata:
94+
for agent_data in metadata["agents"]:
95+
name = agent_data.get("name", "unknown")
96+
# Try to get model info from the agent's config
97+
config = agent_data.get("config", {})
98+
model_name = (
99+
config.get("model", {}).get("model_name")
100+
if isinstance(config.get("model"), dict)
101+
else config.get("model")
102+
)
103+
agent_class = config.get("agent_class")
104+
agents.append(AgentInfo(name=name, model_name=model_name, agent_class=agent_class))
105+
return agents
106+
107+
# Fallback to config-based extraction if no agents field
91108
players_config = metadata.get("config", {}).get("players", {})
92109

93110
# Handle both list and dict formats
94111
if isinstance(players_config, list):
95-
# If players is a list, iterate through each player
96-
for i, player_config in enumerate(players_config):
112+
# If players is a list, use the actual name from each player config
113+
for player_config in players_config:
97114
if isinstance(player_config, dict):
98-
name = f"p{i + 1}" # Default naming p1, p2, etc.
115+
name = player_config.get("name", "unknown") # Use actual agent name
99116
config = player_config.get("config", {})
100-
model_name = config.get("model", {}).get("model_name")
117+
model_name = (
118+
config.get("model", {}).get("model_name")
119+
if isinstance(config.get("model"), dict)
120+
else config.get("model")
121+
)
101122
agent_class = config.get("agent_class")
102123
agents.append(AgentInfo(name=name, model_name=model_name, agent_class=agent_class))
103124
elif isinstance(players_config, dict):
104-
# If players is a dict, iterate through player keys (p1, p2, etc.)
125+
# If players is a dict, iterate through player keys
105126
for player_key, player_config in sorted(players_config.items()):
106127
if isinstance(player_config, dict):
128+
# Use the actual name from config if available, otherwise use the key
129+
name = player_config.get("name", player_key)
107130
config = player_config.get("config", {})
108-
model_name = config.get("model", {}).get("model_name")
131+
model_name = (
132+
config.get("model", {}).get("model_name")
133+
if isinstance(config.get("model"), dict)
134+
else config.get("model")
135+
)
109136
agent_class = config.get("agent_class")
110-
agents.append(AgentInfo(name=player_key, model_name=model_name, agent_class=agent_class))
137+
agents.append(AgentInfo(name=name, model_name=model_name, agent_class=agent_class))
111138

112139
return agents
113140

0 commit comments

Comments
 (0)