| name | dr-mas |
| title | Dr. MAS: Stable RL for Multi-Agent LLM Systems |
| version | 0.0.2 |
| engine | skillxiv-v0.0.2-claude-opus-4.6 |
| license | MIT |
| url | https://arxiv.org/abs/2602.08847 |
| keywords | ["Multi-Agent RL","GRPO","Advantage Normalization","Agent Orchestration","Gradient Stability"] |
| description | Enable stable multi-agent reinforcement learning by normalizing advantages per-agent rather than globally, preventing gradient-norm inflation in heterogeneous multi-agent systems. |
Dr. MAS: Stable RL for Multi-Agent LLM Systems
Problem Context
When applying GRPO (Group Relative Policy Optimization) to multi-agent systems, agents with different reward distributions experience gradient-norm inflation. A global reward baseline poorly aligned with some agents' reward statistics causes their gradients to explode, destabilizing training. This is especially severe in heterogeneous setups where agents play specialized roles.
Core Concept
Agent-Wise Advantage Normalization (Dr. MAS) replaces global advantage normalization with per-agent normalization. Each agent normalizes advantages using its own reward statistics (μₖ, σₖ) rather than a global baseline, preventing gradient scaling mismatches while enabling stable co-training of heterogeneous agents.
Architecture Overview
- Multi-Agent Orchestration: Flexible framework supporting different agent architectures and roles
- Per-Agent Reward Tracking: Partition experiences by agent; compute agent-specific reward statistics
- Advantage Normalization: Normalize each agent's advantages using its own mean/variance
- Shared Resource Pooling: Optional model weight sharing and efficient GPU scheduling
- Gradient Stability: Prevent norm inflation through per-agent normalization
Implementation
Phase 1: Multi-Agent Experience Collection
class MultiAgentOrchestrator:
"""Coordinate multiple LLM agents with per-agent reward tracking"""
def __init__(self, agents, shared_model=None):
self.agents = agents
self.shared_model = shared_model
self.experience_buffer = defaultdict(list)
def collect_trajectory(self, task, max_steps=10):
"""
Collect multi-agent trajectory for a task.
Different agents may activate at different steps.
"""
state = task.initial_state()
trajectory = {
'task': task,
'steps': [],
'agent_assignments': []
}
for step in range(max_steps):
agent_id = select_agent_for_step(self.agents, state, step)
agent = self.agents[agent_id]
action = agent.act(state)
next_state, reward = task.step(action)
trajectory['steps'].append({
'agent_id': agent_id,
'state': state,
'action': action,
'reward': reward,
'next_state': next_state
})
trajectory[].append(agent_id)
state = next_state
task.is_terminal(state):
trajectory
():
agent_experiences = defaultdict()
step trajectory[]:
agent_id = step[]
agent_experiences[agent_id].append({
: step[],
: step[],
: step[]
})
agent_experiences
Phase 2: Per-Agent Reward Statistics
def compute_per_agent_statistics(experiences, agents):
"""
Compute reward mean and variance for each agent separately.
experiences: dict[agent_id] -> list of reward values
"""
agent_stats = {}
for agent_id, agent_exps in experiences.items():
rewards = [exp['reward'] for exp in agent_exps]
mean = np.mean(rewards) if rewards else 0.0
std = np.std(rewards) if rewards else 1.0
std = max(std, 1e-8)
agent_stats[agent_id] = {
'mean': mean,
'std': std,
'count': len(rewards)
}
return agent_stats
Phase 3: Per-Agent Advantage Normalization
def compute_advantages_per_agent(trajectory, value_fn, agents,
agent_stats):
"""
Compute advantages normalized per-agent using per-agent statistics.
"""
advantages = []
agent_assignments = trajectory['agent_assignments']
for step_idx, step in enumerate(trajectory['steps']):
agent_id = step['agent_id']
state = step['state']
reward = step['reward']
next_state = step['next_state']
value_current = value_fn(state)
value_next = value_fn(next_state)
td_residual = reward + 0.99 * value_next - value_current
stats = agent_stats[agent_id]
advantage = (td_residual - stats['mean']) / stats['std']
advantages.append(advantage)
return advantages
def per_agent_advantage_normalization(trajectory, value_fn, agents):
"""
Full pipeline for per-agent normalization.
"""
agent_experiences = defaultdict(list)
for step_idx, step in enumerate(trajectory['steps']):
agent_id = step['agent_id']
agent_experiences[agent_id].append({
'step_idx': step_idx,
'state': step['state'],
'reward': step['reward'],
'next_state': step['next_state']
})
agent_stats = {}
agent_id, exps agent_experiences.items():
rewards = [exp[] exp exps]
mean = np.mean(rewards)
std = np.std(rewards) +
agent_stats[agent_id] = {: mean, : std}
advantages = []
step_idx, step (trajectory[]):
agent_id = step[]
reward = step[]
value_curr = value_fn(step[])
value_next = value_fn(step[])
td = reward + * value_next - value_curr
stats = agent_stats[agent_id]
advantage = (td - stats[]) / stats[]
advantages.append(advantage)
advantages
Phase 4: GRPO Update with Per-Agent Advantages
def grpo_update_per_agent(model, trajectories, value_fn, agents):
"""
Group Relative Policy Optimization with per-agent advantage normalization.
"""
for group in trajectories:
advantages = per_agent_advantage_normalization(
group, value_fn, agents
)
logprobs = []
for step_idx, step in enumerate(group['steps']):
agent_id = step['agent_id']
state = step['state']
action = step['action']
agent = agents[agent_id]
logprob = agent.log_probability(state, action)
logprobs.append(logprob)
logprobs = torch.stack(logprobs)
advantages = torch.tensor(advantages)
advantage_mean = advantages.mean()
advantage_std = advantages.std() + 1e-8
normalized_advantages = (advantages - advantage_mean) / advantage_std
loss = -torch.mean(normalized_advantages * logprobs)
loss.backward()
optimizer.step()
returns = compute_returns(group)
value_loss = mse_loss(value_fn(group['states']), returns)
value_loss.backward()
value_optimizer.step()
Phase 5: Optional Model Sharing
class SharedLLMMultiAgent:
"""
Multi-agent system with shared LLM backbone.
Each agent has task-specific prompt/prefix.
"""
def __init__(self, base_model, agent_configs):
self.base_model = base_model
self.agent_configs = agent_configs
def act(self, agent_id, state):
"""Agent-specific action generation via prompting"""
agent_config = self.agent_configs[agent_id]
prompt = agent_config['system_prompt']
full_prompt = f"{prompt}\n\nState: {state}"
action = self.base_model.generate(full_prompt)
return action
Practical Guidance
When to use: Deploy for heterogeneous multi-agent systems (e.g., planner agent, executor agent, verifier agent) where agents have different roles and reward distributions.
Agent design: Define clear responsibilities per agent to ensure meaningful per-agent reward statistics. Agents with similar reward distributions can be grouped together.
Shared models: Model sharing reduces memory footprint but may introduce competition during training. Start without sharing; add if memory is tight.
Agent configuration: Each agent needs learning rate, entropy coefficient, and other hyperparameters. Can reuse across agents or tune per-agent.
Reward signal design: Ensure reward signals are agent-specific (e.g., planner rewarded for quality plan, executor for execution fidelity). Misaligned rewards prevent benefits of per-agent normalization.
Reference
Dr. MAS achieves +5.6% average improvement over vanilla GRPO while dramatically reducing gradient spikes, enabling stable co-training of heterogeneous agents. The key insight is that per-agent normalization prevents gradient scaling mismatches arising from different reward distributions, crucial for multi-agent systems where agents have specialized roles and different learning curves.