diff --git a/docs/architecture.md b/docs/architecture.md index 1428bb66..a297f987 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -74,7 +74,16 @@ The Action consists of two parts #### List of ActionTypes - **JoinGame**, params={`agent_info`:AgentInfo(``, ``)}: Used to register agent in a game with a given ``. - **QuitGame**, params={}: Used for termination of agent's interaction. -- **ResetGame**, params={`request_trajectory`:`bool` (default=`False`), `randomize_topology`=`bool` (default=`True`)}: Used for requesting reset of the game to its initial position. If `request_trajectory = True`, the coordinator will send back the complete trajectory of the previous run in the next message. If `randomize_topology`=`True`, the agent request topology to be changed in the next episode. NOTE: the topology is changed only if (i) the `use_dynamic_addresses` is set to `True` in the task configuration AND all active agents ask for the change. +- **ResetGame**, params={`request_trajectory`:`bool` (default=`False`), `seed`:`int` (default=`None`), `randomize_topology`:`bool` (default=`False`)}: Used for requesting reset of the game to its initial position. If `request_trajectory = True`, the coordinator will send back the complete trajectory of the previous run in the next message. The `seed` parameter allows setting a specific random seed for reproducibility. If `randomize_topology=True`, the agent requests the topology to be randomized in the next episode. + + !!! note "Topology Change & Seed Dependency" + A topology change can only be requested if the agent also submits a specific `seed`. If `seed` is omitted or `None`, the `randomize_topology` flag is ignored. + + !!! warning "Consensus Requirement" + In multi-agent games, all agents must agree on the `seed` and `randomize_topology` values. If agents request conflicting seeds or topology change flags, the game will shut down. + + !!! note + The topology is changed only if (i) the `use_dynamic_addresses` is set to `True` in the task configuration AND (ii) all active agents ask for the change. --- - **ScanNetwork**, params{`source_host`:``, `target_network`:``}: Scans the given `` from a specified source host. Discovers ALL hosts in a network that are accessible from ``. If successful, returns set of discovered `` objects. - **FindServices**, params={`source_host`:``, `target_host`:``}: Used to discover ALL services running in the `target_host` if the host is accessible from `source_host`. If successful, returns a set of all discovered `` objects. diff --git a/netsecgame/game/coordinator.py b/netsecgame/game/coordinator.py index ca631046..ee0eb9bc 100644 --- a/netsecgame/game/coordinator.py +++ b/netsecgame/game/coordinator.py @@ -487,7 +487,7 @@ async def _process_reset_game_action(self, agent_addr: tuple, reset_action: Acti # - ONLY consider agents that submitted seed # register if the agent wants to randomize the topology if self._reset_seed_requests[agent_addr] is not None: - self._randomize_topology_requests[agent_addr] = reset_action.parameters.get("randomize_topology", True) + self._randomize_topology_requests[agent_addr] = reset_action.parameters.get("randomize_topology", False) if all(self._reset_requests.values()): # all agents want reset - reset the world self.logger.debug(f"All agents requested reset, setting the event") @@ -728,40 +728,24 @@ async def _reset_game(self) -> None: self.logger.debug("\tExiting reset_game task.") break if len(self.agents) > 0: - # verify that all agents agreed on the seed (or sent None) - valid_seeding = False - valid_topology_change = False - non_none_seeds = [seed for seed in self._reset_seed_requests.values() if seed is not None] - if len(non_none_seeds) == 0: # no agent wants to change the seed - seed = None - valid_seeding = True - elif len(set(non_none_seeds)) == 1: # all agents agree on the seed - seed = non_none_seeds[0] - valid_seeding = True - else: # agents disagree on the seed - seed = None - # verify that all agents agreed on the topology change (or sent None) - valid_seed_agents = [agent for agent in self.agents if self._reset_seed_requests[agent] is not None] - valid_topology_requests = [self._randomize_topology_requests[agent] for agent in valid_seed_agents] - if len(set(valid_topology_requests)) == 1: # all valid agents agree on the topology change - valid_topology_change = True - topology_change = valid_topology_requests[0] - else: # agents disagree on the topology change - valid_topology_change = False - topology_change = None - - if valid_seeding and valid_topology_change: - await self._handle_valid_reset(seed, topology_change) - self._reset_event.clear() - # notify all waiting agents - async with self._reset_done_condition: - self._reset_done_condition.notify_all() - elif not valid_seeding: + # 1. Validate Seeding (all non-None seeds must match) + unique_seeds = {seed for seed in self._reset_seed_requests.values() if seed is not None} + valid_seeding = len(unique_seeds) <= 1 + seed = unique_seeds.pop() if len(unique_seeds) == 1 else None + + # 2. Validate Topology Change (only for agents who provided a seed) + unique_topology_requests = {self._randomize_topology_requests[a] for a in self.agents if self._reset_seed_requests.get(a) is not None} + valid_topology_change = len(unique_topology_requests) <= 1 + topology_change = unique_topology_requests.pop() if len(unique_topology_requests) == 1 else False + + # 3. Handle Reset Actions + if not valid_seeding: await self._handle_invalid_reset("Agents disagree on the seed. Undefined state. Stopping the game") - self._reset_event.clear() elif not valid_topology_change: await self._handle_invalid_reset("Agents disagree on the topology change. Undefined state. Stopping the game") - self._reset_event.clear() + else: + await self._handle_valid_reset(seed, topology_change) + self._reset_event.clear() # notify all waiting agents async with self._reset_done_condition: