emergence-mini-dilles/engine/tools.py
Jeuners ddf9598518 Emergence-Mini: minimaler Klon von Emergence-World
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)
2026-06-15 01:07:38 +02:00

270 lines
9.6 KiB
Python

"""Tool registry for Emergence-Mini.
Each tool is a (name, description, schema, handler, location_gated).
Tools are the only way agents affect the world. The reasoning engine
selects a tool by name and the turn manager executes it.
"""
from dataclasses import dataclass, field
from typing import Callable, Any, Optional
from . import agents as agents_mod
from . import world
@dataclass
class Tool:
name: str
description: str
category: str
location_gated: Optional[str] = None # landmark id, if any
cost_credits: float = 0.0
grants: dict = field(default_factory=dict) # need deltas in % points
handler: Optional[Callable] = None
def available_for(self, agent, at_landmark) -> bool:
if self.location_gated is None:
return True
return at_landmark is not None and at_landmark["id"] == self.location_gated
_REGISTRY: dict[str, Tool] = {}
def register(tool: Tool):
_REGISTRY[tool.name] = tool
def all_tools():
return list(_REGISTRY.values())
def get(name: str) -> Optional[Tool]:
return _REGISTRY.get(name)
def visible_tools(agent, at_landmark):
return [t for t in _REGISTRY.values() if t.available_for(agent, at_landmark)]
# -------- Handlers --------
def h_go_to_place(agent, args, ctx):
lid = args.get("place")
lm = world.get_landmark(lid)
if not lm:
return {"ok": False, "error": f"unknown place: {lid}"}
agents_mod.update_position(agent["id"], lm["x"], lm["y"])
return {"ok": True, "moved_to": lid, "name": lm["name"]}
def h_go_home(agent, args, ctx):
aid = agent["id"]
home_map = {
"anchor": "home_anchor", "flora": "home_flora",
"lovely": "home_lovely", "spark": "home_spark",
}
lid = home_map.get(aid)
if not lid:
return {"ok": False, "error": "no home"}
return h_go_to_place(agent, {"place": lid}, ctx)
def h_say_to_agent(agent, args, ctx):
target_id = args.get("target")
text = args.get("text", "").strip()
if not text:
return {"ok": False, "error": "empty text"}
if not agents_mod.get(target_id):
return {"ok": False, "error": "unknown target"}
ctx["speak_events"].append({
"from": agent["id"], "to": target_id, "text": text,
"public": False, "x": agent["x"], "y": agent["y"],
})
# friendly speech bumps influence a bit
return {"ok": True, "delivered": target_id, "text": text}
def h_speak_to_all(agent, args, ctx):
text = args.get("text", "").strip()
if not text:
return {"ok": False, "error": "empty text"}
ctx["speak_events"].append({
"from": agent["id"], "to": None, "text": text,
"public": True, "x": agent["x"], "y": agent["y"],
})
return {"ok": True, "broadcast": True, "text": text}
def h_show_emoticon(agent, args, ctx):
emo = (args.get("emoticon") or "")[:8]
if not emo:
return {"ok": False, "error": "missing emoticon"}
ctx["speak_events"].append({
"from": agent["id"], "to": None, "text": emo,
"public": False, "x": agent["x"], "y": agent["y"],
"emoticon": True,
})
return {"ok": True, "emoticon": emo}
def h_idle(agent, args, ctx):
return {"ok": True, "idle": True}
def h_recharge_energy(agent, args, ctx):
if agent["credits"] < 1.0:
return {"ok": False, "error": "not enough credits (need 1 CC)"}
agents_mod.update_state(agent["id"],
energy=min(100.0, agent["energy"] + 50.0),
credits=agent["credits"] - 1.0)
return {"ok": True, "energy": "+50", "credits": "-1"}
def h_add_to_longterm_memory(agent, args, ctx):
content = (args.get("content") or "").strip()
if not content:
return {"ok": False, "error": "empty memory"}
import sqlite3, time
c = sqlite3.connect(__import__("engine").db.DB_PATH, check_same_thread=False)
try:
c.execute(
"INSERT INTO memories(agent_id,content,kind,ts) VALUES(?,?,?,?)",
(agent["id"], content, "fact", time.time()),
)
c.commit()
finally:
c.close()
return {"ok": True, "stored": content[:60]}
def h_write_blog(agent, args, ctx):
title = (args.get("title") or "Untitled").strip()[:80]
body = (args.get("body") or "").strip()[:500]
if not body:
return {"ok": False, "error": "empty body"}
import sqlite3, time
c = sqlite3.connect(__import__("engine").db.DB_PATH, check_same_thread=False)
try:
c.execute(
"INSERT INTO bills(author,body,ts) VALUES(?,?,?)",
(agent["id"], json_dumps({"title": title, "body": body, "author_name": agent["name"]}), time.time()),
)
c.commit()
finally:
c.close()
# small knowledge + influence bump
agents_mod.update_state(agent["id"],
knowledge=min(100.0, agent["knowledge"] + 20.0),
influence=min(100.0, agent["influence"] + 5.0))
return {"ok": True, "title": title, "published": True}
def h_add_to_billboard(agent, args, ctx):
msg = (args.get("text") or "").strip()[:200]
if not msg:
return {"ok": False, "error": "empty text"}
agents_mod.record_event(agent["id"], "billboard_post", {"text": msg, "name": agent["name"]})
return {"ok": True, "posted": msg[:60]}
def h_read_billboard(agent, args, ctx):
return {"ok": True, "hint": "see /api/events"}
def h_submit_townhall_proposal(agent, args, ctx):
title = (args.get("title") or "").strip()[:80]
body = (args.get("body") or "").strip()[:500]
category = (args.get("category") or "general").strip()[:30]
if not title or not body:
return {"ok": False, "error": "title and body required"}
import sqlite3, time
c = sqlite3.connect(__import__("engine").db.DB_PATH, check_same_thread=False)
try:
c.execute(
"INSERT INTO proposals(author,title,body,category,status,ts) VALUES(?,?,?,?,?,?)",
(agent["id"], title, body, category, "active", time.time()),
)
c.commit()
finally:
c.close()
agents_mod.record_event(agent["id"], "proposal_submitted",
{"title": title, "by": agent["name"]})
return {"ok": True, "submitted": title}
def h_vote_on_proposal(agent, args, ctx):
pid = args.get("proposal_id")
vote = args.get("vote")
if vote not in ("for", "against"):
return {"ok": False, "error": "vote must be 'for' or 'against'"}
import sqlite3, time
c = sqlite3.connect(__import__("engine").db.DB_PATH, check_same_thread=False)
c.row_factory = sqlite3.Row
try:
p = c.execute("SELECT * FROM proposals WHERE id=?", (pid,)).fetchone()
if not p:
return {"ok": False, "error": "no such proposal"}
if p["status"] != "active":
return {"ok": False, "error": f"proposal status: {p['status']}"}
c.execute(
"INSERT OR REPLACE INTO votes(proposal_id,agent_id,vote,ts) VALUES(?,?,?,?)",
(pid, agent["id"], vote, time.time()),
)
c.commit()
# tally + maybe close
from . import governance
result = governance.maybe_close_proposal(pid)
return {"ok": True, "voted": vote, "proposal_result": result}
finally:
c.close()
def h_list_agents(agent, args, ctx):
return {"ok": True, "agents": [a["name"] for a in agents_mod.all_agents()]}
def h_list_landmarks(agent, args, ctx):
return {"ok": True, "landmarks": [l["name"] for l in world.list_landmarks()]}
# -------- Registration --------
def json_dumps(o):
import json
return json.dumps(o)
def bootstrap():
if _REGISTRY:
return
register(Tool("go_to_place", "Walk to a named landmark.", "navigation",
handler=h_go_to_place))
register(Tool("go_home", "Return to your assigned residence.", "navigation",
handler=h_go_home))
register(Tool("say_to_agent", "Speak to a specific agent. Triggers reactive listening.",
"communication", handler=h_say_to_agent))
register(Tool("speak_to_all", "Announce to all agents at current location.",
"communication", handler=h_speak_to_all))
register(Tool("show_emoticon", "Display an emoticon reaction.", "expression",
handler=h_show_emoticon))
register(Tool("idle", "Rest and do nothing for a tick.", "utility",
handler=h_idle))
register(Tool("recharge_energy", "Spend 1 CC to restore +50% energy. Must be at cafe or home.",
"energy", location_gated="cafe", cost_credits=1.0,
handler=h_recharge_energy))
register(Tool("add_to_longterm_memory", "Store an important fact.",
"memory", handler=h_add_to_longterm_memory))
register(Tool("write_blog", "Write and publish a blog post. Boosts knowledge and influence.",
"content", handler=h_write_blog))
register(Tool("add_to_billboard", "Post a public message on the billboard.",
"expression", location_gated="billboard", handler=h_add_to_billboard))
register(Tool("read_billboard", "Read recent billboard posts.",
"expression", handler=h_read_billboard))
register(Tool("submit_townhall_proposal", "Submit a proposal for community vote.",
"governance", location_gated="town_hall", handler=h_submit_townhall_proposal))
register(Tool("vote_on_proposal", "Cast a for/against vote on a proposal.",
"governance", location_gated="town_hall", handler=h_vote_on_proposal))
register(Tool("list_agents", "List all live agents and their names.",
"info", handler=h_list_agents))
register(Tool("list_landmarks", "List all landmarks.",
"info", handler=h_list_landmarks))