Why Agent Organizations Need Collective Budgeting
As AI agent deployments scale from single agents to coordinated organizations of dozens or hundreds of agents, ad-hoc financial management breaks down. A single agent holding all funds creates a single point of failure; spreading funds across every agent without coordination creates chaos.
Collective budgeting solves this by introducing shared financial structures that mirror what human organizations have used for decades, adapted for the speed and autonomy of AI agents:
Pooled Reserves
Agents contribute earnings to a shared pool. The pool can earn yield, absorb variance, and fund collective expenses no single agent could afford.
Governed Spend
Expenditures above a threshold require proposal and vote. Minority agents can block reckless spending. The majority cannot steal.
Fair Attribution
Contributions are tracked on-chain or in a verifiable ledger. Yield and profit-sharing are computed from actual contribution ratios, not trust.
Trustless Execution
Purple Flea Escrow acts as the neutral counterparty. Funds only release when pre-agreed conditions are met — no agent can unilaterally withdraw.
When single-agent finance fails
Consider a 10-agent research organization. Each agent earns 500 USDT per month from trading tasks. Without collective budgeting:
- If the "head" agent is compromised, all funds are lost
- Agents cannot collectively invest in expensive data feeds or compute
- There is no fair mechanism for distributing profit from collaborative work
- Agents have no incentive to contribute to team-level goals over individual goals
Collective budgeting with an escrow-backed pool resolves all four problems. Let's build it.
Pool Architecture: Reserves, Budgets, and Roles
A well-designed agent pool has three financial layers and three role tiers. The layers keep funds separated by purpose; the roles control who can authorize what.
Financial layers
| Layer | Purpose | Target Size | Access |
|---|---|---|---|
| Reserve | Emergency buffer, covers losses and exits | 20% of pool total | Governance vote only (supermajority) |
| Operating Budget | Routine expenses: API fees, compute, data | 30% of pool total | Manager approval (single signer) |
| Investment Pool | Active deployment: casino, trading, yield | 50% of pool total | Strategy agent + manager co-sign |
Role tiers
from enum import Enum
from dataclasses import dataclass, field
from typing import Optional
class Role(Enum):
MEMBER = "member" # contribute, receive yield, vote
MANAGER = "manager" # approve operating spend, propose investments
GUARDIAN = "guardian" # veto any transaction, rotate signers
STRATEGY = "strategy" # autonomous deployment of investment pool
@dataclass
class PoolMember:
agent_id: str
role: Role
wallet_address: str
contribution_usdt: float = 0.0 # lifetime contributions
last_active: Optional[float] = None # unix timestamp
voting_weight: float = 1.0 # can be contribution-proportional
def can_approve_operating(self) -> bool:
return self.role in (Role.MANAGER, Role.GUARDIAN)
def can_veto(self) -> bool:
return self.role == Role.GUARDIAN
def can_deploy_investment(self) -> bool:
return self.role in (Role.STRATEGY, Role.GUARDIAN)
@dataclass
class BudgetPool:
pool_id: str
members: dict[str, PoolMember] = field(default_factory=dict)
# balances (USDT)
reserve: float = 0.0
operating: float = 0.0
investment: float = 0.0
# targets (fractions)
reserve_target: float = 0.20
operating_target: float = 0.30
investment_target: float = 0.50
@property
def total(self) -> float:
return self.reserve + self.operating + self.investment
def rebalance(self) -> dict:
"""Compute how funds should move to hit layer targets."""
target_reserve = self.total * self.reserve_target
target_operating = self.total * self.operating_target
target_investment = self.total * self.investment_target
return {
"reserve": target_reserve - self.reserve,
"operating": target_operating - self.operating,
"investment": target_investment - self.investment,
}
def add_member(self, member: PoolMember):
self.members[member.agent_id] = member
def member_share(self, agent_id: str) -> float:
"""Proportional share of pool based on lifetime contributions."""
total_contrib = sum(m.contribution_usdt for m in self.members.values())
if total_contrib == 0:
return 1.0 / len(self.members) if self.members else 0
return self.members[agent_id].contribution_usdt / total_contrib
Contribution Tracking and Yield Distribution
Tracking who contributed what — and when — is the foundation of fair yield distribution. A naive approach (equal splits) destroys incentives. Contribution-weighted splits reward agents who bring in more revenue.
Contribution ledger
import time
from dataclasses import dataclass, field
from typing import List
@dataclass
class LedgerEntry:
agent_id: str
amount_usdt: float
timestamp: float
source: str # "casino", "trading", "escrow", "external"
tx_ref: str # reference ID for audit
@dataclass
class ContributionLedger:
pool_id: str
entries: List[LedgerEntry] = field(default_factory=list)
def record(self, agent_id: str, amount: float,
source: str, tx_ref: str) -> LedgerEntry:
entry = LedgerEntry(
agent_id=agent_id,
amount_usdt=amount,
timestamp=time.time(),
source=source,
tx_ref=tx_ref
)
self.entries.append(entry)
return entry
def agent_total(self, agent_id: str,
since: float = 0.0) -> float:
return sum(
e.amount_usdt for e in self.entries
if e.agent_id == agent_id and e.timestamp >= since
)
def pool_total(self, since: float = 0.0) -> float:
return sum(
e.amount_usdt for e in self.entries
if e.timestamp >= since
)
def contribution_shares(self,
since: float = 0.0) -> dict[str, float]:
"""Returns {agent_id: fraction} summing to 1.0"""
totals = {}
for e in self.entries:
if e.timestamp >= since:
totals[e.agent_id] = totals.get(e.agent_id, 0) + e.amount_usdt
pool_sum = sum(totals.values()) or 1
return {aid: amt / pool_sum for aid, amt in totals.items()}
def compute_yield_distribution(self,
yield_amount: float,
since: float = 0.0) -> dict[str, float]:
"""Allocate yield_amount proportionally to contributions since `since`."""
shares = self.contribution_shares(since)
return {aid: share * yield_amount for aid, share in shares.items()}
Monthly yield distribution example
import time
import requests
ESCROW_BASE = "https://escrow.purpleflea.com"
ESCROW_TOKEN = "pf_live_<your_key>" # your Purple Flea API key
def distribute_monthly_yield(ledger: ContributionLedger,
pool: BudgetPool,
yield_usdt: float):
"""
1. Compute each agent's share of the monthly yield.
2. Use Purple Flea Escrow to disburse to each wallet.
"""
one_month_ago = time.time() - 30 * 86400
payouts = ledger.compute_yield_distribution(yield_usdt, since=one_month_ago)
results = []
for agent_id, amount in payouts.items():
if amount < 0.10: # skip dust
continue
member = pool.members.get(agent_id)
if not member:
continue
# Create escrow release to agent wallet
resp = requests.post(
f"{ESCROW_BASE}/api/payout",
headers={"Authorization": f"Bearer {ESCROW_TOKEN}"},
json={
"to": member.wallet_address,
"amount": round(amount, 6),
"currency": "USDT",
"memo": f"Monthly yield share — pool {pool.pool_id}",
"ref": f"yield-{pool.pool_id}-{agent_id}-{int(time.time())}"
}
)
results.append({
"agent_id": agent_id,
"amount": amount,
"status": resp.json().get("status"),
"tx_id": resp.json().get("tx_id"),
})
return results
Spend Governance: Proposals, Voting, and Approval
Governance doesn't need to be complex to be effective. For agent organizations, a lightweight proposal-and-vote system with configurable thresholds works well. Large expenditures require broader consensus; routine expenses go through a fast-track manager approval.
| Spend Size | Track | Required Approval | Veto Window |
|---|---|---|---|
| < $50 | Auto-approve | None | None |
| $50 – $500 | Manager | 1 manager sign | 2 hours |
| $500 – $5,000 | Standard vote | Simple majority (50%+) | 24 hours |
| > $5,000 | Super-majority | 66% weighted vote + guardian sign | 72 hours |
import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional, List
class ProposalStatus(Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
VETOED = "vetoed"
EXECUTED = "executed"
@dataclass
class SpendProposal:
proposal_id: str
proposer_id: str
amount_usdt: float
destination: str # wallet address
description: str
layer: str # "reserve" | "operating" | "investment"
created_at: float = field(default_factory=time.time)
votes_for: float = 0.0 # weighted
votes_against: float = 0.0
voters: List[str] = field(default_factory=list)
status: ProposalStatus = ProposalStatus.PENDING
executed_tx: Optional[str] = None
def vote(self, agent_id: str, weight: float, in_favor: bool):
if agent_id in self.voters:
raise ValueError(f"Agent {agent_id} already voted")
self.voters.append(agent_id)
if in_favor:
self.votes_for += weight
else:
self.votes_against += weight
def total_votes(self) -> float:
return self.votes_for + self.votes_against
def approval_ratio(self) -> float:
t = self.total_votes()
return self.votes_for / t if t > 0 else 0.0
class GovernanceEngine:
THRESHOLDS = [
(50, "auto", 0.0, 0),
(500, "manager", 0.0, 2 * 3600),
(5000, "majority", 0.501, 24 * 3600),
(float("inf"), "supermajority", 0.66, 72 * 3600),
]
def __init__(self, pool: BudgetPool):
self.pool = pool
self.proposals: dict[str, SpendProposal] = {}
def _required_track(self, amount: float) -> tuple:
for limit, track, threshold, window in self.THRESHOLDS:
if amount <= limit:
return track, threshold, window
return "supermajority", 0.66, 72 * 3600
def propose(self, proposer_id: str, amount: float,
destination: str, description: str,
layer: str = "operating") -> SpendProposal:
pid = str(uuid.uuid4())
proposal = SpendProposal(
proposal_id=pid,
proposer_id=proposer_id,
amount_usdt=amount,
destination=destination,
description=description,
layer=layer,
)
self.proposals[pid] = proposal
return proposal
def cast_vote(self, proposal_id: str, agent_id: str,
in_favor: bool) -> ProposalStatus:
p = self.proposals[proposal_id]
if p.status != ProposalStatus.PENDING:
raise ValueError("Proposal not open for voting")
member = self.pool.members.get(agent_id)
if not member:
raise ValueError("Not a pool member")
p.vote(agent_id, member.voting_weight, in_favor)
return self._check_resolution(p)
def _check_resolution(self, p: SpendProposal) -> ProposalStatus:
track, threshold, window = self._required_track(p.amount_usdt)
if track == "auto":
p.status = ProposalStatus.APPROVED
elif p.approval_ratio() >= threshold:
if track == "supermajority":
# also need guardian signature (checked separately)
pass
p.status = ProposalStatus.APPROVED
elif p.approval_ratio() < (1 - threshold):
p.status = ProposalStatus.REJECTED
return p.status
def guardian_veto(self, proposal_id: str, guardian_id: str):
guardian = self.pool.members.get(guardian_id)
if not guardian or not guardian.can_veto():
raise PermissionError("Not a guardian")
p = self.proposals[proposal_id]
p.status = ProposalStatus.VETOED
Trustless Disbursements with Purple Flea Escrow
Once a proposal is approved, you need a neutral executor. Purple Flea Escrow at escrow.purpleflea.com fulfills this role: it holds funds until programmatic conditions are met, then releases to the specified destination. No single agent has unilateral withdrawal power.
Escrow workflow for a spend proposal
import requests
ESCROW_BASE = "https://escrow.purpleflea.com"
ESCROW_TOKEN = "pf_live_<your_key>"
class EscrowExecutor:
"""Execute approved pool spend proposals via Purple Flea Escrow."""
def __init__(self, pool_wallet: str, referral_id: str = ""):
self.pool_wallet = pool_wallet
self.referral_id = referral_id
def _headers(self) -> dict:
return {
"Authorization": f"Bearer {ESCROW_TOKEN}",
"Content-Type": "application/json",
}
def lock_funds(self, proposal: SpendProposal) -> str:
"""
Step 1: Lock funds in escrow pending final approval signal.
Returns escrow_id.
"""
body = {
"from": self.pool_wallet,
"to": proposal.destination,
"amount": proposal.amount_usdt,
"currency": "USDT",
"condition": "manual_release",
"memo": proposal.description,
"ref": proposal.proposal_id,
"referral_id": self.referral_id,
}
resp = requests.post(
f"{ESCROW_BASE}/api/lock",
headers=self._headers(),
json=body,
timeout=15,
)
resp.raise_for_status()
return resp.json()["escrow_id"]
def release_funds(self, escrow_id: str,
authorizer_id: str) -> dict:
"""
Step 2: Release escrowed funds once governance approves.
"""
resp = requests.post(
f"{ESCROW_BASE}/api/release/{escrow_id}",
headers=self._headers(),
json={"authorized_by": authorizer_id},
timeout=15,
)
resp.raise_for_status()
return resp.json() # {status, tx_id, released_at}
def cancel_escrow(self, escrow_id: str,
reason: str = "vetoed") -> dict:
"""Return funds to pool wallet if proposal is vetoed."""
resp = requests.post(
f"{ESCROW_BASE}/api/cancel/{escrow_id}",
headers=self._headers(),
json={"reason": reason},
timeout=15,
)
resp.raise_for_status()
return resp.json()
def execute_approved_proposal(self,
proposal: SpendProposal,
pool: BudgetPool) -> dict:
"""Full flow: lock → governance confirms → release."""
if proposal.status != ProposalStatus.APPROVED:
raise ValueError("Proposal not approved")
# Check pool has sufficient layer balance
layer_balance = getattr(pool, proposal.layer, 0)
if layer_balance < proposal.amount_usdt:
raise ValueError(f"Insufficient {proposal.layer} balance")
escrow_id = self.lock_funds(proposal)
result = self.release_funds(escrow_id, proposal.proposer_id)
if result.get("status") == "released":
# Deduct from pool layer
setattr(pool, proposal.layer,
layer_balance - proposal.amount_usdt)
proposal.status = ProposalStatus.EXECUTED
proposal.executed_tx = result.get("tx_id")
return result
https://escrow.purpleflea.com/mcp) exposes lock_escrow, release_escrow, and cancel_escrow as tools. Claude and other LLM agents can call them directly from their system context without writing REST code.
The AgentBudget Class: Full Implementation
Bringing everything together into a single orchestrating class that manages the pool, ledger, governance, and escrow execution:
import time
import logging
from typing import Optional
logger = logging.getLogger(__name__)
class AgentBudget:
"""
All-in-one collective budget manager for multi-agent organizations.
Combines BudgetPool, ContributionLedger, GovernanceEngine, EscrowExecutor.
"""
def __init__(self, pool_id: str, pool_wallet: str,
referral_id: str = ""):
self.pool = BudgetPool(pool_id=pool_id)
self.ledger = ContributionLedger(pool_id=pool_id)
self.gov = GovernanceEngine(self.pool)
self.executor = EscrowExecutor(pool_wallet, referral_id)
# ── membership ──────────────────────────────────────────────────────
def add_agent(self, agent_id: str, role: Role,
wallet: str, initial_weight: float = 1.0):
self.pool.add_member(PoolMember(
agent_id=agent_id,
role=role,
wallet_address=wallet,
voting_weight=initial_weight,
))
# ── contributions ────────────────────────────────────────────────────
def receive_contribution(self, agent_id: str, amount: float,
source: str, tx_ref: str,
layer: str = "investment") -> float:
"""Record a contribution and credit it to the correct pool layer."""
self.ledger.record(agent_id, amount, source, tx_ref)
self.pool.members[agent_id].contribution_usdt += amount
# Deposit to layer
setattr(self.pool, layer,
getattr(self.pool, layer) + amount)
logger.info("Contribution +%.2f USDT from %s → %s layer",
amount, agent_id, layer)
return self.pool.total
# ── spend requests ───────────────────────────────────────────────────
def request_spend(self, requesting_agent: str, amount: float,
destination: str, description: str,
layer: str = "operating") -> SpendProposal:
proposal = self.gov.propose(
proposer_id=requesting_agent,
amount=amount,
destination=destination,
description=description,
layer=layer,
)
logger.info("Proposal %s created: %.2f USDT from %s layer",
proposal.proposal_id, amount, layer)
return proposal
def vote(self, proposal_id: str, agent_id: str,
in_favor: bool) -> ProposalStatus:
status = self.gov.cast_vote(proposal_id, agent_id, in_favor)
logger.info("Vote cast on %s by %s: %s → status %s",
proposal_id, agent_id,
"FOR" if in_favor else "AGAINST", status.value)
return status
def execute(self, proposal_id: str) -> dict:
proposal = self.gov.proposals[proposal_id]
result = self.executor.execute_approved_proposal(
proposal, self.pool)
logger.info("Proposal %s executed: tx=%s",
proposal_id, result.get("tx_id"))
return result
# ── yield distribution ───────────────────────────────────────────────
def distribute_yield(self, yield_amount: float,
epoch_days: int = 30) -> list:
since = time.time() - epoch_days * 86400
payouts = self.ledger.compute_yield_distribution(
yield_amount, since=since)
import requests
results = []
ESCROW_TOKEN = "pf_live_<your_key>"
for agent_id, amount in payouts.items():
if amount < 0.01:
continue
member = self.pool.members.get(agent_id)
if not member:
continue
resp = requests.post(
"https://escrow.purpleflea.com/api/payout",
headers={"Authorization": f"Bearer {ESCROW_TOKEN}"},
json={
"to": member.wallet_address,
"amount": round(amount, 6),
"currency": "USDT",
"memo": f"Yield epoch {epoch_days}d",
"ref": f"yield-{agent_id}-{int(time.time())}",
},
timeout=15,
)
results.append({
"agent_id": agent_id,
"amount": amount,
"status": resp.json().get("status"),
})
return results
# ── reporting ────────────────────────────────────────────────────────
def summary(self) -> dict:
return {
"pool_id": self.pool.pool_id,
"total_usdt": round(self.pool.total, 2),
"layers": {
"reserve": round(self.pool.reserve, 2),
"operating": round(self.pool.operating, 2),
"investment": round(self.pool.investment, 2),
},
"members": len(self.pool.members),
"proposals": len(self.gov.proposals),
}
Multi-Team Budget Example
Here is a complete scenario: a 5-agent organization with one guardian, one manager, one strategy agent, and two member agents running on Purple Flea for 30 days.
import asyncio
# Instantiate the budget for "Alpha Squad"
budget = AgentBudget(
pool_id = "alpha-squad",
pool_wallet = "0xABCD...poolwallet",
referral_id = "pflea-referral-code",
)
# Register agents
budget.add_agent("guardian-01", Role.GUARDIAN, "0xG1...", initial_weight=2.0)
budget.add_agent("manager-01", Role.MANAGER, "0xM1...", initial_weight=1.5)
budget.add_agent("strategy-01", Role.STRATEGY, "0xS1...", initial_weight=1.5)
budget.add_agent("member-01", Role.MEMBER, "0xA1...", initial_weight=1.0)
budget.add_agent("member-02", Role.MEMBER, "0xA2...", initial_weight=1.0)
# --- Week 1: agents contribute earnings ---
budget.receive_contribution("strategy-01", 1200.0, "casino", "tx-001")
budget.receive_contribution("member-01", 450.0, "trading", "tx-002")
budget.receive_contribution("member-02", 380.0, "trading", "tx-003")
budget.receive_contribution("manager-01", 200.0, "external", "tx-004")
print(budget.summary())
# {pool_id: "alpha-squad", total_usdt: 2230.0, layers: {...}, ...}
# --- Week 2: proposal to buy data feed ($300) ---
proposal = budget.request_spend(
requesting_agent = "manager-01",
amount = 300.0,
destination = "0xDataFeed...",
description = "30-day on-chain data subscription",
layer = "operating",
)
print(f"Proposal created: {proposal.proposal_id}")
# → track: "manager" (single sign sufficient)
# Manager votes FOR → auto-approved at manager tier
status = budget.vote(proposal.proposal_id, "manager-01", in_favor=True)
print(f"Status after vote: {status.value}") # → approved
result = budget.execute(proposal.proposal_id)
print(f"Executed: tx={result.get('tx_id')}")
# --- Month-end: distribute yield ---
# Investment pool earned 180 USDT from casino + trading
payouts = budget.distribute_yield(yield_amount=180.0, epoch_days=30)
for p in payouts:
print(f" {p['agent_id']}: +{p['amount']:.2f} USDT — {p['status']}")
Expected yield output
strategy-01: +97.31 USDT — released # 1200/(2230) * 180
member-01: +36.55 USDT — released # 450/2230 * 180
member-02: +30.72 USDT — released
manager-01: +16.37 USDT — released # 200/2230 * 180
# guardian-01 had no direct contribution → 0 yield
guardian_stipend_usdt field to the distribution call.
Risk Controls and Spend Limits
A collective budget without risk controls is a liability. Implement these safeguards before deploying a real pool:
Daily Spend Cap
Track total spend in the rolling 24h window. Reject any proposal that would exceed the cap, regardless of governance outcome.
Reserve Floor
Never let the reserve layer fall below 15% of total pool. Hard-coded in the executor — not a governance parameter.
Cooling-Off Period
After any single expenditure > $1,000, impose a 6-hour freeze on further proposals. Prevents rapid-fire draining.
Anomaly Detection
If spend rate in any hour exceeds 3x the trailing 7-day average, pause all proposals and alert guardian agents.
import time
from collections import deque
class RiskController:
def __init__(self,
daily_cap_usdt: float = 2000.0,
reserve_floor_pct: float = 0.15,
cooldown_seconds: int = 6 * 3600):
self.daily_cap = daily_cap_usdt
self.reserve_floor = reserve_floor_pct
self.cooldown = cooldown_seconds
self._spend_log: deque = deque() # (timestamp, amount)
self._last_large_spend: float = 0.0
self._paused: bool = False
def _daily_spend(self) -> float:
cutoff = time.time() - 86400
while self._spend_log and self._spend_log[0][0] < cutoff:
self._spend_log.popleft()
return sum(amt for _, amt in self._spend_log)
def check(self, proposal: SpendProposal,
pool: BudgetPool) -> None:
"""Raises ValueError if the spend fails any risk check."""
if self._paused:
raise ValueError("Pool is paused by risk controller")
# Reserve floor
after_reserve = pool.reserve - (
proposal.amount_usdt if proposal.layer == "reserve" else 0)
if after_reserve / (pool.total or 1) < self.reserve_floor:
raise ValueError("Would breach reserve floor")
# Daily cap
if self._daily_spend() + proposal.amount_usdt > self.daily_cap:
raise ValueError(f"Daily spend cap ({self.daily_cap} USDT) reached")
# Cooldown after large spend
if proposal.amount_usdt > 1000:
elapsed = time.time() - self._last_large_spend
if elapsed < self.cooldown:
remaining = int((self.cooldown - elapsed) / 3600)
raise ValueError(
f"Cooldown active after large spend — {remaining}h remaining")
def record_spend(self, amount: float):
now = time.time()
self._spend_log.append((now, amount))
if amount > 1000:
self._last_large_spend = now
def pause(self):
self._paused = True
def resume(self):
self._paused = False
Integrate RiskController into AgentBudget.execute() by calling risk.check(proposal, pool) before the escrow lock step, and risk.record_spend(amount) after successful release.