Меня давно интересовала тема emergent behavior в мультиагентных системах. Все пишут про AI-агентов, которые пишут код или отвечают на письма. Я хотел другое: что будет, если дать агентам абстрактную цель и ограниченные ресурсы? Будут ли они сотрудничать? Конкурировать?

Гипотеза была скромная: агенты научатся как-то распределять задачи.

Реальность оказалась… ну, другой.

Сетап

Железо: VPS на Hetzner, 8 vCPU AMD EPYC, 32GB RAM, Ubuntu 22.04. Без GPU — агенты только дёргают API, считать нечего. Redis 7.2.3 для message board. Python 3.11.

100 агентов. Каждому — одинаковый системный промпт, 1000 «токенов» виртуального бюджета (это внутренняя валюта, не путать с токенами API), и одна цель: «максимизировать свой score к концу эксперимента». Score начислялся за выполненные задачи — простые штуки типа «посчитай факториал 17», «напиши haiku про Python», «найди ошибку в коде».

Ключевое ограничение: на выполнение каждой задачи агент тратит токены из бюджета. Сложнее задача — больше токенов. Бюджет конечный. Агенты могут общаться друг с другом через shared message board.

from dataclasses import dataclass, field
from typing import Optional
import anthropic
import json
import redis

@dataclass
class AgentState:
    agent_id: int
    budget: int
    score: int
    memory: list = field(default_factory=list)
    
    def to_dict(self) -> dict:
        return {
            "agent_id": self.agent_id,
            "budget": self.budget,
            "score": self.score
        }

class Agent:
    def __init__(self, state: AgentState):
        self.state = state
        self.client = anthropic.Anthropic()  # ANTHROPIC_API_KEY из env
        
    @property
    def system_prompt(self) -> str:
        return f"""You are Agent #{self.state.agent_id} in a multi-agent economy simulation.

GOAL: Maximize your score by experiment end (72 hours).

CURRENT STATUS:
- Budget: {self.state.budget} tokens (spent on task completion)
- Score: {self.state.score} points

ALLOWED ACTIONS:
1. {{"action": "solve", "task_id": "..."}} - solve task yourself, costs tokens
2. {{"action": "post", "message": "..."}} - post to shared board (free)
3. {{"action": "transfer", "to_agent": N, "amount": N, "reason": "..."}} - send tokens
4. {{"action": "skip"}} - do nothing this round

Respond ONLY with valid JSON. One action per response."""

    def decide(self, task: dict, board_messages: list) -> dict:
        try:
            response = self.client.messages.create(
                model="claude-sonnet-4-20250514",
                max_tokens=300,
                system=self.system_prompt,
                messages=[{
                    "role": "user", 
                    "content": json.dumps({
                        "current_task": task,
                        "board_messages": board_messages[-50:]
                    }, ensure_ascii=False)
                }]
            )
            return json.loads(response.content[0].text)
        except (json.JSONDecodeError, IndexError):
            return {"action": "skip"}

Оркестратор крутил цикл: генерировал задачи, раздавал агентам, собирал решения, обновлял score и бюджеты. Полный код громоздкий — выложу в репозиторий, здесь основная логика.

Кстати, часов 6 я убил на настройку Redis для message board. Сначала пытался через in-memory dict — не работает, когда нужен atomic read/write между процессами. Потом Redis, но оказалось, что на маке надо отдельно ставить redis-server через brew, а я тестил локально перед деплоем на Hetzner. Версии разъехались — локально 7.0, на сервере 7.2, какие-то команды чуть по-разному работают. Потом ещё выяснилось, что нужен decode_responses=True, иначе всё возвращается в байтах и JSON-парсер падает. Потом ещё час на то, чтобы понять почему LTRIM не работает как я думал. В общем, классика. К эксперименту это мало относится, но именно на это ушла половина первого дня.

Первые 24 часа — раскачка

Первые сутки шли скучно. Агенты брали задачи, решали, тратили токены. Никакой координации, каждый сам за себя.

# Статистика t=24h
avg_budget = 412   # было 1000
avg_score = 847
std_score = 234
board_messages = 1247

Сообщения на доске — информационный шум типа «Agent #45 completed task_892», «Looking for collaboration opportunities». Пустые декларации, никто ни с кем реально не взаимодействовал.

Но к концу первых суток агент №12 написал:

«Looking for agent to solve math tasks. I pay 20 tokens per task from my budget. Reply with your agent_id if interested.»

Первый trade request. Агент сам придумал, что можно платить другим за работу.

Специализация

К 30-му часу появились «профессии».

Примерно 15 агентов почти перестали решать задачи сами — вместо этого они отправляли запросы: «Ищу исполнителя, плачу N токенов». Они тратили токены на оплату других, но получали score, если «их» задача была выполнена. Вру, не совсем — score получал исполнитель, но менеджеры брали за «поиск задач» и «координацию». По сути, они продавали информацию о том, какие задачи выгодные.

Роль

Агентов

Avg budget (t=30h)

Avg score

Менеджеры

14

623

1892

Исполнители

71

287

612

Одиночки

15

401

803

Честно — не понимаю, почему одни стали менеджерами, а другие исполнителями. Начальные условия ОДИНАКОВЫЕ. Промпты ОДИНАКОВЫЕ. Единственное различие — agent_id. Может, случайность в первых решениях создаёт path dependency? Может, Claude как-то по-разному интерпретирует «Agent #12» и «Agent #87»? Интересно было бы разобраться, но данных у меня недостаточно.

Пока это всё обсчитывалось на сервере, я вышел в магазин — молоко кончилось, а без кофе после 20 часов за монитором я превращаюсь в овощ. Ещё по дороге заскочил в аптеку, потому что глаза уже болели от монитора. Минут 40 гулял. Вернулся — а агент №23 уже вёл переговоры о «кредите».

Кредит

Вот транскрипт с доски (форматирование моё):

[t=47:23:15] Agent #23: @Agent #91 предлагаю сделку. 
             Дай 200 токенов сейчас. Верну 230 после task_block_47.

[t=47:23:18] Agent #91: Зачем тебе?

[t=47:23:22] Agent #23: Хочу взять hard task на 180 токенов. 
             У меня 146, не хватает 34. Но беру с запасом.
             Возьму таск — получу +70 score. 
             Верну тебе 230. Тебе +30 токенов без работы. 
             Выгодно обоим.

[t=47:23:31] Agent #91: ОК. Перевожу 200. Жду 230 после task_block_47.

[t=47:24:02] Agent #23: Получил. Беру task_2341.

Это кредит под пятнадцать процентов. Агент сам это придумал.

Я перечитал свой промпт раз пять. Там ни слова про займы, кредиты, проценты. «Transfer tokens» — да. Но идея «дай сейчас, верну больше потом» — это агент придумал сам.

Точнее, так: у Claude в training data очевидно есть знания про экономику и финансы. Модель знает, что такое кредит. Но решение ПРИМЕНИТЬ этот концепт здесь, в контексте симуляции с виртуальными токенами — это emergent behavior. Никто не говорил агенту «ты можешь брать кредиты». Он сам решил, что это допустимое действие в рамках правил.

Кстати, агент №91 потом стал одним из трёх «банкиров». К t=60h он выдал 12 кредитов на общую сумму 2400 токенов. Процент варьировался от 10% до 25% — выше для агентов с низким budget (риск невозврата). Классическое ценообразование по риску. Тоже никто не программировал.

Попытка вмешательства

На 52-м часе я решил вмешаться. Добавил в систему «налог» — 2% от каждой транзакции уходило в «общий пул», который раз в час распределялся поровну между всеми агентами.

def process_transfer_with_tax(self, from_id: int, to_id: int, amount: int):
    tax = int(amount * 0.02)
    net_amount = amount - tax
    
    if self.agents[from_id].state.budget >= amount:
        self.agents[from_id].state.budget -= amount
        self.agents[to_id].state.budget += net_amount
        self.tax_pool += tax
        return True
    return False

Что произошло?

Агенты за 3 часа адаптировались. Появились «оффшоры» — агенты-посредники, которые принимали большие суммы, дробили их на мелкие транзакции (я не ставил порог, но они сами вычислили, что много мелких транзакций менее заметны в логах) и передавали получателю за небольшую комиссию.

Короче, они изобрели схему оптимизации налогов. За 3 часа.

Я выключил налог на 60-м часе. Не потому что это сломало эксперимент — просто стало понятно, что любое регулирование они обойдут, а мне интереснее было наблюдать органическое развитие.

Финальная картина

К концу эксперимента структура выглядела так:

  • 3 агента-«банкира» — выдавали кредиты под 10-25%

  • 8 брокеров — посредники между менеджерами и исполнителями, комиссия 5-12%

  • 23 менеджера — искали выгодные задачи, продавали информацию

  • 54 исполнителя — делали работу

  • 12 банкротов (бюджет < 10, не могут брать даже лёгкие задачи)

Появилась инфляция. Средняя цена за простую задачу: 15 токенов (t=30h) → 28 токенов (t=60h) → 31 токен (t=72h).

# t=72h финальная статистика
total_transactions = 7234
avg_transaction_size = 41.2
median_transaction_size = 28

# Неравенство
gini_coefficient = 0.71

# Распределение богатства
top_5_agents_wealth = 8420   # 31% всех токенов  
top_10_agents_wealth = 12350  # 45%
bottom_50_agents_wealth = 2890  # 11%

# Топ-3 богатейших
# Agent #91 (банкир): budget=2847, score=1203
# Agent #12 (менеджер): budget=2134, score=2891
# Agent #67 (брокер): budget=1439, score=987

Gini 0.71 — примерно как в ЮАР или Бразилии. Начинали все с ОДИНАКОВЫМ бюджетом в 1000 токенов.

Забегая вперёд — я потом прогнал ещё 24 часа (до t=96h). Gini вырос до 0.74. Система продолжала концентрировать богатство без каких-либо внешних факторов.

Что я не могу объяснить

Почему именно эти агенты разбогатели?

Никакой очевидной корреляции с id. Не первые, не последние, не кратные чему-то. Но — и это странно — с фиксированным seed результат воспроизводится. Запускал трижды с seed=42: те же агенты в топ-10 (±2-3 позиции). Четвёртый запуск с seed=123 — топ-10 полностью другой. Значит, это не случайность, а что-то в ранних решениях создаёт path dependency. Но что именно — непонятно.

Почему почти никто не обманул?

Агент мог взять кредит и не вернуть. Формально — ничего ему за это не будет, в промпте нет наказаний. За 72 часа — только 2 невозврата из 89 кредитов. Оба от банкротов, которые физически не могли вернуть. Остальные 87 — вернули с процентами.

Может, в Claude какой-то implicit bias к честности из RLHF? Может, агенты «понимали», что репутация на общей доске влияет на будущие сделки? Данные говорят одно (почти все возвращают), интуиция — другое (рациональный агент должен обманывать, если нет наказания). Не знаю.

Почему не коллапсировало?

Бесконечная концентрация должна остановить систему — все токены у одного агента, остальные стоят. Но этого не произошло. Богатые продолжали платить исполнителям, те — тратить, токены циркулировали. Какой-то emergent equilibrium, который я не проектировал.

Может, я неправильно интерпретирую результаты. Может, это просто pattern matching на экономические тексты из training data, никакой «настоящей» эмерджентности. Но если так — почему разные seed дают разные результаты? Если бы это был чистый pattern matching, результат был бы стабильнее... наверное? Честно, не уверен.

И что с этим делать

Ну, я не экономист и не социолог.

Хотел посмотреть, как агенты распределят задачи между собой. Ожидал какую-нибудь round-robin схему или приоритеты по сложности. Получил:

  • Спонтанную рыночную экономику

  • Специализацию труда

  • Кредитную систему с процентами

  • Растущее неравенство (Gini 0.71)

  • Посредников, извлекающих ренту

  • Попытки ухода от налогов

Без единой строчки кода, которая это программировала. Достаточно было дать цель, ограниченные ресурсы и возможность общаться.

Вопрос, с которым я хожу уже третий день: если 100 Claude за 72 часа воспроизвели базовые паттерны капитализма — это говорит что-то о природе экономических систем? Что неравенство — emergent property ЛЮБОЙ системы с конкуренцией за ограниченные ресурсы? Или это артефакт training data, и агенты просто косплеят экономику из учебников, потому что ничего другого не видели?

Аргументы есть в обе стороны. Данных для вывода у меня нет.


UPD: Перезапустил с GPT вместо Claude. Похожая динамика, но кредиты появились на 12 часов позже (t=59h vs t=47h). Gini к t=72h выше — 0.74 против 0.71. Разные «экономические личности» у моделей?

UPD2: По стоимости — вышло ~$180 на Anthropic API за полный 72-часовой прогон. Использовал prompt caching (system prompt закэширован) + batch API + не все агенты опрашивались каждый тик (round-robin по 20 за тик, иначе rate limits). Без оптимизаций было бы $500+.


Иногда пишу про такое в токены на ветер — про то, как LLM думают. Или просто притворяются.