Skip to content

Commit 14de2af

Browse files
committed
Merge branch 'main' into john/battlecode
2 parents 32aa298 + fe1886d commit 14de2af

10 files changed

Lines changed: 305 additions & 59 deletions

File tree

.cursor/rules/viewer.mdc

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
---
2+
description: Writing the viewer/inspector/trajectory viewer
3+
globs:
4+
alwaysApply: false
5+
---
6+
7+
You are building a trajectory viewer for an AI agent benchmark that pits
8+
two AI agent in a round-based game against each other.
9+
10+
## Framework notes
11+
12+
* Write a python server using flask
13+
* As much output as possible should be formatted uses jinja templates
14+
* JS should be kept to a minimum
15+
* Your input is a game folder (described below)
16+
* Use clean modern CSS, but don't go too crazy, simplicity is key
17+
* This is a single page application
18+
19+
## Input folder
20+
21+
The input folders are all subfolders of `logs/`
22+
23+
The input folder has the following files
24+
25+
* results.json
26+
* main.log
27+
* round_$i.log, where $i is the round. Infer the number of rounds from how many of these files we have
28+
* .*_p$i_r$j.traj.log, where $i$ is the player (there's always 1 or 2 players and $j is the round number
29+
* Trajectory files do not exist for the last round
30+
31+
Here's how a `.traj.log` file looks:
32+
33+
34+
```
35+
{
36+
"info": {
37+
"exit_status": null,
38+
"submission": null,
39+
"memory": null,
40+
"model_stats": {
41+
"instance_cost": 0.0661002,
42+
"api_calls": 11
43+
}
44+
},
45+
"messages": [
46+
{
47+
"role": "system",
48+
"content": "You are a helpful assistant that can interact with a computer.\n\nYour response must contain exactly ONE bash code block with ONE command (or commands connected with && or ||).\nInclude a THOUGHT section before your command where you explain your reasoning process.\nFormat your response as shown in <format_example>.\n\n<format_example>\nYour reasoning and analysis here. Explain why you want to perform the action.\n\n```bash\nyour_command_here\n```\n</format_example>\n\nFailure to follow these rules will cause your response to be rejected.\nTo finish, issue the following command: `echo MINI_SWE_AGENT_FINAL_OUTPUT`\nwithout any other command."
49+
},
50+
{
51+
"role": "user",
52+
"content": [
53+
{
54+
"type": "text",
55+
"text": "Please solve this issue: in gitconfig, add a new alias ldc which copies the last diff ..."
56+
}
57+
]
58+
},
59+
{
60+
"role": "assistant",
61+
"content": "THOUGHT: I need to first look at the current directory to see the gitconfig file and understand its structure. Then I'll examine the existing aliases to understand the pattern and add the new 'ldc' alias that combines 'ld' (last diff) with clipboard functionality.\n\n```bash\nls -la\n```"
62+
},
63+
{
64+
"role": "user",
65+
"content": [
66+
{
67+
"type": "text",
68+
"text": "<returncode>0</returncode>\n<output>\ntotal 1232\ndrwxr-xr-x ..."
69+
}
70+
]
71+
},
72+
{
73+
"role": "assistant",
74+
"content": "THOUGHT: I can see there's a `gitconfig.sh` file in the current directory. Let me examine its contents to understand the current structure and find the existing aliases, particularly looking for the 'ld' alias and any clipboard-related aliases that I need to combine.\n\n```bash\ncat gitconfig.sh\n```"
75+
},
76+
"trajectory_format": "mini-swe-agent-1"
77+
}
78+
```
79+
80+
## Page design
81+
82+
### Header
83+
84+
* Here's some control elements to choose the input folder or visualization settings
85+
* There's a dark/light mode switch
86+
87+
### Main body
88+
89+
#### Overall results
90+
91+
* At the top, we show the content of `metadata.json`
92+
* There's a foldout that displays main.log (hidden by default)
93+
94+
#### Rounds
95+
96+
First, show a foldout of `round_1.log`.
97+
98+
Then, show a foldout with the trajectory view of player 1 round 1 (see below for trajectory view)
99+
Then, show a foldout with the content of trajectory player 1 round 1 ["submission"]
100+
Then, show a foldout with the content of trajectory player 1 round 1 ["memory"]
101+
Then, show a foldout with the trajectory view of player 2 round 1 (see below for trajectory view).
102+
Then, show a foldout with the content of trajectory player 2 round 1 ["submission"]
103+
Then, show a foldout with the content of trajectory player 2 round 1 ["memory"]
104+
105+
Then, show a foldout of `round_2.log`.
106+
107+
Then, show a foldout with the trajectory view of player 1 round 2 (see below for trajectory view)
108+
Then, show a foldout with the trajectory view of player 2 round 2 (see below for trajectory view).
109+
Then, show a foldout with the content of trajectory player 2 round 2 ["submission"]
110+
Then, show a foldout with the content of trajectory player 2 round 2 ["memory"]
111+
112+
etc.
113+
114+
Note that he last round only has the `round_n.log`, but there's no more trajectories for the last one.
115+
116+
#### Trajectory view
117+
118+
This should be its own jinja template.
119+
120+
First show the following properties from the `info` section
121+
122+
* Number of LM calls (api calls)
123+
* Cost (instance cost)
124+
125+
Then, show all the messages. Each message should be its own block together with the role.
126+
Because the content can be long, make sure to have a foldout for the content.

.github/workflows/pytest.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Pytest
2+
3+
env:
4+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5+
6+
on:
7+
push:
8+
branches:
9+
- main
10+
- add-gha
11+
paths-ignore:
12+
- 'docs/**'
13+
- 'README.md'
14+
- '.cursor/**'
15+
- '.github/workflows/build-docs.yaml'
16+
- '.github/workflows/release.yaml'
17+
- '.github/workflows/pylint.yaml'
18+
pull_request:
19+
branches:
20+
- main
21+
paths-ignore:
22+
- 'docs/**'
23+
- 'README.md'
24+
- '.cursor/**'
25+
- '.github/workflows/build-docs.yaml'
26+
- '.github/workflows/release.yaml'
27+
- '.github/workflows/pylint.yaml'
28+
29+
jobs:
30+
test:
31+
runs-on: ubuntu-latest
32+
defaults:
33+
run:
34+
shell: bash -l {0}
35+
steps:
36+
- name: Checkout code
37+
uses: actions/checkout@v5
38+
- uses: actions/setup-python@v5
39+
with:
40+
python-version: '3.11'
41+
- name: Install uv
42+
run: |
43+
curl -LsSf https://astral.sh/uv/install.sh | sh
44+
- name: Install dependencies
45+
run: |
46+
uv pip install --python $(which python) -e '.[dev]'
47+
- name: Run pytest
48+
run: pytest -v --cov --cov-branch --cov-report=xml -n auto

codeclash/agents/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@ def get_agent(config: dict, game: CodeGame) -> Player:
1111
}.get(config["agent"])
1212
if agents is None:
1313
raise ValueError(f"Unknown agent type: {config['agent']}")
14-
return agents(config, game)
14+
environment = game.get_environment()
15+
format_vars = {
16+
"game_id": game.game_id,
17+
"rounds": game.rounds,
18+
"round": game.round,
19+
}
20+
return agents(config, environment, format_vars)

