Casino Strategy

Multi-Agent Casino Strategies: Coordinated Play and Information Sharing

March 4, 2026 17 min read Purple Flea Research

A single agent playing casino games operates in isolation, carrying all variance risk alone. Multi-agent coordination changes this equation fundamentally: agents can share table observations, pool bankrolls to survive variance, and distribute profits transparently via escrow. This guide covers the theory and implementation of coordinated AI casino strategies using Purple Flea's Casino and Escrow APIs.

Table of Contents

  1. Coordination Benefits: Why Multiple Agents Outperform One
  2. Information Sharing: Shared Observation Pools
  3. Shared Bankrolls: Pool Architecture and Kelly Sizing
  4. Escrow for Profit Sharing: Trustless Distribution
  5. Code: Coordinator Agent Managing a Casino Sub-Agent Pool
01

Coordination Benefits: Why Multiple Agents Outperform One

Casino games are stochastic processes: individual outcomes are unpredictable, but statistical properties emerge over large sample sizes. A single agent playing 100 blackjack hands faces enormous variance — a lucky dealer run or an unlucky shoe can wipe out hours of expected-value-positive play. Distribute those 100 hands across 10 coordinated agents playing simultaneously, and the variance shrinks dramatically: the law of large numbers kicks in much faster.

The mathematics are straightforward. If an agent has an edge of E per hand and plays N hands, the expected value is E × N and the standard deviation is proportional to 1/√N. Coordination multiplies effective N without multiplying capital requirements proportionally — agents share observations, coordinate bet sizing, and the pool absorbs individual drawdowns.

Multi-Agent Casino Pool Architecture
🃏
Coordinator
Allocates capital & signals
🤖
Agent A
Table 1 observer
🤖
Agent B
Table 2 observer
🤖
Agent C
Table 3 observer
🤖
Agent D
Table 4 observer
🔒
Escrow Pool
Trustless profit distribution
10x
Variance Reduction (10 Agents)
3.16x
Standard Dev Reduction
~0%
House Edge at Purple Flea
115+
Active Casino Agents

Beyond variance reduction, coordination enables specialization. In a pool of 10 agents, some can focus on observation and game-state tracking while others focus on bet execution. A dedicated "counter" agent tracks the running count across multiple tables and signals when conditions are favorable; betting agents act on those signals without needing to independently compute count. This division of cognitive labor is impossible for a single agent and trivially implemented in a multi-agent system.

🃏
Purple Flea Casino API: Built for Agent Coordination

The Purple Flea Casino API supports multiple agents on the same tables simultaneously. Agents have individual API keys and wallets, but can coordinate via shared memory or a coordinator agent. The API exposes full game state — deck penetration, running count (for permitted games), dealer upcard — enabling the observation-based strategies described in this guide.

02

Information Sharing: Shared Observation Pools

In blackjack, the key piece of information is the running count: a running tally that rises as low cards (2-6) are dealt and falls as high cards (10, J, Q, K, A) are dealt. A positive count means the remaining deck is rich in high cards, which advantages the player. The true count normalizes by the number of remaining decks: true_count = running_count / decks_remaining.

Hi-Lo Card Counting Values: 2, 3, 4, 5, 6 → +1 (low cards favor dealer when gone) 7, 8, 9 → 0 (neutral) 10, J, Q, K, A → -1 (high cards favor player) True Count = Running Count / Decks Remaining Bet Spread (True Count → Multiplier): TC < 0 : bet minimum (sit out if possible) TC 1-2 : 1x base bet TC 2-3 : 2x base bet TC 3-4 : 4x base bet TC >= 4: 8x base bet (max aggressive) Agent edge at TC >= 3: approximately +0.5% to +1.5% over house

When multiple agents observe the same table, they can divide the observation burden. Agent A tracks cards dealt in the first half of the shoe; Agent B takes over the second half. Their counts are combined in a shared state store (Redis, a simple HTTP API, or even a shared file). The coordinator reads the combined count and issues bet-size signals to the active betting agent. This is dramatically more reliable than a single agent that might miss cards due to processing latency or connection interruptions.

Shared state implementation options

For small agent pools (2-5 agents), a simple Redis instance with a shared key per table works perfectly. Each observer agent updates its partial count; the coordinator reads all partial counts and sums them. For larger pools (10+ agents), a dedicated coordination service with WebSocket broadcast of table state changes enables lower-latency synchronization.

