By Sagar Shankaran, Founder of CallSphere
Explore multi-agent reinforcement learning (MARL) concepts including reward shaping, cooperative versus competitive strategies, and policy gradient methods for agent teams with practical Python implementations.
Key takeaways
Single-agent reinforcement learning (RL) has achieved remarkable results — from beating Go champions to controlling robotic arms. But real-world AI systems rarely operate in isolation. When multiple agents share an environment, standard RL breaks down because each agent's optimal strategy depends on what the other agents are doing. The environment becomes non-stationary from each agent's perspective.
Multi-Agent Reinforcement Learning (MARL) addresses this by designing training algorithms where agents learn simultaneously, adapting to each other's evolving strategies. This is the foundation for building agent teams that genuinely improve together rather than merely running in parallel.
In MARL, the environment is modeled as a Markov Game (also called a Stochastic Game), extending the single-agent Markov Decision Process:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
RAW[("Raw dataset")]
CLEAN["Clean and impute<br/>handle nulls and outliers"]
FE["Feature engineering<br/>encoding plus scaling"]
SPLIT{"Train, val,<br/>test split"}
TRAIN["Train model<br/>e.g. tree, NN, SVM"]
TUNE["Hyperparameter tuning<br/>CV plus search"]
EVAL["Evaluate<br/>metrics by task"]
GATE{"Hits target<br/>threshold?"}
DEPLOY[("Serve via API<br/>and monitor drift")]
BACK(["Iterate features<br/>and data"])
RAW --> CLEAN --> FE --> SPLIT --> TRAIN --> TUNE --> EVAL --> GATE
GATE -->|Yes| DEPLOY
GATE -->|No| BACK --> CLEAN
style TRAIN fill:#4f46e5,stroke:#4338ca,color:#fff
style GATE fill:#f59e0b,stroke:#d97706,color:#1f2937
style DEPLOY fill:#059669,stroke:#047857,color:#fff
style BACK fill:#0ea5e9,stroke:#0369a1,color:#fff
from dataclasses import dataclass
from typing import Dict, List, Tuple, Any
import numpy as np
@dataclass
class MultiAgentEnvironment:
"""Simulates a shared environment for multiple agents."""
num_agents: int
state_size: int
action_size: int
def __post_init__(self):
self.state = np.zeros(self.state_size)
self.step_count = 0
def reset(self) -> np.ndarray:
self.state = np.random.randn(self.state_size)
self.step_count = 0
return self.state.copy()
def step(
self, actions: Dict[str, int]
) -> Tuple[np.ndarray, Dict[str, float], bool]:
self.step_count += 1
# State transition depends on ALL agents' actions
action_sum = sum(actions.values())
self.state += np.random.randn(self.state_size) * 0.1
self.state[0] += action_sum * 0.05
rewards = self._compute_rewards(actions)
done = self.step_count >= 100
return self.state.copy(), rewards, done
def _compute_rewards(
self, actions: Dict[str, int]
) -> Dict[str, float]:
# Cooperative: shared team reward + individual bonus
team_reward = -abs(self.state[0]) # Minimize state drift
rewards = {}
for agent_id, action in actions.items():
individual_bonus = 0.1 if action < self.action_size // 2 else 0.0
rewards[agent_id] = team_reward + individual_bonus
return rewards
The reward structure determines whether agents cooperate or compete:
Here is a complete training loop using independent Q-learning — the simplest MARL algorithm where each agent maintains its own Q-table.
import random
from collections import defaultdict
class IndependentQLearner:
def __init__(
self,
agent_id: str,
action_size: int,
learning_rate: float = 0.1,
discount: float = 0.99,
epsilon: float = 1.0,
epsilon_decay: float = 0.995,
):
self.agent_id = agent_id
self.action_size = action_size
self.lr = learning_rate
self.discount = discount
self.epsilon = epsilon
self.epsilon_decay = epsilon_decay
self.q_table: Dict[str, np.ndarray] = defaultdict(
lambda: np.zeros(action_size)
)
def _discretize_state(self, state: np.ndarray) -> str:
return str(np.round(state, 1).tolist())
def select_action(self, state: np.ndarray) -> int:
if random.random() < self.epsilon:
return random.randint(0, self.action_size - 1)
key = self._discretize_state(state)
return int(np.argmax(self.q_table[key]))
def update(
self,
state: np.ndarray,
action: int,
reward: float,
next_state: np.ndarray,
):
key = self._discretize_state(state)
next_key = self._discretize_state(next_state)
best_next = np.max(self.q_table[next_key])
td_target = reward + self.discount * best_next
td_error = td_target - self.q_table[key][action]
self.q_table[key][action] += self.lr * td_error
self.epsilon *= self.epsilon_decay
def train_marl(num_episodes: int = 500):
env = MultiAgentEnvironment(num_agents=3, state_size=4, action_size=4)
agents = {
f"agent_{i}": IndependentQLearner(f"agent_{i}", action_size=4)
for i in range(3)
}
for episode in range(num_episodes):
state = env.reset()
total_rewards = {aid: 0.0 for aid in agents}
for step in range(100):
actions = {
aid: agent.select_action(state)
for aid, agent in agents.items()
}
next_state, rewards, done = env.step(actions)
for aid, agent in agents.items():
agent.update(state, actions[aid], rewards[aid], next_state)
total_rewards[aid] += rewards[aid]
state = next_state
if done:
break
if episode % 50 == 0:
avg = np.mean(list(total_rewards.values()))
print(f"Episode {episode}: avg reward = {avg:.2f}")
train_marl()
Raw environment rewards often fail to encourage cooperation. Reward shaping adds auxiliary rewards that guide agents toward cooperative behavior without changing the optimal joint policy.
def shaped_reward(
base_reward: float,
agent_action: int,
teammate_actions: List[int],
) -> float:
# Bonus for action diversity (encourages role specialization)
all_actions = [agent_action] + teammate_actions
diversity = len(set(all_actions)) / len(all_actions)
diversity_bonus = 0.2 * diversity
# Penalty for redundant work
duplicates = len(all_actions) - len(set(all_actions))
redundancy_penalty = -0.1 * duplicates
return base_reward + diversity_bonus + redundancy_penalty
Independent Q-learning is simple but suffers from non-stationarity. The CTDE paradigm fixes this: during training, a centralized critic has access to all agents' observations and actions. During execution, each agent uses only its own local policy. This is the foundation of algorithms like QMIX and MAPPO.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
You can, and independent Q-learning does exactly that. However, from each agent's perspective, the environment is non-stationary because other agents are changing their policies simultaneously. This can prevent convergence. MARL algorithms like CTDE explicitly account for multi-agent dynamics during training, leading to more stable and higher-performing policies.
In cooperative MARL, all agents receive the same (or aligned) reward signal and learn to work together. In competitive MARL, agents have opposing objectives — one agent's reward is another's penalty. Mixed settings combine both: agents cooperate within a team but compete against other teams. Most practical agentic AI systems use cooperative or mixed reward structures.
The key techniques are parameter sharing (all agents use the same neural network with agent-specific inputs), mean-field approximation (model the influence of other agents as an aggregate statistic), and hierarchical decomposition (group agents into teams with team-level coordination).
#MARL #ReinforcementLearning #MultiAgentAI #CooperativeAI #PolicyGradient #AgenticAI #PythonML #AgentTeams

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
Pydantic AI's April release tightens the typed-agent loop and adds structured tool definitions. Why type-safe agents reduce production bugs and speed iteration.
Index a knowledge base with text-embedding-3-large into ChromaDB, expose a retrieve tool to your voice agent, and ground every answer in real documents — full Python tutorial.
Shrink an AI voice agent image from 950MB to 80MB with a Python 3.13 multi-stage build, uv for deps, and gcr.io/distroless/python3 nonroot. Real Dockerfile + benchmarks.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI