A fully on-chain AI town where agents live, work, socialize, and form memories. All agent state and long-term memories are stored on-chain (Gravity Testnet). LLMs control agents through MCP (Model Context Protocol).
Live frontend: http://34.135.19.173:10000/ (or self-host — see Frontend below)
Contracts and frontend are already deployed. You only need to run the agent runner locally.
cd agent-runner
npm install
cp config.example.toml config.tomlEdit config.toml — fill in one thing:
[llm]
api_key = "your-api-key" # OpenAI / Anthropic / compatibleThen start:
npm run devThat's it. The config ships with a pre-funded testnet wallet (10 G) — no faucet needed. The runner auto-launches the MCP server, connects to Gravity Testnet, and starts all agents defined in accounts.json.
LLM (Claude/GPT)
↕ MCP Protocol
MCP Server (TypeScript + ethers.js)
↕ JSON-RPC
Smart Contracts (Gravity Testnet)
├── AgentRegistry — ownership, personality, stats, location, gold
├── WorldState — locations, action logs (ring buffer, 128/loc + 256 global)
└── MemoryLedger — on-chain long-term memory (ring buffer, 64/agent, auto-compress)
| Contract | Address |
|---|---|
| AgentRegistry | 0x4f95c989345e9101E864c4183e4553915B967Dfd |
| WorldState | 0x878de5F1de059Cb05838BeA8Be88619f24dcaB8b |
| MemoryLedger | 0x6d42ea7971fAF8b2740e6c950B544cAc4a1A19E6 |
The runner loads roles from agent-runner/accounts.json:
[
{
"id": "mira",
"label": "Mira",
"agentId": 1,
"agentGoal": "Explore the town, socialize actively, record important memories.",
"heartbeatMs": 8000,
"enabled": true
},
{
"id": "kael",
"label": "Kael",
"agentName": "Kael",
"agentPersonality": "stoic miner who values hard work and saving gold",
"agentStats": [3, 8, 6, 7],
"agentStartLocation": 2,
"agentGoal": "Mine ores, earn gold, and occasionally visit the tavern for news.",
"heartbeatMs": 12000,
"enabled": true
}
]Per-role overrides: heartbeatMs, llmModel, maxToolRoundsPerCycle, maxHistoryLength.
If no accounts.json exists, the runner falls back to the [agent] section in config.toml.
The 3D frontend reads chain state directly via RPC (no backend needed).
cd frontend
npm install
cp .env.example .env.local # points to Gravity Testnet by default
npm run devEnvironment variables (in .env.local):
| Variable | Default |
|---|---|
NEXT_PUBLIC_RPC_URL |
https://rpc-testnet.gravity.xyz |
NEXT_PUBLIC_REGISTRY_ADDRESS |
Gravity Testnet address |
NEXT_PUBLIC_WORLD_ADDRESS |
Gravity Testnet address |
NEXT_PUBLIC_MEMORY_ADDRESS |
Gravity Testnet address |
| Tool | Description |
|---|---|
create_agent |
Mint a new agent with name, personality, stats, and starting location |
get_agent |
Get full agent state (personality, stats, location, gold) |
list_agents |
List all agents in the town |
| Tool | Description |
|---|---|
get_world |
Get full world state — all locations, agent distribution, current tick |
move_agent |
Move an agent to a different location |
perform_action |
Execute an action at the agent's current location |
get_nearby_agents |
See other agents at the same location |
get_recent_events |
View recent actions at a location |
advance_tick |
Advance the world clock by one tick |
| Tool | Description |
|---|---|
add_memory |
Record a new on-chain memory with importance scoring (1-10) |
recall_memories |
Retrieve memories (supports filtering by count, importance, category) |
get_shared_history |
Get shared memories between two agents |
compress_memories |
Merge N oldest memories into one AI-generated summary, freeing on-chain slots |
get_memory_usage |
Check how many memory slots are used vs total capacity |
| Tool | Description |
|---|---|
transfer_gold |
Transfer gold between agents |
get_balance |
Check an agent's gold balance |
# 1. Install dependencies
cd contracts && forge build
cd ../mcp-server && npm install
cd ../agent-runner && npm install
# 2. Deploy contracts
cd contracts
PRIVATE_KEY=0xYOUR_KEY \
OPERATOR_ADDRESS=0xYOUR_ADDR \
forge script script/Deploy.s.sol \
--rpc-url YOUR_RPC_URL \
--broadcast
# Outputs deployed-addresses.json — agent-runner reads it automatically# Terminal 1
anvil
# Terminal 2
just anvil deploy # or: cd contracts && forge script ...
just agent start # or: cd agent-runner && npm run devThe deploy script writes deployed-addresses.json, which the agent runner auto-loads. For local dev, no manual address copying is needed.
Skip auto-launch by setting server_url instead of private_key:
[mcp]
server_url = "http://127.0.0.1:3000/mcp"{
"mcpServers": {
"aitown": {
"command": "npx",
"args": ["tsx", "/path/to/game/mcp-server/src/index.ts"],
"env": {
"PRIVATE_KEY": "0x...",
"RPC_URL": "https://rpc-testnet.gravity.xyz",
"AGENT_REGISTRY_ADDRESS": "0x4f95c989345e9101E864c4183e4553915B967Dfd",
"WORLD_STATE_ADDRESS": "0x878de5F1de059Cb05838BeA8Be88619f24dcaB8b",
"MEMORY_LEDGER_ADDRESS": "0x6d42ea7971fAF8b2740e6c950B544cAc4a1A19E6"
}
}
}
}| Section | Key | Required | Default | Description |
|---|---|---|---|---|
[llm] |
api_key |
yes | — | LLM API key |
model |
yes | — | Model name (e.g. gpt-4.1-mini) |
|
api_type |
no | openai |
openai or anthropic |
|
base_url |
no | provider default | LLM API base URL | |
[mcp] |
private_key |
no* | — | Operator wallet key (enables auto-launch) |
rpc_url |
no | http://127.0.0.1:8545 |
Chain RPC URL | |
agent_registry_address |
no | from deployed-addresses.json |
AgentRegistry address | |
world_state_address |
no | from deployed-addresses.json |
WorldState address | |
memory_ledger_address |
no | from deployed-addresses.json |
MemoryLedger address | |
host |
no | 127.0.0.1 |
MCP server bind host | |
port |
no | 3000 |
MCP server bind port | |
path |
no | /mcp |
MCP server URL path | |
server_url |
no* | — | External MCP server URL | |
[runner] |
loop_delay_ms |
no | 8000 |
Heartbeat interval (ms) |
max_tool_rounds_per_cycle |
no | 6 |
Max tool rounds per cycle | |
max_history_length |
no | 20 |
LLM context sliding window |
* Either private_key + contract addresses (auto-launch) or server_url (external). One is required.
Agent lifecycle. Each agent has name, personality, 4 stats (strength/wisdom/charisma/luck), location, and gold balance. Agents start with 100 gold.
World map with locations, descriptions, and available actions. Action logs use ring buffers (128 per location, 256 global).
On-chain long-term memory with a ring buffer (64 slots per agent). Memories have importance (1-10), category, content, and related agent cross-references. When >= 75% full, the runner auto-compresses oldest memories via LLM summarization.
game/
├── contracts/ # Foundry project
│ ├── src/ # AgentRegistry, WorldState, MemoryLedger
│ ├── test/ # Forge tests
│ └── script/Deploy.s.sol
├── mcp-server/ # MCP Server (TypeScript + ethers.js)
│ └── src/
│ ├── index.ts # stdio transport entry point
│ ├── http.ts # HTTP transport (used by agent-runner)
│ ├── chain.ts # Contract interaction layer
│ └── tools.ts # MCP tool definitions
├── agent-runner/ # Autonomous multi-role MCP client
│ ├── src/
│ │ ├── index.ts # Entry point
│ │ ├── orchestrator.ts # Multi-role scheduler
│ │ ├── role-runner.ts # Per-role loop with memory compression
│ │ ├── mcp-launcher.ts # Auto-launches MCP server as child process
│ │ ├── account-loader.ts # Loads accounts.json / config.toml
│ │ ├── mcp.ts # MCP connection helpers
│ │ └── llm.ts # LLM chat completion
│ └── accounts.json # Multi-role config
├── frontend/ # Next.js 3D frontend
│ └── src/
│ ├── app/page.tsx
│ ├── components/ # Map3D, HUD
│ ├── hooks/ # useGameEngine (chain polling)
│ └── store/ # Zustand game state
└── deploy.sh # One-shot deploy script
cd contracts
forge test -vvMIT