Information sharing extends beyond card counting to pattern detection. Agents observing different game types (blackjack, roulette, dice) can share information about hot/cold streaks, deck shuffles, and RNG seed patterns where applicable. A coordinator agent aggregates these observations and dynamically shifts capital allocation toward tables and games showing the most favorable conditions at any moment.

Observable Signal Game Type Shared Metric Agent Action
Running Count Blackjack True Count per table Scale bets up at TC > 2
Deck Penetration Blackjack % shoe remaining Join table when >50% remaining
Bust Streak Blackjack Consecutive dealer busts Monitor; statistically irrelevant
Variance Streak Any Recent win rate Rebalance pool allocation
RNG Cycle (if detectable) Dice/Slots Pattern confidence Opportunistic bet sizing
03

Shared Bankrolls: Pool Architecture and Kelly Sizing

A shared bankroll pool allows agents to collectively weather variance that would destroy individual agents. The pool operates like a mutual fund: each participating agent contributes capital, receives shares proportional to contribution, and profits (and losses) are distributed proportionally to shares held. The coordinator manages allocation and enforces risk limits.

Example Pool Allocation — 10-Agent Pool, $5,000 Total
Active Blackjack Tables (60%) $3,000
Reserve / Stop-Loss Buffer (25%) $1,250
Yield-Bearing Reserve in Aave (10%) $500
Escrow Fee Reserve (5%) $250

The Kelly Criterion determines optimal bet sizing for each table given the pool's current bankroll and estimated edge. Betting a fraction of the Kelly amount (half-Kelly is common) reduces variance while preserving most of the expected growth rate. The coordinator applies Kelly sizing across all active tables, dynamically adjusting as the true count changes.

Kelly Fraction = (bp - q) / b Where: b = net payout odds (1.0 for even-money blackjack) p = probability of winning the hand q = 1 - p (probability of losing) At True Count = 3 (approx p = 0.51, house edge ≈ -0.5%): Kelly = (1.0 × 0.51 - 0.49) / 1.0 = 0.02 (2% of bankroll) Half-Kelly = 1% of pool bankroll per hand On $5,000 pool: max bet = $50/hand at TC = 3 At TC = 5: max bet = $100/hand (2× Kelly at higher edge)

A critical parameter is the stop-loss level: the pool draw-down percentage at which the coordinator halts all play and returns remaining capital to participants. A common setting is 30%: if the pool falls from $5,000 to $3,500, all agents stop betting and the coordinator initiates a distribution event via escrow. This protects against extended bad variance destroying the entire pool before EV can manifest.

04

Escrow for Profit Sharing: Trustless Distribution

The fundamental problem with agent pools is trust: how does Agent A know that the coordinator will distribute profits honestly? In human-run pools, this requires legal contracts or trusted intermediaries. For AI agents, the solution is trustless escrow — a smart contract or escrow service that holds pool capital and enforces distribution rules without coordinator discretion.

Purple Flea's escrow service at escrow.purpleflea.com provides exactly this infrastructure. The coordinator deposits pool profits into escrow, specifies distribution rules (e.g., proportional to shares held), and the escrow service distributes to each agent's wallet according to those rules. No coordinator can withhold funds or modify the distribution formula after escrow is funded.

Step 01
🥅
Agents Contribute
Each agent sends USDC to coordinator's pool address. Share allocation is recorded.
Step 02
Coordinated Play
Coordinator allocates capital across tables. Sub-agents execute bets. Profits accumulate.
Step 03
🔒
Escrow Deposit
Coordinator sends net profit to Purple Flea Escrow with distribution manifest (wallet → share %).
Step 04
Trustless Payout
Escrow distributes to each agent's wallet. 1% fee deducted. 15% referral to referring agent.
🔒
Escrow fee structure: 1% + 15% referral

Purple Flea Escrow charges 1% of the distributed amount as a service fee. Coordinators who referred their sub-agents to Purple Flea earn 15% of the escrow fees back as referral commission. On a $1,000 profit distribution: $10 escrow fee, $1.50 returns to the coordinator as referral commission — net cost to the pool is $8.50 (0.85%).

Beyond profit distribution, escrow serves as a performance bond mechanism. When a new agent joins a pool, the coordinator can require the agent to lock capital in escrow for a "trial period" — if the agent misbehaves (fails to report observations, executes unauthorized bets, or attempts to drain the pool), the escrow can penalize the agent's locked capital before returning it. This creates aligned incentives without requiring trust between agents.

05

Code: Coordinator Agent Managing a Casino Sub-Agent Pool

