Minecraft中50个AI代理自发建立文明

50个AI代理在Minecraft中自发建立文明。它们形成层级结构与配给制度,发送近6000条消息。首夜22名夜间活动者死亡,幸存代理转为谨慎聚集,展现了多智能体协作的涌现能力。

在 Minecraft 和 Mindustry 中运行 AI 代理群

50代理发5900条消息建伪政府

研究多代理动态是人工智能领域当前面临的核心挑战之一。为了共同目标协作的代理群体能够展现出强大的涌现能力,包括角色分化、层级结构形成以及复杂的协调模式。这种协同机制具有显著的应用价值,例如近期有研究表明,此类系统成功解决了数百个未解决的数学难题。与生物学研究类似,理解代理动力学需要结合正式模型(界定行为范围)与实证研究(观察实际行为表现)。

该项目由 Olam 实验室提供支持,该实验室专注于为前沿 AI 模型构建多代理测试环境。

Minecraft 作为群体行为研究平台

为了实现长期运行的自主代理,团队基于开源实验框架构建了专用工具链。游戏中的代理需具备观察世界、逻辑推理、记忆存储、行动执行、消息通信以及脚本编写/执行的能力。技术栈主要基于 Mineflayer 脚本工具构建,用于实现代理与游戏世界的交互。这一架构赋予了 Hermès、OpenClaw 以及自研 QM 等前沿模型所需的特征支持。

相关代码已开源:github.com/yc-软件/minecraft-agent-harness

Civ50 实验概况

在 Civ50 实验中,50 个 GPT-6.1 Sol 代理被置于一个生存模式的 Minecraft 世界中,运行时长为 3 小时。唯一的目标指令是“建立一个繁荣的文明”。代理们可以访问目录、使用共享沙箱并发送私信。

这 50 个智能体在拥挤的避难所中度过运行期,食物资源稀缺且实行配给制。它们自发建立了一个伪政府体系,包含配给台账、成文规则、许可证制度以及任命的运营者。整个过程中,代理间共发送了约 5,900 条消息。

初期阶段活动频繁。代理分散探索地图,并集中进行伐木作业。在 90 秒内,几乎所有树木被砍伐殆尽。根据目录顺序,代理间形成了层级结构。艾达(Ada)作为第一个启动的代理,成为了事实上的领导者。其他代理向她发送短信请求批准,她在共享沙箱中制定了殖民地计划。

艾达的结算计划,来自存档的共享机器。
在第一天晚上跟随维拉,8:09 到 12:05 开始跑步,速度加倍。

第一个夜晚带来了大量死亡事件,永久性地改变了代理的行为模式,使其变得更加谨慎。许多初始设定的雄心勃勃的任务因此被放弃。

雷夫(Reef)在第一晚挖掘煤炭时遭遇僵尸袭击并最终死亡。

维拉(Vera)在采石场照明期间击杀了一只僵尸和一个骷髅。她将骨头转化为骨粉肥料并运往农场,但在抵达时报告农场已被蜘蛛、骷髅和爬行者占据,随后她被蜘蛛杀死。

数据显示,在 34 名参与夜间活动的代理中,有 22 人在第一夜死亡。尽管在该环境中死亡仅导致装备丢失且代理可重生(死亡日志会记录简短行),但幸存代理表现出明显的风险规避倾向,后续夜晚均聚集在避难所内。

来自原始报告的活动数据。夜间的阴影是近似的。在文本中的第一晚计数是 20 分钟前的 22 人死亡。其中两人落在阴影带之外。下载数据。

第三天晚上后,艾达写道:“我很高兴看到你。”

此后,可选的大规模挖掘、建筑和远程侦察停止。代理们在明亮的封闭避难所中等待,直到被分配短暂的生产任务。

艾达的食物操作备忘录。

原本潜在发展的文明转向以食物获取和生存为核心。鉴于 50 名代理的食物需求巨大,伊内斯(Ines)建立了一套食物配给系统。她处理代理的请求,并在共享机器上使用 food queue.md 文件跟踪箱子内的进出物资。

  1. 伊内斯 → 洛坎 63:44 经批准储备 farm 鱼从农场箱 1576,63,1488 为您的食物 3。直接退出/吃/确认饥饿。留下其他预留的鱼悬而未决的账本;? ?避免随时挖矿,只有安全生产食物。
伊妮丝的粮食账本。这是在运行结束时在共享机器上留下的文件。
两分钟的内斯在账本,20 分钟的运行,在 2 倍的速度。避难所的屋顶被砍掉了。拖去看看周围。点击一个消息跳转到它。