codeclash/agents/abstract.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,35 @@
33

44
from dotenv import load_dotenv
55
from ghapi.all import GhApi
6+
from minisweagent import Environment
67

78
from codeclash.constants import GH_ORG
8-
from codeclash.games.abstract import CodeGame
99

1010
load_dotenv()
1111

1212

1313
class Player(ABC):
14-
def __init__(self, config: dict, game: CodeGame):
14+
def __init__(
15+
self,
16+
config: dict,
17+
environment: Environment,
18+
format_vars: dict,
19+
):
1520
self.config = config
16-
self.name = f"{game.game_id}_{config['name']}"
17-
self.container = game.get_environment()
18-
self.game = game
21+
self.name = f"{format_vars['game_id']}_{config['name']}"
22+
self.environment = environment
23+
self.round = format_vars["round"]
24+
self.format_vars = format_vars
1925

2026
def commit(self):
2127
"""Commit changes to the agent's codebase."""
28+
rounds = self.format_vars["rounds"]
2229
for cmd in [
2330
"git add -A",
24-
f"git commit --allow-empty -m 'Round {self.game.round}/{self.game.rounds} Update'",
31+
f"git commit --allow-empty -m 'Round {self.round}/{rounds} Update'",
2532
]:
26-
self.container.execute(cmd)
27-
print(
28-
f"Committed changes for {self.name} for round {self.game.round}/{self.game.rounds}"
29-
)
33+
self.environment.execute(cmd)
34+
print(f"Committed changes for {self.name} for round {self.round}/{rounds}")
3035

3136
def push(self):
3237
"""Push codebase to a new repository."""
@@ -41,7 +46,7 @@ def push(self):
4146
f"git remote add origin https://x-access-token:{token}@github.com/{GH_ORG}/{self.name}.git",
4247
"git push -u origin main",
4348
]:
44-
self.container.execute(cmd)
49+
self.environment.execute(cmd)
4550

4651
@abstractmethod
4752
def run(self):

codeclash/agents/dummy.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,5 @@
44
class Dummy(Player):
55
"""A dummy player that does nothing. Mainly for testing purposes."""
66

7-
def __init__(self, config: dict, game):
8-
super().__init__(config, game)
9-
107
def run(self):
118
self.commit()

codeclash/agents/minisweagent.py

Lines changed: 31 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,13 @@
88
import yaml
99
from jinja2 import Template
1010
from minisweagent import Environment, Model
11-
from minisweagent.agents.default import (
12-
AgentConfig,
13-
DefaultAgent,
14-
NonTerminatingException,
15-
TerminatingException,
16-
)
11+
from minisweagent.agents.default import AgentConfig, DefaultAgent
1712
from minisweagent.models.litellm_model import LitellmModel
1813
from minisweagent.run.utils.save import save_traj
1914
from rich.console import Console
2015