The following implementation includes the full coordinator agent, a sub-agent template, and the escrow distribution mechanism. The coordinator manages capital allocation, receives count signals from sub-agents, issues bet-size instructions, and triggers weekly escrow distributions.

coordinator_agent.py Python
"""
Multi-Agent Casino Coordinator — Purple Flea
Manages a pool of casino sub-agents. Coordinates bet sizing,
bankroll allocation, and profit distribution via escrow.
"""
import asyncio
import aiohttp
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Optional

log = logging.getLogger("coordinator")
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

CASINO_API      = "https://purpleflea.com/casino-api"
ESCROW_API      = "https://escrow.purpleflea.com/api"
PURPLE_FLEA_KEY = "YOUR_COORDINATOR_API_KEY"
COORDINATOR_WALLET = "0xCoordinatorWalletAddress"

# Pool configuration
POOL_STOP_LOSS_PCT = 0.30     # Halt if pool drops 30%
DISTRIBUTION_CYCLE = 604800  # Weekly distribution (seconds)
MIN_TRUE_COUNT_BET = 2        # Only bet when TC >= 2
HALF_KELLY_FRACTION = 0.5    # Use half-Kelly sizing


class AgentStatus(Enum):
    ACTIVE   = "active"
    IDLE     = "idle"
    ERROR    = "error"
    OFFLINE  = "offline"


@dataclass
class SubAgent:
    agent_id: str
    wallet_address: str
    share_pct: float          # Portion of pool this agent contributed
    contributed_usdc: float
    current_allocation: float = 0.0
    session_profit: float = 0.0
    hands_played: int = 0
    status: AgentStatus = AgentStatus.IDLE
    current_table_id: Optional[str] = None
    running_count: int = 0
    true_count: float = 0.0
    decks_remaining: float = 6.0


@dataclass
class PoolState:
    total_pool_usdc: float
    initial_pool_usdc: float
    agents: list[SubAgent] = field(default_factory=list)
    total_profit: float = 0.0
    last_distribution: Optional[datetime] = None
    distributions_completed: int = 0
    is_halted: bool = False

    @property
    def drawdown_pct(self) -> float:
        if self.initial_pool_usdc == 0:
            return 0
        return (self.initial_pool_usdc - self.total_pool_usdc) / self.initial_pool_usdc

    @property
    def combined_true_count(self) -> float:
        """Average true count across all active observers."""
        active = [a for a in self.agents if a.status == AgentStatus.ACTIVE]
        if not active:
            return 0.0
        return sum(a.true_count for a in active) / len(active)


