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.), 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 installed locally (free, no key needed)
Install
git clone https://github.com/Bonum/Soci.git
cd Soci
pip install -r requirements.txt
Configure
# 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
# 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.
Create a Space at https://huggingface.co/new-space
- SDK: Docker
- Visibility: Public
Add the HF remote and push:
git remote add hf https://YOUR_HF_USERNAME:YOUR_HF_TOKEN@huggingface.co/spaces/YOUR_HF_USERNAME/soci git push hf master:mainGet a write token at https://huggingface.co/settings/tokens (select Write + Inference Providers permissions).
Add Space secrets (Settings β Variables and Secrets):
Secret Value SOCI_PROVIDERgeminiGEMINI_API_KEYyour AI Studio key GITHUB_TOKENGitHub PAT (repo read/write) GITHUB_OWNERyour GitHub username GITHUB_REPO_NAMESociYour 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)
Connect your GitHub repo at https://render.com/new
Choose Web Service β Docker
Set Start Command:
python -m uvicorn soci.api.server:app --host 0.0.0.0 --port $PORTSet environment variables in the Render dashboard:
Variable Value SOCI_PROVIDERgeminiorgroqGEMINI_API_KEYyour key GITHUB_TOKENGitHub PAT GITHUB_OWNERyour GitHub username GITHUB_REPO_NAMESociTo 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
- Go to https://railway.app β New Project β Deploy from GitHub repo
- Railway auto-detects the Dockerfile
- Add environment variables in the Railway dashboard (same as Render above)
- Railway assigns a public URL automatically
Option 4 β Local + Ngrok (quick public URL for testing)
# 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