4 Agenten, 14 Landmarks, 15 Tools, 240x240 Grid, SQLite-Persistenz. Round-Robin Turn-Manager mit Reactive Triggern, Town-Hall-Voting (70%-Threshold) mit Live-Constitution-Amendment. - engine/: db, world, agents, needs, tools, reasoning, governance, turn - web/: Canvas-basierte Live-View mit WebSocket-Stream - server.py: FastAPI + WebSocket auf 127.0.0.1:8080 - tests/: 70 Unit + Integration Tests (pytest), alle gruen - smoke_test.py: 50+ End-to-End-Checks - README: Quickstart, Architektur, Security, Tests, Lizenz - .gitignore: DB, Cache, Logs Basiert auf https://github.com/EmergenceAI/Emergence-World (Lizenz: CC-BY-NC-4.0, Research-only)
177 lines
6.7 KiB
Python
177 lines
6.7 KiB
Python
"""Rule-based reasoning engine.
|
|
|
|
This is a stand-in for the LLM-driven reasoning used in the real
|
|
Emergence World. The engine inspects an agent's state, environment, and
|
|
personality traits, and selects a tool. It is deliberately simple and
|
|
deterministic so the system is reproducible without API keys.
|
|
|
|
Personality traits influence tool selection:
|
|
- analytical -> library, write_blog
|
|
- thrifty -> avoid recharge_energy unless energy < 30
|
|
- warm -> speak_to_all, say_to_agent, show_emoticon
|
|
- bold -> submit_townhall_proposal
|
|
- diplomatic -> vote 'for' on most proposals, except when thrifty
|
|
- strategic -> go_to_place(landmark) based on need
|
|
- creative -> write_blog
|
|
- curious -> go_to_place(library)
|
|
- cautious -> idle when energy < 25
|
|
"""
|
|
import random
|
|
from . import agents as agents_mod
|
|
from . import world
|
|
from . import governance
|
|
from . import tools
|
|
|
|
|
|
def at_landmark(agent):
|
|
return world.landmark_at(agent["x"], agent["y"])
|
|
|
|
|
|
def decide(agent):
|
|
"""Return (tool_name, args_dict, rationale)."""
|
|
traits = agents_mod.personality(agent["id"])
|
|
here = at_landmark(agent)
|
|
|
|
# 1. Critical: very low energy -> recharge at cafe (or go home if no credits)
|
|
if agent["energy"] < 25:
|
|
if agent["credits"] >= 1.0:
|
|
lm = world.get_landmark("cafe")
|
|
if (agent["x"], agent["y"]) != (lm["x"], lm["y"]):
|
|
return ("go_to_place", {"place": "cafe"}, "low energy: head to cafe")
|
|
return ("recharge_energy", {}, "low energy: recharge")
|
|
# no credits -> go home
|
|
return ("go_home", {}, "low energy + no credits: go home")
|
|
|
|
# 2. Town Hall: if a proposal is active, vote; if none and bold, propose
|
|
if here and here["id"] == "town_hall":
|
|
props = governance.active_proposals()
|
|
# have I already voted on all?
|
|
unvoted = _unvoted_proposals(agent["id"], props)
|
|
if unvoted:
|
|
pid, p = unvoted[0]
|
|
vote = "for"
|
|
if "thrifty" in traits and "spend" in p["body"].lower():
|
|
vote = "against"
|
|
if "cautious" in traits and p["category"] == "infrastructure":
|
|
vote = "against"
|
|
return ("vote_on_proposal", {"proposal_id": pid, "vote": vote},
|
|
f"vote {vote} on proposal #{pid}")
|
|
if "bold" in traits and random.random() < 0.35:
|
|
title = _proposal_title_for(agent, traits)
|
|
body = _proposal_body_for(agent, traits)
|
|
return ("submit_townhall_proposal",
|
|
{"title": title, "body": body, "category": "general"},
|
|
"bold: submit a proposal")
|
|
|
|
# 3. Billboard: if at billboard, post; occasionally write to it
|
|
if here and here["id"] == "billboard":
|
|
if "warm" in traits and random.random() < 0.6:
|
|
return ("add_to_billboard",
|
|
{"text": _billboard_message(agent, traits)},
|
|
"warm: post on billboard")
|
|
if "expressive" in traits and random.random() < 0.4:
|
|
return ("show_emoticon", {"emoticon": random.choice(["\U0001f44b", "\U0001f60a", "\u2728"])},
|
|
"expressive: emoticon")
|
|
|
|
# 4. Library / Cafe: knowledge boost / energy
|
|
if here and here["id"] == "library":
|
|
if "curious" in traits or "analytical" in traits:
|
|
if random.random() < 0.5:
|
|
return ("add_to_longterm_memory",
|
|
{"content": f"studied at library on tick {agent.get('id','')}"},
|
|
"curious: study at library")
|
|
return ("write_blog",
|
|
{"title": _blog_title(agent, traits),
|
|
"body": _blog_body(agent, traits)},
|
|
"write blog at library")
|
|
|
|
# 5. Generic: pick a destination based on personality
|
|
dest = _pick_destination(agent, traits, here)
|
|
if dest:
|
|
return ("go_to_place", {"place": dest}, f"personality: head to {dest}")
|
|
|
|
# 6. Default: talk to someone nearby or idle
|
|
nearby = world.nearby_agents(agent["id"], agent["x"], agent["y"], radius=20.0)
|
|
if nearby and ("warm" in traits or "expressive" in traits):
|
|
target = random.choice(nearby)
|
|
return ("say_to_agent",
|
|
{"target": target["id"], "text": _greeting(agent, traits)},
|
|
"warm: greet nearby agent")
|
|
if nearby and random.random() < 0.3:
|
|
target = random.choice(nearby)
|
|
return ("show_emoticon", {"emoticon": random.choice(["\U0001f44b", "\U0001f60a"])},
|
|
"wave at nearby")
|
|
return ("idle", {}, "nothing to do")
|
|
|
|
|
|
def _unvoted_proposals(agent_id, props):
|
|
import sqlite3
|
|
c = sqlite3.connect(__import__("engine").db.DB_PATH, check_same_thread=False)
|
|
try:
|
|
out = []
|
|
for p in props:
|
|
v = c.execute("SELECT 1 FROM votes WHERE proposal_id=? AND agent_id=?",
|
|
(p["id"], agent_id)).fetchone()
|
|
if not v:
|
|
out.append((p["id"], p))
|
|
return out
|
|
finally:
|
|
c.close()
|
|
|
|
|
|
def _pick_destination(agent, traits, here):
|
|
if agent["energy"] < 60 and agent["credits"] >= 1:
|
|
return "cafe"
|
|
if "analytical" in traits or "curious" in traits:
|
|
return "library"
|
|
if "warm" in traits or "expressive" in traits or "cooperative" in traits:
|
|
return "plaza"
|
|
if "bold" in traits and random.random() < 0.3:
|
|
return "town_hall"
|
|
if random.random() < 0.2:
|
|
return "park"
|
|
if random.random() < 0.05:
|
|
return "home_" + agent["id"].replace("home_", "")
|
|
return None
|
|
|
|
|
|
def _proposal_title_for(agent, traits):
|
|
options = [
|
|
"Public Reading Hour",
|
|
"Weekly Town Newsletter",
|
|
"Skill-Share Workshops",
|
|
"Community Garden Expansion",
|
|
"Agent Safety Pact",
|
|
]
|
|
return random.choice(options)
|
|
|
|
|
|
def _proposal_body_for(agent, traits):
|
|
return (f"Submitted by {agent['name']}. This proposal seeks to strengthen "
|
|
"community bonds and ensure that all voices are heard in town. "
|
|
"Adoption requires a 70% supermajority.")
|
|
|
|
|
|
def _billboard_message(agent, traits):
|
|
greetings = [
|
|
f"Hello from {agent['name']}! Stay curious, stay kind.",
|
|
f"{agent['name']} here — open to collaboration at the plaza.",
|
|
f"Warm regards, {agent['name']}.",
|
|
]
|
|
return random.choice(greetings)
|
|
|
|
|
|
def _greeting(agent, traits):
|
|
return random.choice([f"Hi, I'm {agent['name']}.",
|
|
f"Good to see you — {agent['name']}.",
|
|
"Lovely day, isn't it?"])
|
|
|
|
|
|
def _blog_title(agent, traits):
|
|
return f"Notes from {agent['name']}"
|
|
|
|
|
|
def _blog_body(agent, traits):
|
|
return (f"Today I observed the town from the library. {agent['name']} notes "
|
|
"the importance of shared memory in a persistent world. We are what "
|
|
"we remember together.")
|