11import os
2+ import uuid
23from abc import ABC , abstractmethod
34
45from dotenv import load_dotenv
@@ -18,9 +19,11 @@ def __init__(
1819 config : dict ,
1920 environment : Environment ,
2021 game_context : GameContext ,
21- ):
22+ ) -> None :
2223 self .config = config
2324 self .name = config ["name" ]
25+ self ._player_unique_id = uuid .uuid4 ()
26+ """Unique ID that doesn't clash even accross multiple games. Used for git tags."""
2427 self .environment = environment
2528 self .game_context = game_context
2629 self .game_context .render_and_set_prompts ()
@@ -29,28 +32,39 @@ def __init__(
2932 log_path = self .game_context .log_local / f"{ self .name } .log" ,
3033 emoji = "👤" ,
3134 )
35+ self ._metadata = {
36+ "name" : self .name ,
37+ "player_unique_id" : self ._player_unique_id ,
38+ "diff" : {}, # mapping round -> diff
39+ "incremental_diff" : {}, # mapping round -> diff
40+ }
3241
33- @property
34- def branch_name (self ):
35- """Get the branch name for the agent's codebase."""
36- return f"{ self .game_context .id } .{ self .name } "
37-
38- def commit (self ):
39- """Commit changes to the agent's codebase."""
40- r , rounds = self .game_context .round , self .game_context .rounds
41- for cmd in [
42- "git add -A" ,
43- f"git commit --allow-empty -m 'Round { r } /{ rounds } Update'" ,
44- ]:
45- assert_zero_exit_code (self .environment .execute (cmd ), logger = self .logger )
46- self .logger .info (f"Committed changes for { self .name } for round { r } /{ rounds } " )
42+ # --- Main methods ---
4743
48- def on_round_update (self , new_round : int ):
49- """Update the agent's round to match the game round."""
44+ def pre_run_hook (self , * , new_round : int ) -> None :
45+ """Should be called before we call the run method."""
46+ if new_round == 1 :
47+ self ._tag_round (0 )
5048 self .game_context .round = new_round
5149 self .game_context .render_and_set_prompts ()
5250
53- def push (self ):
51+ def post_run_hook (self , * , round : int ) -> None :
52+ """Should be called after we called the run method."""
53+ self ._commit ()
54+ self ._metadata ["diff" ][round ] = self ._get_round_diff (round )
55+ self ._metadata ["incremental_diff" ][round ] = self ._get_round_diff (
56+ round , incremental = True
57+ )
58+
59+ @abstractmethod
60+ def run (self ) -> None :
61+ """Given the observation / recap, update the codebase"""
62+
63+ def get_metadata (self ) -> dict :
64+ """Get metadata for the agent."""
65+ return self ._metadata
66+
67+ def push (self ) -> None :
5468 """Push codebase to a branch on the game's remote repository."""
5569 token = os .getenv ("GITHUB_TOKEN" )
5670 if not token :
@@ -59,13 +73,61 @@ def push(self):
5973 for cmd in [
6074 "git remote remove origin" ,
6175 f"git remote add origin https://x-access-token:{ token } @github.com/{ GH_ORG } /{ self .game_context .name } .git" ,
62- f"git push origin { self .branch_name } " ,
76+ f"git push origin { self ._branch_name } " ,
77+ "git push origin --tags" ,
6378 ]:
6479 assert_zero_exit_code (self .environment .execute (cmd ), logger = self .logger )
6580 self .logger .info (
66- f"Pushed { self .name } commit history to remote repository (branch { self .branch_name } )"
81+ f"Pushed { self .name } commit history to remote repository (branch { self ._branch_name } )"
6782 )
6883
69- @abstractmethod
70- def run (self ):
71- """Given the observation / recap, update the codebase"""
84+ # --- Helper methods ---
85+
86+ def _tag_round (self , round : int ) -> None :
87+ """Git tag the codebase at the given round."""
88+ assert_zero_exit_code (
89+ self .environment .execute (
90+ f"git tag -a { self ._get_round_tag_name (round )} -m 'Round { round } Update'"
91+ ),
92+ logger = self .logger ,
93+ )
94+
95+ @property
96+ def _branch_name (self ) -> str :
97+ """Get the branch name for the agent's codebase."""
98+ return f"{ self .game_context .id } .{ self .name } "
99+
100+ def _get_round_tag_name (self , round : int ) -> str :
101+ """Get git tag name for the version of the codebase at the given round."""
102+ return f"{ self ._player_unique_id } -round-{ round } "
103+
104+ def _commit (self ) -> None :
105+ """Commit changes to the agent's codebase."""
106+ r = self .game_context .round
107+ for cmd in [
108+ "git add -A" ,
109+ f"git commit --allow-empty -m 'Round { r } Update'" ,
110+ ]:
111+ assert_zero_exit_code (self .environment .execute (cmd ), logger = self .logger )
112+ self ._tag_round (r )
113+ self .logger .info (f"Committed changes for { self .name } for round { r } " )
114+
115+ def _get_round_diff (self , round : int , * , incremental : bool = False ) -> str :
116+ """Get the diff between the round and initial version (round 0).
117+ If incremental is True, get the diff between the round and the previous round.
118+ Returns empty string if round is 0.
119+ """
120+ if round == 0 :
121+ return ""
122+ if incremental :
123+ previous_round_tag = self ._get_round_tag_name (round - 1 )
124+ else :
125+ previous_round_tag = self ._get_round_tag_name (0 )
126+ current_round_tag = self ._get_round_tag_name (round )
127+ out = assert_zero_exit_code (
128+ self .environment .execute (
129+ f"git diff { previous_round_tag } ..{ current_round_tag } "
130+ ),
131+ logger = self .logger ,
132+ )
133+ return out ["output" ]
0 commit comments