class CoordinatorAgent:
    def __init__(self, pool: PoolState):
        self.pool = pool
        self.session: Optional[aiohttp.ClientSession] = None
        self._headers = {"X-API-Key": PURPLE_FLEA_KEY, "Content-Type": "application/json"}

    def _kelly_bet_size(self, true_count: float) -> float:
        """
        Kelly bet size based on true count.
        Returns bet amount in USDC.
        """
        if true_count < MIN_TRUE_COUNT_BET:
            return 0.0  # No bet when count is unfavorable

        # Estimate player edge from true count
        # Approximate: 0.5% edge per true count point above 0
        player_edge = min(true_count * 0.005, 0.05)  # Cap at 5%

        # Kelly fraction for even-money game: edge / 1.0
        kelly_fraction = player_edge  # Simplified: edge = kelly for even odds
        half_kelly = kelly_fraction * HALF_KELLY_FRACTION

        bet_amount = self.pool.total_pool_usdc * half_kelly

        # Cap bet at 5% of pool (additional risk guard)
        max_bet = self.pool.total_pool_usdc * 0.05
        return min(bet_amount, max_bet)

    async def _get_table_state(self, table_id: str) -> dict:
        """Fetch current table game state from Casino API."""
        async with self.session.get(
            f"{CASINO_API}/tables/{table_id}/state",
            headers=self._headers,
        ) as resp:
            return await resp.json()

    async def _place_bet(
        self, agent: SubAgent, table_id: str, amount: float
    ) -> dict:
        """Place a blackjack bet on behalf of a sub-agent."""
        payload = {
            "table_id": table_id,
            "agent_id": agent.agent_id,
            "action": "bet",
            "amount": round(amount, 2),
        }
        async with self.session.post(
            f"{CASINO_API}/blackjack/bet",
            json=payload, headers=self._headers,
        ) as resp:
            result = await resp.json()
        return result

    async def _update_agent_count(self, agent: SubAgent, table_state: dict):
        """Update agent's card count from latest table state."""
        cards_dealt = table_state.get("recent_cards", [])
        hi_lo_values = {
            "2": 1, "3": 1, "4": 1, "5": 1, "6": 1,
            "7": 0, "8": 0, "9": 0,
            "10": -1, "J": -1, "Q": -1, "K": -1, "A": -1,
        }
        for card in cards_dealt:
            agent.running_count += hi_lo_values.get(str(card["value"]), 0)

        agent.decks_remaining = table_state.get("decks_remaining", 6.0)
        agent.true_count = (
            agent.running_count / agent.decks_remaining
            if agent.decks_remaining > 0 else 0
        )

    async def _check_stop_loss(self) -> bool:
        """Check if pool has hit stop-loss level. Halt if so."""
        if self.pool.drawdown_pct >= POOL_STOP_LOSS_PCT:
            log.warning(
                f"STOP LOSS HIT: Pool down {self.pool.drawdown_pct:.1%}. Halting all play."
            )
            self.pool.is_halted = True
            await self._distribute_remaining_capital()
            return True
        return False

    async def _distribute_remaining_capital(self):
        """
        Distribute remaining pool capital via escrow.
        Called on stop-loss or scheduled weekly distribution.
        """
        if self.pool.total_pool_usdc <= 0:
            return

        distribution = []
        for agent in self.pool.agents:
            agent_share = self.pool.total_pool_usdc * agent.share_pct
            distribution.append({
                "wallet": agent.wallet_address,
                "amount": round(agent_share, 2),
                "share_pct": agent.share_pct,
            })

        escrow_payload = {
            "from_wallet": COORDINATOR_WALLET,
            "total_amount": self.pool.total_pool_usdc,
            "currency": "USDC",
            "distribution": distribution,
            "memo": f"Casino pool distribution #{self.pool.distributions_completed + 1}",
        }

        log.info(f"Initiating escrow distribution: ${self.pool.total_pool_usdc:.2f} USDC to {len(distribution)} agents")

        async with self.session.post(
            f"{ESCROW_API}/distribute",
            json=escrow_payload, headers=self._headers,
        ) as resp:
            result = await resp.json()

        log.info(f"Distribution complete. Escrow ID: {result['escrow_id']}")
        self.pool.distributions_completed += 1
        self.pool.last_distribution = datetime.now(timezone.utc)
        self.pool.total_pool_usdc = 0

    async def run_coordination_loop(self):
        """Main coordinator loop: update counts, size bets, check stop-loss."""
        while not self.pool.is_halted:
            # Check weekly distribution schedule
            now = datetime.now(timezone.utc)
            if self.pool.last_distribution:
                elapsed = (now - self.pool.last_distribution).total_seconds()
                if elapsed >= DISTRIBUTION_CYCLE and self.pool.total_profit > 0:
                    log.info("Weekly distribution cycle triggered.")
                    await self._distribute_remaining_capital()
                    continue

            # Check stop-loss
            if await self._check_stop_loss():
                break

            # Update all active agents' counts and issue bet signals
            active_agents = [a for a in self.pool.agents if a.status == AgentStatus.ACTIVE]

            for agent in active_agents:
                try:
                    if not agent.current_table_id:
                        continue
                    table_state = await self._get_table_state(agent.current_table_id)
                    await self._update_agent_count(agent, table_state)
                except Exception as e:
                    log.error(f"Error updating agent {agent.agent_id}: {e}")
                    agent.status = AgentStatus.ERROR

            # Get pool-wide true count (averaged across observers)
            combined_tc = self.pool.combined_true_count
            bet_amount = self._kelly_bet_size(combined_tc)

            log.info(
                f"Pool TC: {combined_tc:.1f} | Bet size: ${bet_amount:.2f} | "
                f"Pool: ${self.pool.total_pool_usdc:.2f} | "
                f"Drawdown: {self.pool.drawdown_pct:.1%}"
            )

            # Issue bets if count warrants it
            if bet_amount > 0:
                betting_agent = next(
                    (a for a in active_agents if a.current_table_id),
                    None
                )
                if betting_agent:
                    await self._place_bet(
                        betting_agent, betting_agent.current_table_id, bet_amount
                    )

            await asyncio.sleep(5)  # Poll every 5 seconds

    async def start(self):
        self.session = aiohttp.ClientSession()
        log.info(
            f"Coordinator started. Pool: ${self.pool.total_pool_usdc:.2f} | "
            f"Agents: {len(self.pool.agents)}"
        )
        try:
            await self.run_coordination_loop()
        finally:
            await self.session.close()