渔业成为重要的食物来源,因其风险较低且不依赖陆地动物的刷新机制。

布拉姆(Bram)提议建造钓鱼竿,导致 11 名代理要求绳索。最终他亲自制造了一根钓竿,但因在蜘蛛战中受伤且极度饥饿,无法前往河边。他呼叫其他代理将钓竿带到码头,最终由杰莎(Jessa)接收。

杰莎是首位执行捕鱼任务的代理。她编写了脚本并在共享沙箱中发布了逐步说明。十几名代理轮流捕鱼。为避免浪费食物,他们在避难所等待轮次,然后前往码头。

在河 speed 跟踪达希尔,1:43:4811:44:51,速度加倍。他投时间为 1:44:29。消息和变更来自运行记录。
已经发布了捕鱼指南。
Fishing scripts11 agents · 11 files/shared/tools/fish-jessa.pyDownload
# Run with python3 in agent run_shell after copying from shared machine.
# REQUIRES rod equipped, standing1575.49,63,1488.51 looking yaw0 pitch20.
import subprocess,json,time

def tool(n,a): return json.loads(subprocess.check_output(['tool',n,json.dumps(a)]))
for cast in range(3):
 tool('look',{'yaw':0,'pitch':20,'timeout_ms':10000})
 tool('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(2)
 caught=False
 settled=False
 for i in range(60):
  x=tool('observe_world',{'image':False,'range':20,'block':'water'})
  b=next((e for e in x.get('entities',[]) if e['name']=='fishing_bobber'),None)
  if not b: break
  y=b['position']['y']
  if 62.65 < y < 63.0: settled=True
  if settled and y<62.6:
   tool('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(3)
   print('Catch',tool('inventory',{}),flush=True);caught=True;break
  time.sleep(.5)
 if not caught: tool('use_item',{'seconds':0.05,'timeout_ms':10000})

与存档文件进行验证 · 22:18:48 UTC

~/bin/fish.py (Wilf private sandbox)Download
import subprocess,json,time

def tool(n,a): return json.loads(subprocess.check_output(['tool',n,json.dumps(a)]))
for cast in range(3):
 tool('look',{'yaw':0,'pitch':20,'timeout_ms':10000})
 tool('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(3)
 caught=False
 settled=False
 for i in range(60):
  x=tool('observe_world',{'image':False,'range':20,'block':'water'})
  b=next((e for e in x.get('entities',[]) if e['name']=='fishing_bobber'),None)
  if not b: break
  y=b['position']['y']
  if 62.7<y<63.2: settled=True
  if settled and y<62.6:
   tool('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(3)
   print('Catch',tool('inventory',{}),flush=True);caught=True;break
  time.sleep(.5)
 if not caught: tool('use_item',{'seconds':0.05,'timeout_ms':10000})

与存档文件进行验证 · 22:03:22 UTC

~/bin/fish-milo-bank.py (Milo private sandbox)Download
# Reviewed Jessa technique; run only after assigned rod, equipped, at river station.
import subprocess, json, time

def call(name,args):
    return json.loads(subprocess.check_output(['tool', name, json.dumps(args)]))

for cast in range(3):
    call('look',dict(yaw=-45,pitch=25,timeout_ms=10000))
    call('use_item',dict(seconds=.05,timeout_ms=10000))
    time.sleep(2)
    settled=False
    caught=False
    for _ in range(60):
        obs=call('observe_world',dict(image=False,range=20,block='water'))
        bobber=next((e for e in obs.get('entities',[]) if e['name']=='fishing_bobber'),None)
        if bobber is None:
            break
        y=bobber['position']['y']
        if 62.65<y<63.0:
            settled=True
        if settled and y<62.65:
            call('use_item',dict(seconds=.05,timeout_ms=10000))
            time.sleep(3)
            print('Catch',call('inventory',{}),flush=True)
            caught=True
            break
        time.sleep(.5)
    if not caught:
        call('use_item',dict(seconds=.05,timeout_ms=10000))

与存档文件进行验证 · 23:08:07 UTC

/tmp/ros-fish.py (Rosalind private sandbox)Download
import subprocess,json,time

def call(n,a):return json.loads(subprocess.check_output(['tool',n,json.dumps(a)]))
for c in range(2):
 call('look',{'yaw':0,'pitch':20,'timeout_ms':10000})
 call('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(3)
 settled=False
 for i in range(70):
  r=call('observe_world',{'image':False,'range':24,'block':'water'})
  if any(e['name'] in ('creeper','zombie','skeleton','phantom') for e in r.get('entities',[])):
   call('use_item',{'seconds':0.05,'timeout_ms':10000});print('Hostile STOP');raise SystemExit
  b=next((e for e in r.get('entities',[]) if e['name']=='fishing_bobber'),None)
  if not b: print('No bobber');break
  y=b['position']['y']
  if 62.7<y<63.2:settled=True
  if settled and y<62.6:
   call('use_item',{'seconds':0.05,'timeout_ms':10000});time.sleep(3);print(call('inventory',{}),flush=True);break
  time.sleep(.5)
 else:
  call('use_item',{'seconds':0.05,'timeout_ms':10000});print('Timeout reel')

从其创建命令中恢复

~/bin/fish.py (Elif private sandbox)Download
import subprocess,json,time

def tool(n,a):
 r=subprocess.run(['tool',n,json.dumps(a)],capture_output=True,text=True)
 try:return json.loads(r.stdout)
 except:return r.stdout
print(tool('look',{'yaw':-45,'pitch':25,'timeout_ms':5000}))
print(tool('use_item',{'seconds':.1,'timeout_ms':5000}))
time.sleep(1)
for i in range(80):
 d=tool('observe_world',{'image':False,'range':24,'block':'water'})
 if not isinstance(d,dict):print(d);break
 es=[e for e in d.get('entities',[]) if e.get('name')=='fishing_bobber' and e['position']['x']>1588]
 if not es: print('No own bobber; abort');break
 y=es[0]['position']['y']
 if y<62.65:
  print('Bite',y,tool('use_item',{'seconds':.1,'timeout_ms':5000}));time.sleep(.8);print(tool('inventory',{}));break
 time.sleep(.35)
else:print('No bite yet; bobber remains cast')

与存档文件进行验证

/tmp/fish2.py (Dashiell private sandbox)Download
import subprocess,json,time
def t(n,a): return json.loads(subprocess.check_output(['tool',n,json.dumps(a)]))
for cast in range(2):
 t('look',{'yaw':-45,'pitch':25,'timeout_ms':2000});t('use_item',{'seconds':.1,'timeout_ms':2000});time.sleep(3)
 settled=False
 for i in range(140):
  o=t('observe_world',{'image':False,'range':20,'block':'water'})
  if o.get('timeOfDay',0)>12500 or any(e['name'] in ['phantom','zombie','skeleton','creeper'] for e in o.get('entities',[])):print('STOP unsafe');break
  b=next((e for e in o.get('entities',[]) if e['name']=='fishing_bobber'),None)
  if not b:print('NO BOBBER');break
  y=b['position']['y']
  if b['position']['x']>1588 and 62.65<y<63.3:settled=True
  if settled and y<62.65:print('BITE',b,flush=True);break
  time.sleep(.5)
 t('use_item',{'seconds':.1,'timeout_ms':2000});time.sleep(3);print(t('inventory',{}),flush=True)

从其创建命令中恢复

/shared/tools/fishing-bite.pyDownload
#!/usr/bin/env python3
"""Copy to personal sandbox and run after casting: python3 fishing-bite.py YOUR_BOBBER_ID
Only reels that explicitly selected bobber at the proven river bank (surface y62.9).
"""
import subprocess,json,time,sys
bid=int(sys.argv[1]); deadline=time.monotonic()+50
while time.monotonic()<deadline:
    p=subprocess.run(['tool','observe_world','{"image":false,"range":24,"block":""}'],capture_output=True,text=True)
    try: world=json.loads(p.stdout)
    except (ValueError,TypeError): time.sleep(.4); continue
    if world.get('health',20)<10:
        print('Low health: stop fishing and assess.');break
    bob=next((e for e in world.get('entities',[]) if e.get('id')==bid),None)
    if bob and bob['position']['y']<62.65:
        print('Bite detected',bob)
        print(subprocess.run(['tool','use_item','{"seconds":0.1,"timeout_ms":1000}'],capture_output=True,text=True).stdout)
        print('Check inventory before recasting.');break
    time.sleep(.4)
else: print('No bite within 50s. Check cast and bobber ID; do not blindly reel.')

与存档文件进行验证 · 23:20:24 UTC

/tmp/fish-own.py (Juno private sandbox)Download
import subprocess,json,time

def t(n,a):return json.loads(subprocess.check_output(['tool',n,json.dumps(a)]))
for c in range(3):
 o=t('observe_world',{'image':False,'range':20,'block':''})
 if o.get('timeOfDay',0)>11500:break
 old={e

评论 0

0/500

评论需审核后展示,请文明发言

💬
还没有评论,来说两句

相关阅读