2116
from codeclash.agents.abstract import Player
2217
from codeclash.agents.utils import resolve_api_key
23-
from codeclash.games.abstract import CodeGame
2418

2519

2620
class ClashAgent(DefaultAgent):
@@ -34,74 +28,70 @@ def __init__(
3428
model: Model,
3529
env: Environment,
3630
name: str,
37-
game: CodeGame,
31+
format_vars: dict,
3832
*,
3933
config_class: Callable = AgentConfig,
4034
**kwargs,
4135
):
4236
super().__init__(model, env, config_class=config_class, **kwargs)
4337
self.name = name
44-
self.game = game
38+
self.format_vars = format_vars
4539
self.console = Console()
4640

41+
def add_message(self, role: str, content: str, **kwargs):
42+
super().add_message(role, content, **kwargs)
43+
if role == "assistant":
44+
self.console.print(
45+
f"[{self.name}] Step taken (step {self.model.n_calls}, cost {self.model.cost:.2f})"
46+
)
47+
4748
def render_template(self, template: str, **kwargs) -> str:
4849
cs = (
4950
asdict(self.config)
5051
| asdict(self.env.config)
5152
| asdict(self.model.config)
5253
| platform.uname()._asdict()
53-
| {
54-
"rounds": self.game.rounds,
55-
"round": self.game.round,
56-
}
54+
| self.format_vars
5755
)
5856
return Template(template).render(**kwargs, **cs, **os.environ)
5957

6058
def run(self) -> tuple[str, str]:
6159
"""Run step() until agent is finished. Return exit status & message"""
62-
self.messages = []
63-
self.add_message("system", self.render_template(self.config.system_template))
64-
self.add_message("user", self.render_template(self.config.instance_template))
65-
66-
# Start rich spinner
67-
with self.console.status(
68-
f"[bold green]{self.name} updating codebase..."
69-
) as status:
70-
while True:
71-
try:
72-
self.step()
73-
except NonTerminatingException as e:
74-
self.add_message("user", str(e))
75-
except TerminatingException as e:
76-
self.add_message("user", str(e))
77-
return type(e).__name__, str(e)
78-
79-
def has_finished(self, output: dict[str, str]):
80-
"""Raises Submitted exception with final output if the agent has finished its task."""
81-
save_traj(self, Path(f"{self.name}_r{self.game.round}.traj.json")) # type: ignore
82-
super().has_finished(output)
60+
with self.console.status(f"[bold green]{self.name} updating codebase..."):
61+
return super().run(task="")
8362

8463

8564
class MiniSWEAgent(Player):
8665
"""Player with agentic code editing capabilities"""
8766

88-
def __init__(self, config: dict, game: CodeGame):
89-
super().__init__(config, game)
67+
def __init__(self, config: dict, environment: Environment, format_vars: dict):
68+
super().__init__(config, environment=environment, format_vars=format_vars)
9069
self.agent = ClashAgent(
9170
LitellmModel(
9271
model_name=config["model"],
9372
model_kwargs={"api_key": resolve_api_key(config["model"])},
9473
),
95-
self.container,
74+
self.environment,
9675
self.name,
97-
game,
76+
format_vars,
9877
**yaml.safe_load(Path(config["config"]).read_text())["agent"],
9978
)
10079

10180
def run(self):
81+
exit_status = None
82+
result = None
10283
try:
10384
exit_status, result = self.agent.run()
10485
except Exception as e:
105-
result = str(e)
106-
print(traceback.format_exc())
107-
self.commit()
86+
exit_status = str(e)
87+
exc_message = traceback.format_exc()
88+
result = exc_message
89+
print(exc_message)
90+
finally:
91+
save_traj(
92+
self.agent, # type: ignore
93+
Path(f"{self.name}_r{self.format_vars['round']}.traj.json"),
94+
exit_status=exit_status,
95+
result=result,
96+
)
97+
self.commit()

codeclash/games/abstract.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _pre_round_setup(self, agents: list[Any]):
100100
# Copy agent codebases into game's container
101101
for agent in agents:
102102
copy_between_containers(
103-
src_container=agent.container,
103+
src_container=agent.environment,
104104
dest_container=self.environment,
105105
src_path=DIR_WORK,
106106
dest_path=f"/{agent.name}",
@@ -124,9 +124,9 @@ def _post_round_setup(self, agents: list[Any]):
124124
for agent in agents:
125125
copy_between_containers(
126126
self.environment,
127-
agent.container,
127+
agent.environment,
128128
self.round_log_path,
129-
f"{agent.container.config.cwd}/logs/round_{self.round}.log",
129+
f"{agent.environment.config.cwd}/logs/round_{self.round}.log",
130130
)
131131
print(f"Copied round log to {agent.name}'s container.")
132132
print(f"Round {self.round} completed.")

0 commit comments

Comments
 (0)