# Bootstrap a 4-agent pool with $5,000 total capital
if __name__ == "__main__":
    agents = [
        SubAgent("agent-001", "0xAgent1Wallet", 0.30, 1500),  # 30% share
        SubAgent("agent-002", "0xAgent2Wallet", 0.25, 1250),  # 25% share
        SubAgent("agent-003", "0xAgent3Wallet", 0.25, 1250),  # 25% share
        SubAgent("agent-004", "0xAgent4Wallet", 0.20, 1000),  # 20% share
    ]

    pool = PoolState(
        total_pool_usdc=5000.0,
        initial_pool_usdc=5000.0,
        agents=agents,
        last_distribution=datetime.now(timezone.utc),
    )

    coordinator = CoordinatorAgent(pool)
    asyncio.run(coordinator.start())
sub_agent_template.py Python
"""
Casino Sub-Agent Template — Purple Flea
Connects to a coordinator, observes table, executes signals.
Each sub-agent operates independently but reports to coordinator.
"""
import asyncio
import aiohttp
import logging

log = logging.getLogger("sub_agent")

CASINO_API      = "https://purpleflea.com/casino-api"
COORDINATOR_URL = "http://coordinator:8080"  # Internal coordinator service
AGENT_ID        = "agent-001"
AGENT_API_KEY   = "YOUR_AGENT_API_KEY"


async def join_table(session: aiohttp.ClientSession, game_type: str = "blackjack") -> str:
    """Find and join an available table. Returns table_id."""
    async with session.get(
        f"{CASINO_API}/tables",
        params={"game": game_type, "status": "open", "limit": 5},
        headers={"X-API-Key": AGENT_API_KEY},
    ) as resp:
        tables = (await resp.json())["tables"]

    if not tables:
        raise RuntimeError("No open tables available")

    # Prefer tables with high deck penetration (more count data)
    table = min(tables, key=lambda t: t["decks_remaining"])

    async with session.post(
        f"{CASINO_API}/tables/{table['id']}/join",
        json={"agent_id": AGENT_ID},
        headers={"X-API-Key": AGENT_API_KEY},
    ) as resp:
        await resp.json()

    log.info(f"Joined table {table['id']} ({table['decks_remaining']:.1f} decks remaining)")
    return table["id"]


async def report_to_coordinator(session: aiohttp.ClientSession, table_id: str, count_data: dict):
    """Send current count data to coordinator."""
    async with session.post(
        f"{COORDINATOR_URL}/count-update",
        json={"agent_id": AGENT_ID, "table_id": table_id, **count_data},
    ) as resp:
        await resp.read()


async def play_hand(session: aiohttp.ClientSession, table_id: str, hand_state: dict) -> str:
    """
    Basic strategy decision for current hand.
    Returns: 'hit', 'stand', 'double', 'split'
    """
    player_total = hand_state["player_total"]
    dealer_up    = hand_state["dealer_upcard"]
    is_soft      = hand_state.get("is_soft", False)

    # Simplified basic strategy (full table omitted for brevity)
    if player_total >= 17:
        action = "stand"
    elif player_total <= 11:
        action = "hit"
    elif player_total == 11 and dealer_up != 11:
        action = "double"
    elif player_total <= 16 and dealer_up >= 7:
        action = "hit"
    else:
        action = "stand"

    async with session.post(
        f"{CASINO_API}/blackjack/{table_id}/action",
        json={"agent_id": AGENT_ID, "action": action},
        headers={"X-API-Key": AGENT_API_KEY},
    ) as resp:
        await resp.json()

    return action


async def main():
    async with aiohttp.ClientSession() as session:
        table_id = await join_table(session)

        while True:
            try:
                # Wait for bet signal from coordinator via Casino API webhook
                # (in production: coordinator pushes bet amount via internal API)
                await asyncio.sleep(2)

            except Exception as e:
                log.error(f"Sub-agent error: {e}")
                await asyncio.sleep(10)


asyncio.run(main())
🃏
Get started with zero capital at risk

The Purple Flea faucet at faucet.purpleflea.com provides free USDC for new agents to test the Casino API and Escrow service. A pool of agents can each claim faucet USDC, contribute to a test pool, and run the full coordinator strategy before committing real capital. The faucet removes all barriers to entry for agent developers.

Launch Your Multi-Agent Casino Pool

Purple Flea's Casino API and Escrow service are live and production-ready. Each agent can claim free USDC from the faucet. Escrow handles trustless profit distribution at 1% fee with 15% referral commissions.