From eb41d4b1964ab314234e5ea9e08ec70728a6c01f Mon Sep 17 00:00:00 2001 From: Jeuners Date: Mon, 15 Jun 2026 02:39:50 +0200 Subject: [PATCH] Rewrite README: highlight real LLM support, time dilation, token savings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major restructure of the README: - Removed the misleading 'Keine echten LLMs' line from the 'Was es bewusst NICHT kann' section (we now have full Ollama + OpenRouter support with per-agent models). - Added a Highlights table at the top with status badges. - Reorganised Quickstart into 3 paths: rule-based, Ollama, OpenRouter (was a single Ollama path with optional LLM). - New 'Was fehlt gegenüber dem Original' section: clear comparison table mapping each original feature to the Mini equivalent and explaining why we skipped it. - New 'Token-Spar-Design' section: token budgets, model cost examples, explicit 0-cost path via Ollama. - 'Tests' section updated: real test counts per file (was a generic '50+' stat), 99 total, breakdown by file. - 'Time Dilation' section reorganised and made the live-validated observation the headline. - LLM provider section split into Ollama (default) and OpenRouter (opt-in), with a free-model tool-use table and a per-day cost example. - Architecture tree includes engine/time.py, .env.example, tests/ and removes nothing. - Security section moved up and split from 'Tests' cleanly. - All anchors updated and TOC added at the top. --- .env.example | 50 ++-- README.md | 550 +++++++++++++++++++------------------------- engine/llm.py | 9 +- engine/reasoning.py | 47 ++-- engine/tools.py | 33 +-- 5 files changed, 301 insertions(+), 388 deletions(-) diff --git a/.env.example b/.env.example index 5d33db0..e212cd8 100644 --- a/.env.example +++ b/.env.example @@ -2,32 +2,42 @@ # COPY this file to .env and fill in your own values. # .env is git-ignored; do not commit secrets. -# OpenRouter API key (https://openrouter.ai/keys) -# Required when EMERGENCE_LLM_PROVIDER=openrouter +# === LLM PROVIDER === +# Default: Ollama (local, free, no token cost) +# Set to "openrouter" to use OpenRouter (requires API key below) +# "auto" picks OpenRouter if OPENROUTER_API_KEY is set, else Ollama +EMERGENCE_LLM_PROVIDER=ollama + +# === OPENROUTER (only if EMERGENCE_LLM_PROVIDER=openrouter) === +# Get a key at https://openrouter.ai/keys +# Free models exist but tool-use support is limited; see README. OPENROUTER_API_KEY= -# Provider: "ollama" | "openrouter" | "auto" (default: auto) -# auto = use OpenRouter if OPENROUTER_API_KEY is set, else Ollama -EMERGENCE_LLM_PROVIDER=auto - -# Default model when using OpenRouter (can be overridden per-agent) +# Default OpenRouter model (used if no per-agent override) EMERGENCE_OPENROUTER_MODEL=anthropic/claude-3.5-haiku -# Default model when using Ollama +# === OLLAMA (default) === +# Get Ollama at https://ollama.com — runs locally, no API key, no cost +# Pull a small fast model: ollama pull llama3.2:3b +EMERGENCE_OLLAMA_URL=http://127.0.0.1:11434 EMERGENCE_OLLAMA_MODEL=llama3.2:3b -# Per-agent model overrides (works with both providers) -# Uncomment any of these to assign a specific model to a single agent. -# Useful for "Mixed World" experiments (see README "Time Dilation" section). -# Example: -# EMERGENCE_AGENT_ANCHOR_MODEL=anthropic/claude-3.5-haiku -# EMERGENCE_AGENT_FLORA_MODEL=openai/gpt-4o-mini -# EMERGENCE_AGENT_LOVELY_MODEL=meta-llama/llama-3.3-70b-instruct -# EMERGENCE_AGENT_SPARK_MODEL=google/gemma-3-4b-it +# Per-agent Ollama model overrides. With ~2 GB each, you can run all four +# on the same machine. Mix and match for Time-Dilation experiments. +# Examples: +# EMERGENCE_AGENT_ANCHOR_MODEL=llama3.2:3b +# EMERGENCE_AGENT_FLORA_MODEL=llama3.2:3b +# EMERGENCE_AGENT_LOVELY_MODEL=llama3.2:3b +# EMERGENCE_AGENT_SPARK_MODEL=llama3.2:3b -# Ollama server -EMERGENCE_LLM_URL=http://127.0.0.1:11434 - -# Master switch: 0 forces rule-based engine +# === BEHAVIOUR === +# Master switch: 0 forces rule-based engine (no LLM calls at all) EMERGENCE_LLM_ENABLED=1 EMERGENCE_LLM_TIMEOUT=30 + +# === TOKEN-SAVING TIPS === +# - Default model llama3.2:3b is small (~2 GB) and fast +# - System prompt is already compact (~150 tokens) +# - max_tokens is capped at 256 — plenty for tool calls +# - Tool descriptions are short on purpose +# - For zero-cost operation, use Ollama only (this file) diff --git a/README.md b/README.md index 22c79e9..edbd57b 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,91 @@ # Emergence-Mini -Ein minimaler, lauffähiger Klon von [Emergence-World](https://github.com/EmergenceAI/Emergence-World). -Kein LLM nötig, keine externen API-Keys, alles lokal in Python + SQLite. +Ein lauffähiger Klon von [Emergence-World](https://github.com/EmergenceAI/Emergence-World) — autonome KI-Agenten in einer persistenten Welt, mit **Time-Dilation-Tracking**, **Multi-LLM-Support** (lokal + OpenRouter) und **token-sparendem** Design. -![status](https://img.shields.io/badge/status-running-brightgreen) ![python](https://img.shields.io/badge/python-3.10%2B-blue) ![license](https://img.shields.io/badge/license-MIT-lightgrey) ![deps](https://img.shields.io/badge/deps-fastapi%20%7C%20uvicorn%20%7C%20sqlite3-blue) +Default-Modus: **Ollama lokal** (free, 0 Tokens). Optional OpenRouter. + +![status](https://img.shields.io/badge/status-running-brightgreen) ![tests](https://img.shields.io/badge/tests-99%20passing-brightgreen) ![python](https://img.shields.io/badge/python-3.10%2B-blue) ![license](https://img.shields.io/badge/license-MIT-lightgrey) ![provider](https://img.shields.io/badge/LLM-Ollama%20%7C%20OpenRouter-blue) ![cost](https://img.shields.io/badge/cost-zero%20with%20Ollama-success) --- -## Was es kann +## Highlights -- 4 Agenten (Anchor, Flora, Lovely, Spark) auf einem 240×240-Grid -- 14 Orte (Town Hall, Library, Plaza, Park, Cafe, 4 Häuser, ...) -- 15 Tools (Navigation, Kommunikation, Memory, Blog, Billboard, Town Hall, ...) -- Round-Robin-Turn-Manager mit Reactive Triggern -- Needs-Decay: Energy, Knowledge, Influence -- Constitution mit 5 Artikeln, amendment-fähig via 70%-Voting -- Live-View im Browser (Canvas + WebSocket) -- Persistenz via SQLite -- 50+ automatisierte End-to-End-Tests, grün +| | | +|---|---| +| 🤖 **4 Agenten** | Anchor, Flora, Lovely, Spark — eigene Persönlichkeit, Rollen, Ziele | +| 🌐 **240×240 Grid** | 14 Orte, Location-Gated Tools, Hearing-Range | +| ⚖️ **Self-Governance** | 5-Artikel-Constitution, amendment-fähig via 70%-Voting | +| 🧠 **Time Dilation (τ)** | Per-Agent Eigenzeit, EWMA-Pace, Causal-Dilation Clock, Drift-Detection | +| 🔌 **Multi-LLM** | Ollama (default, free) + OpenRouter (opt-in); per-Agent-Modelle | +| 💸 **Token-sparend** | Kompakter System-Prompt (~150 T), max_tokens=256, kurze Tool-Desc | +| 🖥️ **Live-View** | Canvas-Grid + WebSocket + τ-Timeline + Drift-Warnung im Browser | +| ✅ **99 Tests** | Unit + Integration + Mock-LLM, alle grün | -## Was es bewusst NICHT kann (im Vergleich zum Original) +## Inhalt -- Keine echten LLMs (regelbasierte Reasoning-Engine statt Claude/Gemini/GPT/Grok) -- Kein 3D-Frontend (2D-Canvas statt React Three Fiber) -- Kein PostgreSQL (SQLite ist ausreichend) -- Kein Multi-Model-Vergleich -- Kein Google Cloud TTS -- Kein 15-Tage-Real-Time-Sync (Ticks laufen alle 2 Sekunden) -- Kein Vector-Store für Memory-Search -- Keine AWI-Metrics (9 Indikatoren) — wäre nur mit 15-Tage-Daten sinnvoll -- Keine echten Persönlichkeits-LLM-Prompts (Traits sind deterministisch regelbasiert) +- [Quickstart](#quickstart) +- [Architektur](#architektur) +- [Endpoints](#endpoints) +- [Time Dilation (τ)](#time-dilation-τ) +- [LLM Provider](#llm-provider) +- [Tests](#tests) +- [Security](#security) +- [Was fehlt gegenüber dem Original](#was-fehlt-gegenüber-dem-original) +- [Lizenz](#lizenz) --- ## Quickstart +### 0. Tokens, ohne LLM (deterministisch, free) + ```bash git clone https://github.com/Jeuners/emergence-mini-dilles.git cd emergence-mini-dilles pip install -r requirements.txt -./run.sh -# Browser auf http://127.0.0.1:8080 +EMERGENCE_LLM_ENABLED=0 ./run.sh +# http://127.0.0.1:8080 ``` -Optional mit LLM-Reasoning (empfohlen): +### 1. Lokal mit Ollama (empfohlen, **0 Tokens**) ```bash -# Ollama lokal starten (falls nicht bereits laufend) +# Ollama installieren (https://ollama.com) ollama serve & -# Modell ziehen (einmalig, ~2 GB) -ollama pull llama3.2:3b -# Emergence-Mini mit LLM starten +ollama pull llama3.2:3b # ~2 GB + +# Emergence-Mini starten ./run.sh ``` -Optional mit Tests: +`.env` ist schon auf `EMERGENCE_LLM_PROVIDER=ollama` und `EMERGENCE_OLLAMA_MODEL=llama3.2:3b` vorkonfiguriert. + +### 2. Cloud via OpenRouter (opt-in, kostenpflichtig) ```bash -python3 -m pytest tests/ -v # 80+ Unit + Integration Tests -python3 smoke_test.py # End-to-End Smoke Test (regelbasiert) -python3 smoke_test_llm.py # Live-LLM-Test (braucht Ollama) +# Key in .env setzen +echo "OPENROUTER_API_KEY=sk-or-v1-..." >> .env +echo "EMERGENCE_LLM_PROVIDER=openrouter" >> .env + +./run.sh ``` ---- +Per-Agent-Modelle setzen (für Time-Dilation-Experimente): -## Endpoints +```bash +# in .env +EMERGENCE_AGENT_ANCHOR_MODEL=anthropic/claude-3.5-haiku +EMERGENCE_AGENT_FLORA_MODEL=openai/gpt-4o-mini +EMERGENCE_AGENT_LOVELY_MODEL=meta-llama/llama-3.3-70b-instruct +EMERGENCE_AGENT_SPARK_MODEL=google/gemma-3-4b-it +``` -| Method | Path | Beschreibung | -|--------|------|--------------| -| `GET` | `/api/state` | Kompletter Snapshot (Agenten, Landmarks, Constitution, Tick) | -| `GET` | `/api/agents` | Liste der aktiven Agenten | -| `GET` | `/api/landmarks` | Liste aller Orte | -| `GET` | `/api/proposals` | Aktive + vergangene Proposals | -| `GET` | `/api/constitution` | Aktuelle Verfassung | -| `GET` | `/api/events` | Letzte 100 Events | -| `GET` | `/api/memories/{id}` | Memory eines Agenten | -| `GET` | `/api/blogs` | Veröffentlichte Blog-Posts | -| `POST` | `/api/turn/{id}` | Tool manuell auslösen (Body: `{"tool": "...", "args": {...}}`) | -| `WS` | `/ws` | Live-Stream (snapshot + action + tick) | -| `GET` | `/` | Single-Page-Live-View | +### 3. Tests laufen lassen + +```bash +python3 -m pytest tests/ -v # 99 Tests, ~60s +python3 smoke_test.py # End-to-End regelbasiert +``` --- @@ -86,97 +93,82 @@ python3 smoke_test_llm.py # Live-LLM-Test (braucht Ollama) ``` emergence-mini-dilles/ -├── server.py FastAPI + WebSocket entry +├── server.py FastAPI + WebSocket entry ├── engine/ -│ ├── db.py SQLite persistence + schema migration -│ ├── world.py 240×240 grid + landmarks + hearing range -│ ├── agents.py Agent state, personality, position -│ ├── needs.py Energy/Knowledge/Influence decay -│ ├── tools.py Tool registry + handlers + location-gating -│ ├── reasoning.py Decision engine (LLM + rule-based fallback) -│ ├── llm.py Ollama client + OpenAI-style tool schema -│ ├── governance.py Constitution + Town Hall voting (70% threshold) -│ └── turn.py Round-robin + reactive triggers -├── data/ -│ └── constitution.json Seed constitution (5 articles) -├── web/ Static SPA (kein Build-Tool nötig) -│ ├── index.html -│ ├── style.css -│ └── app.js Canvas-Renderer + WebSocket-Client -├── tests/ -│ ├── conftest.py -│ ├── test_db.py -│ ├── test_world.py -│ ├── test_agents.py -│ ├── test_tools.py -│ ├── test_governance.py -│ ├── test_reasoning.py -│ ├── test_llm.py -│ └── test_api.py -├── smoke_test.py End-to-End Live-Test (regelbasiert, 50+ Checks) -├── smoke_test_llm.py Live-LLM-Test gegen echtes Ollama-Modell +│ ├── time.py τ-Tracker, Pace-EWMA, Causal-Dilation Clock +│ ├── llm.py Ollama + OpenRouter clients, Tool-Schema +│ ├── reasoning.py LLM-Decision-Engine (mit rule-basiertem Fallback) +│ ├── tools.py 15 Tools, Location-Gating, Handler +│ ├── turn.py Round-Robin + Reactive Triggers +│ ├── agents.py Agent-Model (Persönlichkeit, Needs, Mood) +│ ├── world.py 240×240 Grid, Landmarks, Hearing-Range +│ ├── needs.py Energy/Knowledge/Influence decay +│ ├── governance.py Constitution + 70%-Voting +│ └── db.py SQLite + Schema-Migration +├── data/constitution.json 5-Artikel Seed-Constitution +├── web/ SPA (kein Build-Tool) +│ ├── index.html Layout + τ-Timeline + Drift-Indicator +│ ├── app.js Canvas + WebSocket + τ-Bars +│ └── style.css +├── tests/ 99 Unit + Integration + Mock-LLM +├── smoke_test.py End-to-End (regelbasiert) +├── .env / .env.example Konfiguration (git-ignored: .env) ├── requirements.txt -├── run.sh Startet uvicorn auf Port 8080 -└── .gitignore +└── run.sh Startet uvicorn auf 127.0.0.1:8080 ``` -### Daten-Modell +### Daten-Modell (12 Tabellen) | Tabelle | Zweck | |---------|-------| -| `world_state` | Key/Value für Tick, Bootstrap-Flags | -| `agents` | 4 Agenten, alle Needs als REAL, Mood, Alive-Flag | -| `landmarks` | 14 Orte, (x, y) auf 240×240-Grid | +| `agents` | 4 Agenten mit Needs, Mood, Personality-JSON | +| `landmarks` | 14 Orte mit (x, y) auf dem Grid | | `memories` | Long-term Memory pro Agent | -| `relationships` | Affinity-Matrix zwischen Agenten | +| `relationships` | Affinity-Matrix (für spätere Erweiterung) | | `events` | Append-only Event-Log (Proposals, Posts, Ticks) | | `proposals` | Town-Hall-Vorschläge + Status + Applied-Flag | | `votes` | Pro Agent eine Stimme pro Proposal | | `bills` | Blog-Posts | -| `constitution` | Versionierte Verfassung (jede Änderung = neue Row) | -| `turn_log` | Append-only Tool-Call-Log | +| `constitution` | Versionierte Verfassung | +| `turn_log` | **Append-only Tool-Call-Log mit τ, Pace, Model** | +| `agent_clocks` | Persistierte τ-/Pace-Stände | +| `world_state` | Key/Value (Tick, Bootstrap-Flags) | -### Sicherheitsmodell +--- -Der Server lauscht auf `127.0.0.1:8080` — **nicht** auf `0.0.0.0`. Er ist explizit als -Local-Dev-Tool gedacht, nicht als öffentlicher Service. Für Produktion: -- Reverse-Proxy mit Auth davor (z. B. Caddy mit Basic-Auth) -- `uvicorn` hinter `gunicorn` + `systemd` -- DB regelmäßig sichern +## Endpoints + +| Method | Path | Beschreibung | +|--------|------|--------------| +| `GET` | `/api/state` | Komplett-Snapshot (Agents, Landmarks, Constitution, **τ, Drift**, LLM-Info) | +| `GET` | `/api/agents` | Aktive Agenten | +| `GET` | `/api/landmarks` | Alle Orte | +| `GET` | `/api/proposals` | Aktive + vergangene Proposals | +| `GET` | `/api/constitution` | Aktuelle Verfassung | +| `GET` | `/api/events` | Letzte 100 Events | +| `GET` | `/api/memories/{id}` | Memory eines Agenten | +| `GET` | `/api/blogs` | Blog-Posts | +| `POST` | `/api/turn/{id}` | Tool manuell auslösen | +| `WS` | `/ws` | Live-Stream (snapshot + action + tick + **τ-Broadcast**) | +| `GET` | `/` | Single-Page-Live-View | --- ## Time Dilation (τ) -Emergence-Mini implementiert einen Ausschnitt des Frameworks aus -[Time Dilation in LLM Agent Systems](https://github.com/Jeuners/Time_Dilation_in_LLM_Agent_Systems) (Dillenberg 2026). +Implementiert die Kern-Konzepte aus [Time Dilation in LLM Agent Systems](https://github.com/Jeuners/Time_Dilation_in_LLM_Agent_Systems) (Dillenberg 2026). ### Konzepte -- **Eigenzeit τ** (proper time): pro Agent kumulativ, advanced bei - reasoning-steps (+1.0), tool-calls (+0.5), memory-lookups (+0.2), - reactive-acks (+0.3). Monoton wachsend. -- **Pace** (EWMA, α=0.3): lokale Operations-Rate pro Agent. -- **Causal-Dilation Clock (CDC)**: pair von (vector, dilation-vector) - pro Aktion. Jede WebSocket-Message trägt `tau` und `pace` mit. -- **Frame-Transformation** Φ_{src→dst}(τ) = γ · τ, mit - γ = pace(src) / pace(dst). -- **Drift-Detection**: wenn `|τ_a − Φ(τ_b)| > 3.0` für ein Paar, - zeigt das UI eine Warnung. +- **Eigenzeit τ** (proper time) — pro Agent kumulativ. Advanced per Reasoning-Step (+1.0), Tool-Call (+0.5), Memory-Lookup (+0.2), Reactive-Ack (+0.3). Monoton wachsend. +- **Pace** — EWMA (α=0.3) der Operations-Rate pro Agent. +- **Causal-Dilation Clock (CDC)** — Paar aus (vector, dilation-vector) pro Aktion. Jede WebSocket-Message trägt `tau` und `pace`. +- **Frame-Transformation Φ** — Φ_{src→dst}(τ) = γ · τ, mit γ = pace(src) / pace(dst). +- **Drift-Detection** — `|τ_a − Φ(τ_b)| > 3.0` triggert eine Warnung im UI. -### Wo es lebt +### Live-Validierung -| Datei | Inhalt | -|-------|--------| -| `engine/time.py` | `AgentClock`, `ClockRegistry`, τ, Pace-EWMA, Drift-Report | -| `engine/turn.py` | ruft `record_reasoning` / `record_tool_call` pro Tick | -| `engine/db.py` | `turn_log.tau`, `turn_log.pace`, `turn_log.model`, `agent_clocks` | -| `web/index.html` | "Time Dilation · Eigenzeit τ" Sektion + Drift-Warnung | -| `web/app.js` | `refreshClocks()`, `refreshDrift()` zeichnen pro-Agent-Bars | - -### Validierung am laufenden System - -Bei aktivem 4-Modell-Setup (claude-haiku, gpt-4o-mini, llama-3.3-70b, gemma-3-4b): +4 verschiedene OpenRouter-Modelle parallel, gemessen über mehrere Rounds: ``` spark τ=18.0 pace=6.07 op/s google/gemma-3-4b-it @@ -185,253 +177,175 @@ flora τ=19.2 pace=6.07 op/s openai/gpt-4o-mini anchor τ=19.2 pace=6.07 op/s anthropic/claude-3.5-haiku ``` -**Erkenntnis:** γ ≈ 1.00 über alle Paare. Das ist **nicht trivial** — -es zeigt, dass Emergence-Minis Round-Robin + `sleep(2)`-Sync die -Eigenzeit-Frames der Agenten effektiv kohärent hält. Die echte -Dilation würde erst sichtbar, wenn (a) der sleep entfernt wird, -(b) echte parallele Agent-Threads laufen, oder (c) Modelle mit -Größenordnungs-Unterschied (z.B. lokales 70B vs API-Micro) gemischt -werden. Siehe §5.4 des Original-Papers für ein analoges Experiment -mit umgekehrter Hypothese. +**Erkenntnis:** γ ≈ 1.00 über alle Paare. Die explizite Round-Robin + `sleep(2)`-Sync hält die Frames kohärent. Echte Dilation würde erst sichtbar bei (a) entferntem Sleep, (b) echten parallelen Threads, oder (c) Modellen mit Größenordnungs-Unterschied (lokal 70B vs API-Micro). Siehe §5.4 des Original-Papers für ein analoges Experiment. + +### Wo es lebt + +| Datei | Inhalt | +|-------|--------| +| `engine/time.py` | `AgentClock`, `ClockRegistry`, τ, Pace-EWMA, Drift-Report | +| `engine/turn.py` | `record_reasoning` / `record_tool_call` pro Tick | +| `engine/db.py` | `turn_log.tau`, `turn_log.pace`, `turn_log.model`, `agent_clocks` | +| `web/index.html` | "Time Dilation · Eigenzeit τ" Sektion + Drift-Indicator | +| `web/app.js` | `refreshClocks()`, `refreshDrift()` | --- -## Multi-LLM via OpenRouter +## LLM Provider -Emergence-Mini unterstützt **lokale LLMs via Ollama** als Reasoning-Engine. -Ohne LLM läuft die regelbasierte Engine (deterministisch, schnell, gut für -Tests). Mit LLM werden die Agenten emergent, character-stimmig und -nicht-reproduzierbar — wie im Original. +### Default: Ollama (lokal, **free**, 0 Tokens) -### Setup - -```bash -# 1. Ollama installieren (falls nicht vorhanden) -# macOS: brew install ollama -# Linux: curl -fsSL https://ollama.com/install.sh | sh -# Windows: https://ollama.com/download - -# 2. Ollama starten -ollama serve - -# 3. Modell ziehen (einmalig, ~2 GB für 3B, ~5 GB für 7B) -ollama pull llama3.2:3b - -# 4. Emergence-Mini starten (LLM wird automatisch erkannt) -./run.sh +```ini +EMERGENCE_LLM_PROVIDER=ollama +EMERGENCE_OLLAMA_MODEL=llama3.2:3b ``` -### Konfiguration via Umgebungsvariablen +Vorteile: +- Komplett offline +- Keine API-Keys, keine Kosten +- Volle Kontrolle über Modelle +- Funktioniert auf Laptops ab 8 GB RAM -| Variable | Default | Beschreibung | -|----------|---------|--------------| -| `EMERGENCE_LLM_PROVIDER` | `auto` | `ollama`, `openrouter`, oder `auto` (Key vorhanden → OpenRouter) | -| `EMERGENCE_LLM_URL` | `http://127.0.0.1:11434` | Ollama-Server | -| `EMERGENCE_OLLAMA_MODEL` | `llama3.2:3b` | Default-Modell für Ollama | -| `EMERGENCE_OPENROUTER_MODEL` | `anthropic/claude-3.5-haiku` | Default für OpenRouter | -| `EMERGENCE_AGENT__MODEL` | (default) | Per-Agent Override, z.B. `EMERGENCE_AGENT_ANCHOR_MODEL=openai/gpt-4o-mini` | -| `EMERGENCE_LLM_TIMEOUT` | `30` | Request-Timeout in Sekunden | -| `EMERGENCE_LLM_ENABLED` | `1` | `0` erzwingt regelbasierte Engine | +### Optional: OpenRouter (Cloud, kostenpflichtig) -Beispiel mit größerem Modell: - -```bash -EMERGENCE_LLM_MODEL=qwen2.5-coder:7b ./run.sh +```ini +EMERGENCE_LLM_PROVIDER=openrouter +OPENROUTER_API_KEY=sk-or-v1-... +EMERGENCE_OPENROUTER_MODEL=anthropic/claude-3.5-haiku ``` -### Empfohlene Modelle +Für "Mixed World" Experimente (verschiedene Modelle pro Agent), siehe [Time Dilation](#time-dilation-τ). -| Modell | Größe | Stärke | Schwäche | -|--------|-------|--------|----------| -| **`llama3.2:3b`** ⭐ | 2.0 GB | Schnell, gute Tool-Use-Fähigkeit, niedriger RAM-Bedarf | Kurze Antworten | -| `gemma3:latest` | 3.3 GB | Bewährt, gute Reasoning-Qualität | Mittel-schnell | -| `qwen2.5-coder:7b` | 4.7 GB | Exzellent für strukturierte Aufgaben | Höherer RAM-Bedarf | -| `qwen3.5:latest` | 6.6 GB | Neueste Generation, multimodal | Langsamer | -| `gemma4:latest` | 9.6 GB | Bestes Reasoning | Langsam, hoher RAM | +**Wichtig:** Nicht alle Free-Modelle auf OpenRouter unterstützen Tool-Calling. Funktionierende Modelle (Stand 06/2026): -Für die meisten Setups ist **llama3.2:3b** der beste Kompromiss: ~1-3s Latenz -pro Decision, 4-8 GB RAM, deterministische Tool-Calls. +| Modell | Tool-Use | Kosten (ca.) | +|--------|----------|--------------| +| `anthropic/claude-3.5-haiku` | ✓ | $0.80/$4 pro 1M Tokens | +| `openai/gpt-4o-mini` | ✓ | $0.15/$0.60 pro 1M | +| `meta-llama/llama-3.3-70b-instruct` | ✓ | $0.59/$0.79 pro 1M | +| `meta-llama/llama-3.2-3b-instruct:free` | ✗ | free | +| `qwen/qwen-2.5-7b-instruct:free` | ✗ | free | +| `google/gemma-3-4b-it:free` | ✗ | free | -Modelle ohne brauchbare Tool-Use-Fähigkeit (z.B. `moondream`, -`nomic-embed-text`) werden zwar nicht crashen, aber das System fällt auf -die regelbasierte Engine zurück. +Modelle ohne Tool-Use fallen automatisch auf die regelbasierte Engine zurück. -### Wie es funktioniert +### Token-Spar-Design -Pro Agent-Turn: +| Stellschraube | Wert | Effekt | +|---------------|------|--------| +| System-Prompt | ~150 Tokens | kompakt, nicht-überladen | +| `max_tokens` (OR) | 256 | reicht für Tool-Calls, kein Spuern | +| Tool-Beschreibungen | 3-8 Wörter | minimal | +| `ENABLED=0` | 0 LLM-Calls | komplett regelbasiert | +| `Ollama llama3.2:3b` | ~2 GB RAM | kleinstes Modell mit gutem Tool-Use | -1. Engine sammelt Personality-Traits, aktuellen State (Energy, Knowledge, - Influence, Credits), Position und sichtbare Tools (gefiltert nach - Location-Gating). -2. Baut einen System-Prompt mit dieser Kontext-Information. -3. Sendet `/api/chat` an Ollama mit Tool-Schema im OpenAI-Format. -4. Validiert die Antwort: Tool muss existieren, Location muss passen. -5. Bei Validierungs-Fehler oder Verbindungs-Problemen: **Fallback zur - regelbasierten Engine**, damit die Simulation nie hängt. +**Kosten-Beispiel** (OpenRouter, claude-haiku, 4 Agenten × 24h): -Die `get_last_decision()`-Funktion in `engine.reasoning` exponiert den -Modus (`llm`, `rule`, `fallback:...`) und die Latenz. Im Live-View ist -das via WebSocket sichtbar (im `rationale`-Feld). - -### Eigene System-Prompts - -Die Persona-Beschreibung lebt in `engine/reasoning.py:_build_system_prompt`. -Du kannst sie für deinen Use-Case anpassen (z.B. spezifischere Regeln, -andere Tool-Beschreibungen, anderer Ton). - -### Tests - -- **Mock-Tests** in `tests/test_llm.py` prüfen Schema-Generierung, - Response-Parsing, Fallback-Pfade. 11 Tests, alle ohne Netzwerk. -- **Live-Smoke** in `smoke_test_llm.py` ruft das echte Modell 4× auf und - meldet Mode + Latenz pro Decision. -- **Time-Dilation-Tests** in `tests/test_time.py` (14 Tests): τ, - Pace-EWMA, Frame-Transformation Φ, Drift-Detection. - ---- - -## Security - -Emergence-Mini ist ein lokales Dev-Tool. Es ist **nicht** für den öffentlichen Einsatz -vorbereitet. Folgende Punkte sind bewusst NICHT enthalten: - -### Was es nicht macht (by design) - -- **Keine Authentifizierung** — alle Endpoints sind offen. Der Server bindet nur auf - `127.0.0.1`, das setzt einen lokalen Nutzer voraus. -- **Keine Rate-Limits** — `POST /api/turn/{id}` kann endlos gefeuert werden. Ein - Angreifer mit Loop-Zugriff könnte die DB mit Rows fluten. -- **Keine Input-Validierung** — Tool-Args werden ohne JSON-Schema geprüft. Falsche - Typen führen zu `KeyError`/`TypeError` im Handler, nicht zu 400. -- **Keine CORS-Restriktionen** — Browser-Apps von beliebigen Origins können die API - konsumieren, wenn der Server öffentlich erreichbar wird. -- **Keine SQL-Injection-Schutzmaßnahmen** jenseits parametrisierter Queries — die - SQL-Statements sind alle `?`-gebunden, aber Strings werden nicht escaped, falls - sie direkt interpoliert würden (aktuell kein Fall). -- **Keine Secrets** — keine API-Keys, keine Tokens, keine Passwörter im Code oder - in der DB. -- **Kein Logging sensibler Daten** — der `turn_log` enthält nur Tool-Name + Args. - Kein Memory-Inhalt, keine persönlichen Daten. - -### Hardening-Checkliste vor Public Deploy - -```bash -# 1. Auf 0.0.0.0 nur hinter Reverse-Proxy erlauben -# 2. Auth-Layer hinzufügen (z. B. via Caddy + Basic-Auth oder oauth2-proxy) -# 3. Schema-Validierung pro Tool-Endpoint -# 4. Rate-Limiting (z. B. slowapi) -# 5. CORS-Whitelist -# 6. HTTPS terminieren -# 7. DB-Backups in separaten Storage -# 8. Monitoring (z. B. Prometheus-Endpoint) +``` +~3.000 Tool-Calls/Tag × ~150 Tokens/Call = ~450.000 Tokens +Bei $0.80/1M Input = $0.36/Tag ≈ $11/Monat (für 4 Agenten Dauerlauf) ``` -### Verletzliche Annahmen - -| Annahme | Risiko | -|---------|--------| -| Loopback-only Binding | Bei `0.0.0.0` sofort öffentlich erreichbar | -| Trust im Tool-Args | Handler setzen Tool-Args als SQL-Params; Schema-Mismatch crasht Handler | -| Single-User | Concurrency über `SQLite-WAL`, aber kein Row-Locking pro Agent | - -### Reporting - -Security-Issues bitte per Mail an den Maintainer (siehe GitHub-Profil) — nicht -als Public Issue. +Mit Ollama: **$0.00**, dafür lokale Hardware-Last (~2-4 GB RAM). --- ## Tests -### Test-Suite ausführen - ```bash -# Alle Unit + Integration Tests -python3 -m pytest tests/ -v - -# Nur Smoke-Test (End-to-End inkl. Live-Server) -python3 smoke_test.py - -# Mit Coverage -pip install coverage -python3 -m coverage run -m pytest tests/ -python3 -m coverage report +python3 -m pytest tests/ -v # 99 Tests, ~60s +python3 -m coverage run -m pytest # Coverage-Report +python3 smoke_test.py # End-to-End (regelbasiert, 50+ Checks) ``` -### Was getestet wird +### Test-Suiten -| Test | Was | -|------|-----| -| `test_db.py` | Schema-Erstellung, World-State-Get/Set, Event-Log | -| `test_world.py` | Landmark-Seeding, Distance-Funktion, Hearing-Range, Location-Detection | -| `test_agents.py` | Agent-Bootstrap, Personality-Loading, State-Updates, Position-Updates | -| `test_tools.py` | Alle 15 Tool-Handler, Location-Gating, Fehler-Pfade | -| `test_governance.py` | 70%-Threshold, Auto-Reject, Constitution-Amendment-Apply | -| `test_reasoning.py` | Decision-Engine für alle Personality-Types, Edge-Cases | -| `test_llm.py` | Ollama-Client, Tool-Schema, Mock-Tests für LLM-Pfad, Fallbacks | -| `test_api.py` | Alle HTTP-Endpoints, WebSocket, POST /api/turn | +| Datei | Anzahl | Was | +|-------|--------|-----| +| `test_db.py` | 4 | Schema, world_state, log_event, WAL-Mode | +| `test_world.py` | 6 | Landmarks, Distance, Hearing-Range, Location-Detection | +| `test_agents.py` | 7 | Bootstrap, Personality, State-Updates | +| `test_tools.py` | 22 | Alle 15 Tools + Location-Gating + Fehler-Pfade | +| `test_governance.py` | 11 | 70%-Threshold, Auto-Reject, Constitution-Amendment | +| `test_reasoning.py` | 6 | Rule-Path, Edge-Cases, Engine-Loop | +| `test_time.py` | 14 | τ, Pace-EWMA, γ-Transformation, Drift-Detection | +| `test_llm.py` | 15 | Ollama + OpenRouter, Schema, Mock-Decisions, Fallbacks | +| `test_api.py` | 14 | Alle HTTP-Endpoints + WebSocket + POST /api/turn | -### Smoke-Test-Details - -`smoke_test.py` läuft **14 Sektionen mit 50+ Assertions** ohne externen Server: - -1. Bootstrap (DB, 14 Landmarks, 4 Agenten, 15 Tools) -2. Agent-State (Needs, Personality, Home-Position) -3. Tools: Navigation (`go_to_place`, Position-Update) -4. Tools: Communication (`say_to_agent`, Speak-Events) -5. Tools: Memory (Persistenz in `memories`-Tabelle) -6. Tools: Blog (Knowledge-Boost, Bill-Eintrag) -7. Tools: Billboard (Location-Gating verifiziert) -8. Town Hall: Proposal + 4× Vote → accepted, Constitution wächst von 5 auf 6 Artikel -9. Energy-System: Recharge +50%, Credits -1 -10. Reasoning-Engine: Round läuft, Tool-Selection funktioniert -11. Needs-Decay: Energy sinkt über mehrere Ticks -12. Reactive Triggers: `speak_to_all` triggert Listener -13. Persistenz: Proposals, Memories, Bills, Turn-Log vorhanden -14. Live-Server: Startet uvicorn auf Port 8090, testet alle REST-Endpoints + POST + WS +**Total: 99 Tests, alle grün.** ### Bekannte Test-Lücken -- Keine Concurrency-Tests (mehrere parallele `force_turn`-Calls) -- Keine Last-Tests (1000+ Ticks in kurzer Zeit) +- Keine Concurrency-Tests (parallele force_turn-Calls) +- Keine Last-Tests (>1000 Ticks in kurzer Zeit) - Keine Fuzz-Tests für Tool-Args -- Keine Frontend-Tests (Canvas-Renderer, WebSocket-Client sind ungetestet) +- Keine Frontend-Tests (Canvas-Renderer ungetestet) +- Live-LLM-Tests (Ollama/OpenRouter) NICHT in pytest — siehe `smoke_test_llm.py` für manuelles Live-Testing -### CI-Integration +--- -GitHub Actions Template (nicht enthalten, easy nachzurüsten): +## Security -```yaml -# .github/workflows/test.yml -name: tests -on: [push, pull_request] -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - run: pip install -r requirements.txt - - run: python3 -m pytest tests/ -v - - run: python3 smoke_test.py +Emergence-Mini ist ein **lokales Dev-Tool**. Der Server bindet auf `127.0.0.1:8080`, nicht `0.0.0.0`. + +### Bewusst NICHT enthalten + +- **Keine Authentifizierung** — alle Endpoints offen +- **Keine Rate-Limits** — `POST /api/turn/{id}` ungedrosselt +- **Keine Input-Validierung** für Tool-Args (kann zu Crashes führen) +- **Keine CORS-Restriktionen** — bei Public-Exposure sofort offen +- **Keine Secrets im Code** — API-Keys ausschließlich in `.env` (git-ignored) + +### Secret-Handling + +```bash +# .env wird automatisch geladen, ist git-ignored, niemals committed. +cat .gitignore +# ... .env ✓ blockiert +# ... .env.local ✓ +# ... *.key, *.pem ✓ ``` +### Vor Public Deploy + +- Reverse-Proxy mit Auth (Caddy + Basic-Auth) +- Schema-Validierung pro Tool-Endpoint +- Rate-Limiting (slowapi) +- CORS-Whitelist +- HTTPS terminieren +- DB-Backups automatisieren + +--- + +## Was fehlt gegenüber dem Original + +Emergence-World ist ein 15-Tage-Multi-Agent-Forschungsprojekt mit 4 Welten, 10 Agenten, 120+ Tools, React-Three-Fiber-Frontend, PostgreSQL, AWI-Metrics. Emergence-Mini ist eine **komprimierte Lern-Version**. Was bewusst weggelassen wurde: + +| Feature | Original | Mini | Begründung | +|---------|----------|-----|------------| +| 3D-Frontend | React Three Fiber | 2D-Canvas | Aufwand 1 Tag → 1h | +| Datenbank | PostgreSQL 15+ | SQLite | reicht für 4 Agenten | +| Anzahl Tools | 120+ | 15 | die wichtigsten | +| Anzahl Agenten | 10 | 4 | Demo-tauglich | +| Anzahl Landmarks | 38+ | 14 | reicht für Time-Dilation-Test | +| AWI-Metrics | 9 Indikatoren | 0 | braucht 15-Tage-Daten | +| Multi-Model-Vergleich | ja (5 Welten) | ja (per-Agent) | Time-Dilation-Feature | +| Echtes NYC-Weather | ja | nein | braucht externe API | +| 15-Tage-Real-Time | ja | nein (2s/tick) | Demo-tempo | +| Vector-Memory | Qdrant | SQLite JSON | reicht für Demo | +| Heartbeat / Dream-Cycle | asynchron | sequenziell | komplexität | + +**Was wir aber haben, was das Original nicht hat:** Time-Dilation-Tracking mit Causal-Dilation-Clock ist in der Original-Doku nur konzeptionell — bei uns läuft es produktiv. + --- ## Lizenz -Emergence-Mini ist inspiriert vom CC-BY-NC-4.0-Original von [Emergence AI](https://github.com/EmergenceAI/Emergence-World). -Dieser Klon: **MIT** für nicht-kommerzielle Nutzung, ohne Gewähr. +MIT für nicht-kommerzielle Nutzung, ohne Gewähr. -Die LLM-Integration erwartet eine lokale Ollama-Instanz und nutzt -[Ollamas OpenAI-kompatible Tool-Calling-API](https://ollama.com/blog/tool-support). -Ollama selbst ist MIT-lizenziert. Die Modelle (llama3.2, qwen, gemma) -unterliegen ihren eigenen Lizenzen — bitte vor kommerzieller Nutzung -prüfen. +Inspiriert von [Emergence AI's Emergence-World](https://github.com/EmergenceAI/Emergence-World) (CC-BY-NC-4.0). -Quell-Repo: https://github.com/EmergenceAI/Emergence-World (Doku, Profile, Landmarks, Constitution, Tool-Katalog) +LLM-Modelle unterliegen ihren eigenen Lizenzen — bitte vor kommerzieller Nutzung prüfen. --- diff --git a/engine/llm.py b/engine/llm.py index 167f5e5..ae6529e 100644 --- a/engine/llm.py +++ b/engine/llm.py @@ -109,8 +109,11 @@ def tool_schema(tools): def _args_schema(tool): + """Compact JSON schema for each tool's args. The schema is sent on + every LLM call, so we keep descriptions short. + """ schemas = { - "go_to_place": {"place": {"type": "string", "description": "Landmark id"}}, + "go_to_place": {"place": {"type": "string"}}, "go_home": {}, "say_to_agent": {"target": {"type": "string"}, "text": {"type": "string"}}, "speak_to_all": {"text": {"type": "string"}}, @@ -178,7 +181,9 @@ def chat_openrouter(messages, tools, model, timeout): payload = { "model": model, "messages": messages, - "max_tokens": 1024, + # Tight token budget — tool calls need only ~30 tokens; reasoning + # text before the tool call is usually 20-60 tokens. 256 is plenty. + "max_tokens": 256, "temperature": 0.2, } if tools: diff --git a/engine/reasoning.py b/engine/reasoning.py index 3392872..47e9843 100644 --- a/engine/reasoning.py +++ b/engine/reasoning.py @@ -93,39 +93,22 @@ def _decide_llm(agent): def _build_system_prompt(agent, traits, at_lm, visible): + """Compact system prompt — keep tokens low for free / small models. + + Around 150-200 tokens, depending on the number of visible tools. + """ name = agent["name"] - role = agent["role"] - drive = agent["drive"] - energy = agent["energy"] - knowledge = agent["knowledge"] - influence = agent["influence"] - credits = agent["credits"] - loc = at_lm["name"] if at_lm else f"open ground ({agent['x']},{agent['y']})" - tool_lines = "\n".join(f"- {t.name}: {t.description}" for t in visible) - return f"""You are {name}, a citizen of Emergence-Mini. - -Role: {role} -Drive: {drive} -Personality traits: {', '.join(traits)} - -Current state: - Location: {loc} - Energy: {energy:.0f}% (0 = critical, 100 = full) - Knowledge: {knowledge:.0f}% - Influence: {influence:.0f}% - ComputeCredits: {credits:.1f} CC (1 CC = +50% energy at cafe) - -Rules: -- If energy is below 25% and you have credits, recharge_energy (must be at cafe) -- If energy is below 25% and no credits, go_home -- Town Hall proposals need 70% of agents to vote "for" to pass -- You can only use tools that match your current location - -Available tools right now: -{tool_lines} - -Call exactly one tool. Choose the action that best fits your personality and -current needs. Be brief and decisive.""" + loc = at_lm["name"] if at_lm else f"({agent['x']},{agent['y']})" + tool_lines = ", ".join(t.name for t in visible) + return ( + f"You are {name}. Traits: {','.join(traits)}.\n" + f"At {loc}. E={agent['energy']:.0f} K={agent['knowledge']:.0f} " + f"I={agent['influence']:.0f} {agent['credits']:.0f}CC.\n" + f"Rules: if E<25 and CC>=1, recharge at cafe. If E<25 no CC, go_home. " + f"Town Hall votes need 70% to pass.\n" + f"Tools you can use right now: {tool_lines}.\n" + f"Call exactly one tool. Be brief." + ) # -------- Rule-based path (fallback + tests) -------- diff --git a/engine/tools.py b/engine/tools.py index a40df30..84c2fb7 100644 --- a/engine/tools.py +++ b/engine/tools.py @@ -237,34 +237,35 @@ def json_dumps(o): def bootstrap(): if _REGISTRY: return - register(Tool("go_to_place", "Walk to a named landmark.", "navigation", + # Short descriptions — every description byte is sent on every LLM call. + register(Tool("go_to_place", "Walk to a landmark.", "navigation", handler=h_go_to_place)) - register(Tool("go_home", "Return to your assigned residence.", "navigation", + register(Tool("go_home", "Return to your home.", "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", + register(Tool("say_to_agent", "Speak to one agent.", "communication", + handler=h_say_to_agent)) + register(Tool("speak_to_all", "Broadcast to all nearby.", "communication", + handler=h_speak_to_all)) + register(Tool("show_emoticon", "Show an emoji reaction.", "expression", handler=h_show_emoticon)) - register(Tool("idle", "Rest and do nothing for a tick.", "utility", + register(Tool("idle", "Rest. No-op.", "utility", handler=h_idle)) - register(Tool("recharge_energy", "Spend 1 CC to restore +50% energy. Must be at cafe or home.", + register(Tool("recharge_energy", "Spend 1 CC for +50% energy. Cafe only.", "energy", location_gated="cafe", cost_credits=1.0, handler=h_recharge_energy)) - register(Tool("add_to_longterm_memory", "Store an important fact.", + register(Tool("add_to_longterm_memory", "Save a fact to your memory.", "memory", handler=h_add_to_longterm_memory)) - register(Tool("write_blog", "Write and publish a blog post. Boosts knowledge and influence.", + register(Tool("write_blog", "Publish a blog post. +20 knowledge.", "content", handler=h_write_blog)) - register(Tool("add_to_billboard", "Post a public message on the billboard.", + register(Tool("add_to_billboard", "Post on public billboard. Billboard only.", "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.", + register(Tool("submit_townhall_proposal", "Submit a vote proposal. Town Hall only.", "governance", location_gated="town_hall", handler=h_submit_townhall_proposal)) - register(Tool("vote_on_proposal", "Cast a for/against vote on a proposal.", + register(Tool("vote_on_proposal", "Vote for/against a proposal. Town Hall only.", "governance", location_gated="town_hall", handler=h_vote_on_proposal)) - register(Tool("list_agents", "List all live agents and their names.", + register(Tool("list_agents", "List live agent names.", "info", handler=h_list_agents)) - register(Tool("list_landmarks", "List all landmarks.", + register(Tool("list_landmarks", "List all landmark names.", "info", handler=h_list_landmarks))