soci2 / README.md
RayMelius's picture
Add app_port: 7860 to HF Space YAML config
5ec5bd3
---
title: Soci
emoji: πŸ™οΈ
colorFrom: blue
colorTo: purple
sdk: docker
app_port: 7860
pinned: false
---
# Soci β€” LLM-Powered City Population Simulator
Simulates a diverse population of AI people living in a city using an LLM as the reasoning engine. Each agent has a unique persona, memory stream, needs, and relationships.
Inspired by [Stanford Generative Agents (Joon Park et al.)](https://arxiv.org/abs/2304.03442), CitySim, AgentSociety, and a16z ai-town.
**Live demo:** https://huggingface.co/spaces/RayMelius/soci
---
## Features
- AI agents with unique personas, goals, and memories
- Maslow-inspired needs system (hunger, energy, social, purpose, comfort, fun)
- Relationship graph with familiarity, trust, sentiment, and romance
- Agent cognition loop: **OBSERVE β†’ REFLECT β†’ PLAN β†’ ACT β†’ REMEMBER**
- Web UI with animated city map, zoom, pan, and agent inspector
- Road-based movement with L-shaped routing (agents walk along streets)
- Agent animations: walking (profile/back view), sleeping on bed
- Speed controls (1x β†’ 50x) and real-time WebSocket sync across browsers
- **LLM probability slider** β€” tune AI usage from 0–100% to stay within free-tier quotas
- **Player login** β€” register an account, get your own agent on the map, chat with NPCs
- Multi-LLM support: Gemini (free tier), Groq (free tier), Anthropic Claude, Ollama (local)
- GitHub-based state persistence (survives server reboots and redeploys)
- Cost-efficient model routing (Haiku for routine, Sonnet for novel situations)
- Daily quota circuit-breaker with warnings at 50 / 70 / 90 / 99% usage
---
## System Architecture
```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser (web/index.html β€” single-file Vue-less UI) β”‚
β”‚ β€’ Canvas city map β€’ Agent inspector β€’ Chat panel β”‚
β”‚ β€’ Speed / LLM-probability sliders β€’ Login modal β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ REST GET /api/* β”‚ WebSocket /ws
β”‚ POST /api/controls/* β”‚ push events
β–Ό β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ FastAPI Server (soci/api/server.py) β”‚
β”‚ β€’ lifespan: load state β†’ start sim loop β”‚
β”‚ β€’ routes.py β€” REST endpoints β”‚
β”‚ β€’ websocket.py β€” broadcast tick events β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ asyncio.create_task
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Simulation Loop (background task) β”‚
β”‚ tick every N sec β†’ sim.tick() β†’ sleep β”‚
β”‚ respects: _sim_paused / _sim_speed / llm_call_prob β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Simulation.tick() (engine/simulation.py) β”‚
β”‚ β”‚
β”‚ 1. Entropy / world events β”‚
β”‚ 2. Daily plan generation ──► LLM (if prob gate βœ“) β”‚
β”‚ 3. Agent needs + routine actions (no LLM) β”‚
β”‚ 4. LLM action decisions ─────► LLM (if prob gate βœ“) β”‚
β”‚ 5. Conversation turns ───────► LLM (if prob gate βœ“) β”‚
β”‚ 6. New conversation starts ──► LLM (if prob gate βœ“) β”‚
β”‚ 7. Reflections ──────────────► LLM (if prob gate βœ“) β”‚
β”‚ 8. Romance / relationship updates (no LLM) β”‚
β”‚ 9. Clock advance β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ await llm.complete_json()
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ LLM Client (engine/llm.py) β”‚
β”‚ β€’ Rate limiter (asyncio.Lock, min interval per RPM) β”‚
β”‚ β€’ Daily usage counter β†’ warns at 50/70/90/99% β”‚
β”‚ β€’ Quota circuit-breaker (expires midnight Pacific) β”‚
β”‚ β€’ Providers: GeminiClient / GroqClient / β”‚
β”‚ ClaudeClient / HFInferenceClient / β”‚
β”‚ OllamaClient β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTP (httpx async)
β–Ό
External LLM API (Gemini / Groq / …)
```
---
## Message Flow β€” One Simulation Tick
```
Browser poll (3s) Simulation background loop
β”‚ β”‚
β”‚ GET /api/city tick_delay (4s Gemini, 0.5s Ollama)
│◄─────────────────────────────
β”‚ β”‚ sim.tick()
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ For each agent: β”‚
β”‚ β”‚ tick_needs() β”‚
β”‚ β”‚ check routine │──► execute routine action (no LLM)
β”‚ β”‚ roll prob gate β”‚
β”‚ β”‚ _decide_action() │──► LLM call ──► AgentAction JSON
β”‚ β”‚ _execute_action() β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ Conversations: β”‚
β”‚ β”‚ continue_conv() │──► LLM call ──► dialogue turn
β”‚ β”‚ new conv start │──► LLM call ──► opening line
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ Reflections: β”‚
β”‚ β”‚ should_reflect()? │──► LLM call ──► memory insight
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚
β”‚ clock.tick()
β”‚ β”‚
β”‚ WebSocket push β”‚
│◄── events/state β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
[browser updates map, event log, agent inspector]
```
---
## Agent Cognition Loop
```
Every tick, each NPC agent runs:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”‚ OBSERVE ──► perceive nearby agents, events, β”‚
β”‚ location, time of day β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ REFLECT ──► check memory.should_reflect() β”‚
β”‚ LLM synthesises insight from recent β”‚
β”‚ memories β†’ stored as reflection β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ PLAN ───► if no daily plan: LLM generates β”‚
β”‚ ordered list of goals for the day β”‚
β”‚ (or routine fills the plan β€” no LLM) β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ ACT ────► routine slot? β†’ execute directly β”‚
β”‚ no slot? β†’ LLM picks action β”‚
β”‚ action types: move / work / eat / β”‚
β”‚ sleep / socialise / leisure / rest β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ REMEMBER β–Ί add_observation() to memory stream β”‚
β”‚ importance 1–10, recency decay, β”‚
β”‚ retrieved by relevance score β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
LLM budget per tick (rate-limited providers):
max_llm_calls_this_tick = 1 (Gemini/Groq/HF)
llm_call_probability = 0.45 (Gemini default β†’ ~10h/day)
```
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Language | Python 3.10+ |
| API server | FastAPI + Uvicorn |
| Real-time | WebSocket (FastAPI) |
| Database | SQLite via aiosqlite |
| LLM providers | Gemini Β· Groq Β· Anthropic Claude Β· HF Inference Β· Ollama |
| Config | YAML (city layout, agent personas) |
| State persistence | GitHub API (simulation-state branch) |
| Container | Docker (HF Spaces / Render) |
---
## Quick Start (Local)
### Prerequisites
- Python 3.10+
- At least one LLM API key β€” or [Ollama](https://ollama.ai) installed locally (free, no key needed)
### Install
```bash
git clone https://github.com/Bonum/Soci.git
cd Soci
pip install -r requirements.txt
```
### Configure
```bash
# Pick ONE provider (Gemini recommended β€” free tier is generous):
export GEMINI_API_KEY=AIza... # https://aistudio.google.com/apikey
# or
export GROQ_API_KEY=gsk_... # https://console.groq.com
# or
export ANTHROPIC_API_KEY=sk-ant-...
# or install Ollama and pull a model β€” no key needed
```
### Run
```bash
# Web UI (recommended)
python -m uvicorn soci.api.server:app --host 0.0.0.0 --port 8000
# Open http://localhost:8000
# Terminal only
python main.py --ticks 20 --agents 5
```
---
## Deploying to the Internet
### Option 1 β€” Hugging Face Spaces (free, recommended)
HF Spaces runs the Docker container for free with automatic HTTPS.
1. **Create a Space** at https://huggingface.co/new-space
- SDK: **Docker**
- Visibility: Public
2. **Add the HF remote** and push:
```bash
git remote add hf https://YOUR_HF_USERNAME:YOUR_HF_TOKEN@huggingface.co/spaces/YOUR_HF_USERNAME/soci
git push hf master:main
```
Get a write token at https://huggingface.co/settings/tokens (select *Write* + *Inference Providers* permissions).
3. **Add Space secrets** (Settings β†’ Variables and Secrets):
| Secret | Value |
|--------|-------|
| `SOCI_PROVIDER` | `gemini` |
| `GEMINI_API_KEY` | your AI Studio key |
| `GITHUB_TOKEN` | GitHub PAT (repo read/write) |
| `GITHUB_OWNER` | your GitHub username |
| `GITHUB_REPO_NAME` | `Soci` |
4. Your Space rebuilds automatically on every push. Visit
`https://YOUR_HF_USERNAME-soci.hf.space`
> **Free-tier tip:** Gemini free tier = 5 RPM, ~1500 requests/day (resets midnight Pacific).
> The default LLM probability is set to **45%** which gives ~10 hours of AI-driven simulation per day.
> Use the 🧠 slider in the toolbar to adjust at runtime.
---
### Option 2 β€” Render (free tier)
1. Connect your GitHub repo at https://render.com/new
2. Choose **Web Service** β†’ Docker
3. Set **Start Command**:
```
python -m uvicorn soci.api.server:app --host 0.0.0.0 --port $PORT
```
4. Set environment variables in the Render dashboard:
| Variable | Value |
|----------|-------|
| `SOCI_PROVIDER` | `gemini` or `groq` |
| `GEMINI_API_KEY` | your key |
| `GITHUB_TOKEN` | GitHub PAT |
| `GITHUB_OWNER` | your GitHub username |
| `GITHUB_REPO_NAME` | `Soci` |
5. To prevent state-file commits from triggering redeploys, set **Ignore Command**:
```
[ "$(git diff --name-only HEAD~1 HEAD | grep -v '^state/' | wc -l)" = "0" ]
```
> **Note:** Render free tier spins down after 15 min of inactivity. Simulation state is saved to GitHub on shutdown and restored on the next boot β€” no data is lost.
---
### Option 3 β€” Railway
1. Go to https://railway.app β†’ **New Project** β†’ **Deploy from GitHub repo**
2. Railway auto-detects the Dockerfile
3. Add environment variables in the Railway dashboard (same as Render above)
4. Railway assigns a public URL automatically
---
### Option 4 β€” Local + Ngrok (quick public URL for testing)
```bash
# Start the server
python -m uvicorn soci.api.server:app --host 0.0.0.0 --port 8000 &
# Expose it publicly (install ngrok first: https://ngrok.com)
ngrok http 8000
# Copy the https://xxxx.ngrok.io URL and share it
```
---
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `SOCI_PROVIDER` | auto-detect | LLM provider: `gemini` Β· `groq` Β· `claude` Β· `hf` Β· `ollama` |
| `GEMINI_API_KEY` | β€” | Google AI Studio key (free tier: 5 RPM, ~1500 RPD) |
| `GROQ_API_KEY` | β€” | Groq API key (free tier: 30 RPM) |
| `ANTHROPIC_API_KEY` | β€” | Anthropic Claude API key |
| `SOCI_LLM_PROB` | per-provider | LLM call probability 0–1 (`0.45` Gemini Β· `0.7` Groq Β· `1.0` Ollama) |
| `GEMINI_DAILY_LIMIT` | `1500` | Override Gemini daily request quota for warning thresholds |
| `SOCI_AGENTS` | `50` | Starting agent count |
| `SOCI_TICK_DELAY` | `0.5` | Seconds between simulation ticks (overridden to 4.0 for rate-limited providers) |
| `SOCI_DATA_DIR` | `data` | Directory for SQLite DB and snapshots |
| `GITHUB_TOKEN` | β€” | GitHub PAT for state persistence across deploys |
| `GITHUB_OWNER` | β€” | GitHub repo owner (e.g. `alice`) |
| `GITHUB_REPO_NAME` | β€” | GitHub repo name (e.g. `Soci`) |
| `GITHUB_STATE_BRANCH` | `simulation-state` | Branch used for state snapshots (never touches main) |
| `GITHUB_STATE_FILE` | `state/autosave.json` | Path inside repo for state file |
| `PORT` | `8000` | HTTP port (set to `7860` on HF Spaces automatically) |
---
## Web UI Controls
| Control | How |
|---------|-----|
| Zoom | Scroll wheel or **οΌ‹ / -** buttons |
| Fit view | **Fit** button |
| Pan | Drag canvas or use sliders |
| Rectangle zoom | Click **⬚**, then drag |
| Inspect agent | Click agent on map or in sidebar list |
| Speed | **🐒 1x 2x 5x 10x 50x** buttons |
| LLM usage | **🧠** slider (0–100%) β€” tune AI call frequency |
| Switch LLM | Click the provider badge (e.g. **✦ Gemini 2.0 Flash**) |
| **Login / play** | Register β†’ your agent appears with a gold ring |
| **Talk to NPC** | Select agent β†’ **Talk to [Name]** button |
| **Move** | Player panel β†’ location dropdown β†’ **Go** |
| **Edit profile** | Player panel β†’ **Edit Profile** |
| **Add plans** | Player panel β†’ **My Plans** |
---
## LLM Provider Comparison
| Provider | Free tier | RPM | Daily limit | Best for |
|----------|-----------|-----|-------------|----------|
| **Gemini 2.0 Flash** | βœ… Yes | 5 | ~1500 req | Cloud demos (default) |
| **Groq Llama 3.1 8B** | βœ… Yes | 30 | ~14k tokens/min | Fast responses |
| **Ollama** | βœ… Local | ∞ | ∞ | Local dev, no quota |
| **Anthropic Claude** | ❌ Paid | β€” | β€” | Highest quality |
| **HF Inference** | ⚠️ PRO only | 5 | varies | Experimenting |
---
## Project Structure
```
Soci/
β”œβ”€β”€ src/soci/
β”‚ β”œβ”€β”€ world/ City map, simulation clock, world events
β”‚ β”œβ”€β”€ agents/ Agent cognition: persona, memory, needs, relationships
β”‚ β”œβ”€β”€ actions/ Movement, activities, conversation, social actions
β”‚ β”œβ”€β”€ engine/ Simulation loop, scheduler, entropy, LLM clients
β”‚ β”œβ”€β”€ persistence/ SQLite database, save/load snapshots
β”‚ └── api/ FastAPI REST + WebSocket server
β”œβ”€β”€ config/
β”‚ β”œβ”€β”€ city.yaml City layout, building positions, zones
β”‚ └── personas.yaml Named character definitions (20 hand-crafted agents)
β”œβ”€β”€ web/
β”‚ └── index.html Single-file web UI (no framework)
β”œβ”€β”€ Dockerfile For HF Spaces / Render / Railway deployment
β”œβ”€β”€ render.yaml Render deployment config
└── main.py Terminal runner (no UI)
```
---
## License
MIT