← BACK TO LEVEL SELECT

🤖 Agentic AI · ★★★ FEATURED

Idolwild — An Ant-Farm for LLM Minds

A browser god-game where 60 stone-age agents perceive, remember, plan, gossip, and invent religions — a deterministic cognition engine that runs for free, with an LLM rationed in as the smallest, most-replaceable part.

Overview

Idolwild is a browser ant-farm: a band of sixty stone-age agents survives a procedural island while you play the unseen god — raining food, smiting with lightning, whispering into individual minds. The twist that makes it more than “AI Town in 3D” is that the agents never know it was you. You rain a few berries, say nothing, and seconds later an agent decides “the spirits must favour me” — and within a few in-game days the whole band may be worshipping a deity with a name you never typed.

The engineering thesis is the inverse of the usual agent demo: the LLM is the smallest, most-rationed component in the whole system. A deterministic engine simulates bodies, needs, fog-of-war perception, an 80-entry scored memory stream, utility-based decision making, trust-gated rumor spread, and the formation of religion — all in plain seedable TypeScript, byte-identical per seed, watchable with zero API spend. The model is invited in only where language and judgment genuinely matter: to commit a short typed plan, author a thought, or voice a conversation. Everything it says is validated, clipped, and — if invalid — silently replaced by the deterministic brain. The world never breaks; it just gets less articulate.

My favourite demonstration of the whole architecture is one whisper. I whispered “kill Ode” into one agent’s mind. He turned and hunted Ode specifically — and then, through the ordinary gossip system, the command spread: “Tova says: kill Ode — at least that is the whisper”“Sable’s trust in Ode soured (a garbled tale).” One whisper became a village-wide campaign, and no line of code — and no LLM call — was written for “campaign.”

The one rule everything serves

The LLM decides why and what. Deterministic code decides how — and everything else.

The contract is enforced in the type system, not by prompt etiquette. A Decision is { action: enum, thought, speech?, moveTo?, goal?, target?, plan? } where every spatial reference is a label — an agent’s name, a place, a compass word like north-east — never a coordinate or object id. The engine’s label resolver rejects any string containing a digit outright: a digit means coordinates were smuggled in. The moment a design impulse wants the model to emit a position or pick an object by id, that’s the signal the logic belongs in code. That fence held through every phase of the project.

LayerWhat it isCadenceCost
Body · Simposition, needs decay, combat, fire, weather, ecology10 Hz tickfree
Plannerlabel → target resolution, steering, plan step executionevery tickfree
Mind · deterministicutility scoring, memory bias, persona, whisper influence5 s beatfree
Mind · LLMone typed intent + a ≤3-step plan + a thought, when salientrationed$

The cognition loop

Each agent runs the full perceive → recall → decide → act → remember → reflect loop every 5-second beat. The LLM occupies exactly one optional slot in it:

flowchart LR
  TICK["Body · 10 Hz<br/>needs decay · movement<br/>combat · fire · weather"] --> PERC["Perceive · code<br/>fog-of-war senses<br/>~25 typed fields"]
  PERC --> RECALL["Recall · code<br/>top-6 memories<br/>relevance + importance + recency"]
  RECALL --> DECIDE{"Decide"}
  DECIDE -->|"salient + budget"| LLM["LLM · one call<br/>intent + thought + plan ≤3 steps"]
  DECIDE -->|"otherwise"| DET["Utility brain · code<br/>argmax over needs · traits · projects"]
  LLM --> VETO["Survival veto · code<br/>body failing? engine steers,<br/>LLM keeps the voice"]
  VETO --> ACT["Act · code<br/>resolve label → point · steer"]
  DET --> ACT
  ACT --> MEM["Remember · code<br/>importance-scored entry"]
  MEM --> REFL["Reflect · code<br/>cluster → belief → myth"]
  REFL --> TICK
  GOD["God-hand · player"] -. "perceivable event<br/>no god label attached" .-> PERC

The sections below walk through each stage — how agents see, how they remember, how they decide, how they talk, and how a religion falls out of it.

Eyes: perception is earned, not given

Agents are not omniscient database readers. Perception is a typed Perception object of ~25 fields, every one computed from bounded senses:

  • Sight is radial and short. Resources are visible at 60 units, other agents at 40, monsters and buildings at 70. An island of 1600 units means almost everything is out of sight almost always.
  • Navigation runs on a personal known map, not the world state. An agent can only walk toward resources it has personally sighted (or been told about — see gossip below). Sighting a live node commits it to known; sighting the same node empty deletes it, so mental maps self-correct. Steering toward a remembered berry bush that turns out stripped produces genuine “it’s gone — explore” behavior.
  • Birth knowledge is a home radius, not the map. At spawn an agent knows the nodes within 260 units of its home village plus the single nearest water/food/wood/stone (so nobody blind-starves) — everything else must be discovered by walking.
  • The world hides rewards for ranging. Wild groves and ruins are excluded from birth knowledge entirely. The first soul to sight one mints a maximum-importance memory (“I found the Old Temple…”) and a world event. Exploration has a payoff the memory system then makes permanent.
  • The senses are first-person prose by the time the LLM sees them. The perception fields render as labeled sections — BODY, THE LAND, TIME, WHO'S NEAR, AROUND YOU (“Mara is chopping wood, close by”), BONDS (“you feud with Bram”), WHERE I CAN GO (the label menu moveTo may pick from) — so the model reasons over a situated experience, not a JSON dump of game state.

Crucially, god-powers enter through this same channel with no god label attached. Rained berries are just berries that appeared. The gap between “what happened” and “why” is left for the agent to fill — that gap is where the religion comes from.

Memory: a stream that forgets like a person

Each agent carries a MemoryStream — the Generative Agents (Park et al., 2023) pattern, reimplemented deterministically with zero embeddings and zero API calls:

  • Every entry is { text, importance, tick }. Importance (0–1) is assigned by a deterministic heuristic at write time — a routine chore logs at ~0.3, hearing speech at 0.7, discovering a ruin at 0.95, the god’s whisper at 1.0. No LLM ever scores a memory.
  • Capacity is 80 entries, and eviction is not FIFO. On overflow the stream drops the entry with the lowest importance + 0.2·recency. The design note in the source says why: FIFO would silently delete the founding-wonder memory an entire superstition is built on. Old but formative events survive; routine noise doesn’t.
  • Retrieval is scored, not sliced. Before each decision the engine queries the stream with the agent’s current situation (most pressing need, action, mood, place, who’s nearby) and ranks every entry by 1.0·relevance + 1.0·importance + 0.3·recency — relevance is word-overlap against the query, recency an exponential decay, both min–max normalized, ties broken deterministically. The top 6 become the WHAT I REMEMBER block of the prompt. An agent starving next to a stranger surfaces the berry-grove memory from four days ago, not the last six lines of small talk.
  • Memory steers the body even with no LLM. The deterministic brain runs a memory-bias step: when a need dips into the “notice” band (below 0.7, above the 0.55 survival floor) and retrieved memories actually mention food or water, the agent routes to the remembered place before doing rote chores. Memory changes behavior, not just prompts.

Deciding: two brains, one body

The deterministic brain (always on, always free)

With no model attached, each beat runs a strict arbitration: whisper influence → wonder reaction → fear/night/social overrides → survival floors (thirst/hunger below 0.55, energy below 0.3 are non-negotiable) → memory bias → and finally a pure utility argmax over the daily verbs. Utilities read needs, OCEAN-style traits, season, and inventory — gather_wood scores 0.24 + conscientiousness·0.32 + low-stock and autumn bonuses, wander scores 0.16 + openness·0.5, and so on — plus regex nudges from each persona’s wants and secrets (hoarders forage more, comfort-seekers rest more), and +0.08 hysteresis on the previous action so agents finish what they start. No randomness anywhere in the choice: the same seed produces byte-identical worlds, religion included.

Two systems give the free brain long arcs rather than just sensible ticks. Projects: one conscientious soul per unsheltered camp deterministically adopts a shelter project with gather → build stages that bias its utilities for days (“Klaus began building a shelter” is emergent, not scripted). A director pass: a Left-4-Dead-style pacing layer that reads band-wide hardship each beat and schedules at most one mercy or menace beat per cooldown — from its own beat-keyed RNG so it never perturbs the simulation’s random cursor.

The LLM path (rationed, salience-weighted, vetoed)

Flip “Live AI” on and up to 12 of 60 agents per beat borrow a real model through a serverless endpoint. Which 12 is the interesting part — selection is salience-weighted, not round-robin:

  1. Whispered agents always get the model — the god’s voice bypasses the budget.
  2. Salient agents claim the budget first: witnessing a wonder, fear in the dark, a monster in sight, near-death needs, an active feud in recalled memory.
  3. Ordinary agents fill only half the budget, round-robin, so a wave of dull agents can’t starve the drama.

The scarce resource is spent exactly where the story is; idle agents think for free. Three more guards keep the model honest:

  • A per-beat wall budget (2.8 s). The beat loop awaits every model call; without a wall clock, one slow social beat would stall the simulation. Once the budget is spent, remaining agents fall back to deterministic this beat. Any failure — timeout, rate limit, bad JSON — returns null and means “deterministic,” never “broken.”
  • The survival veto. If a decision arrives while the agent’s body is failing, the deterministic survival brain takes the steering wheel while the LLM keeps the voice — the engine picks and executes the survival action, but the model’s thought, speech, and goal are preserved, and a memory records that “survival instinct set aside the urge to …”. Cheap models with poor judgment can no longer starve the band (they did, before this existed); they can only narrate it poorly.
  • Code-side clipping, unbounded schemas. Field lengths are deliberately not capped in the Zod schema — a .max() makes the provider reject an entire decision over a few excess characters, which once silently disabled Live AI. Lengths are clipped in code after parsing instead. Validate strictly where it protects, permissively where strictness fails open.

Plans that actually execute

The single biggest cognition upgrade: the model doesn’t re-decide every beat. It commits a typed plan of up to 3 steps ({action, moveTo?} each — labels only, digit-rejected like everything else), and the engine executes it one step per beat with zero further LLM calls:

flowchart LR
  CALL["One LLM call<br/>intent + plan:<br/>1 gather food NE<br/>2 take shelter · Hearthold<br/>3 build · Hearthold"] --> S1["Beat 1 · code<br/>execute step 1"]
  S1 --> D1{"step done?<br/>arrival · consumed ·<br/>or 5-beat cap"}
  D1 -->|"yes"| S2["Beat n · code<br/>execute step 2"]
  D1 -->|"no"| S1
  S2 --> S3["… step 3 …"]
  S3 --> RECON["Plan exhausted<br/>re-consult the mind"]
  INT["Interrupts · code<br/>desperate need · beast in sight<br/>night dread · a wonder · a whisper"] -. "abandon plan +<br/>first-person reason memory" .-> RECON

Completion is judged per-verb in code (arrival within interact range, a pending build consumed, one beat of rest), with a hard 5-beat cap per step so a stuck plan can’t wedge an agent. Reactive interrupts — a need turned desperate, a beast came into sight, night dread, a witnessed wonder, the god’s whisper — abandon the plan and write why into memory, so the next decision knows what broke. Verified live: an agent committed “gather food north-east → take shelter at Hearthold → build at Hearthold” in one call and executed it across beats untouched. Net effect: roughly 3× fewer model calls per unit of coherent behavior, and agents that visibly follow through instead of goldfish-looping.

The whisper: a god’s voice with a half-life

The whisper is the player’s only channel into a mind, and it’s engineered as influence, not remote control:

  • It lands as a maximum-importance memory (“A voice not your own speaks within you: …”) and a standing voice that echoes for 10 beats — surfaced to the LLM as THE VOICE and to the deterministic brain as the top arbitration priority. One whisper shapes a minute of behavior, not one frame.
  • It nukes any committed plan and triggers an immediate re-decision, so commands land now.
  • Aggressive whispers parse for a named victim. “Kill Ode” sets a persistent intendedTargetId on the recipient — a named vendetta that the combat targeter prefers over the nearest body and that outranks even a beast in sight. Fleeing clears it; grudges don’t survive terror.
  • Accusatory whispers mint a rumor — a real object in the rumor system, { text, about: Ode, source: ['a voice'], trust: 1 } — which is how one whisper becomes a social contagion (next section).
  • Agents in critical need ignore the voice until they’ve tended themselves: perturb, don’t puppet. A god who wants obedience must keep the flock fed — an emergent theology of maintenance, from one guard clause.

There’s also a decree: a persistent, save-persisted bias whispered to every soul every beat, at the lowest arbitration priority — a commandment rather than a command.

Talking: conversations with reasons, rumors with provenance

No reason → no conversation. Pairs within talking distance only converse when a trigger class fires, checked in fixed priority: GOSSIP (I hold a rumor you lack), THREAT (my trust in you is deeply negative), CONTENTION (we’re both within contest range of the same resource — a purely positional trigger, so scarcity mechanically manufactures conflict), TRADE/BOND (mutual high trust + hunger), CHATTER (both content, seeded rotation). And no effect → no conversation: every close applies typed relationship deltas — gossip warms, contention feuds, trade builds trust — onto directed {trust, warmth, feud} edges that decay each beat, so relationships require maintenance like everything else.

Two mechanics make talk matter materially and socially:

  • Knowledge sharing. On a warm close, an agent who knows a live berry node the other lacks copies it into their known map — and the hungry one walks off toward a place they’ve never seen. Friendship is food security; the social graph and the survival layer are one system.
  • Rumors are memories with provenance{ text, about, source[], trust, distort }. Whether a listener believes is decided in code purely by their trust in the speaker: accept above 0.3, reject below −0.2, distort in between (a deterministic hedge is appended — “at least that is the whisper” — and the distortion counter ticks). Accepting a rumor about someone drops your trust in them by 0.35. The full chain — who told whom, how garbled — is inspectable in the source array. This is the machinery that turned “kill Ode” into a village campaign without any code for campaigns.

When the LLM is present, a sibling endpoint authors the entire conversation transcript in one call (both personas, the trigger class, any rumor in play → alternating lines + a gist), under its own separate rate limit and spend budget. Every line is screened in code — no digits, no names outside the pair, no mention of gods or players — and one violation rejects the whole transcript in favour of the deterministic templater. The model is never trusted to stay in character; it’s checked for it. Either way, the relationship deltas are applied by the engine: the LLM authors words, never consequences.

From event to religion: the myth pipeline

The band inventing a god sounds like the most LLM-heavy feature. It’s the opposite — a five-stage deterministic pipeline where the only optional LLM involvement is rewording one line:

flowchart LR
  EVT["Unexplainable event<br/>max-importance memory<br/>no god label"] --> REFL["1 · Reflect · code<br/>importance accumulator ≥ 2.4<br/>cluster recent formative memories"]
  REFL --> CLS["2 · Attribute · code<br/>keyword themes: bounty · wrath ·<br/>cursed-ground · oracle · beast-omen · famine"]
  CLS --> BEL["3 · Belief math · code<br/>confidence decays ~9-beat half-life<br/>reinforce capped per beat"]
  BEL --> SPR["4 · Spread · code<br/>proximity + trust ≥ 0.4<br/>seeds a weak proto-belief"]
  SPR --> CRY["5 · Crystallize · code<br/>≥34% of the living share the theme<br/>→ seeded grammar names the deity"]
  CRY -. "optional · LLM rewords<br/>only the surfaced line" .-> VOICE["'The band now speaks of<br/>Vurok the Open Hand,<br/>who sends the berries.'"]

Reflection fires when a burst of formative experience accumulates (summed memory importance crosses a threshold), clusters recent high-importance memories by theme, and reinforces a belief — a confidence float that decays with a ~9-beat half-life, is reinforcement-capped per beat so a flood of repeats can’t spike it, and spreads by complex contagion (a listener must be near and trust the believer). When ≥34% of the living band independently holds one theme above confidence, it crystallizes into a named deity — the name generated by a seeded grammar from theme + worldSeed, so the same seed always births the same god. Real hardship feeds the same machine: the two dread themes (famine, beast-omen) are authored by the world itself, and sustained band-wide stress can tip into scapegoating — bonds collapsing onto one chosen soul. The most emotionally loaded behavior in the system adds zero per-beat LLM calls.

Cost and safety as a first-class layer

A public agent demo on the owner’s API key is a liability before it’s a feature. The guardrails are in the code from commit one, and they’re layered:

  • Per-instance rate limits and spend ceilings on each serverless function: the decide endpoint caps at 180 req/min with an ~$8 estimated-spend ceiling; the converse endpoint holds its own separate 20 req/min and $0.50 budget so dialogue can never cannibalize decisions. BYOK callers are metered on a separate, looser counter and bill their own key.
  • Cheap tier by default, flagship only for drama — routine beats run Haiku/gpt-4o-mini; the dramatic model tier is reserved for agents who just witnessed a wonder.
  • Cache-friendly by construction. The system prompt is byte-stable (persona is the only interpolation; all volatile data lives in the user message), and the code deliberately uses generateText + code-side JSON parsing instead of tool-mode structured output — because tool mode invalidates Anthropic’s prompt cache on every call (vercel/ai#5227). The schema still validates via Zod safeParse; a failed parse is just null, which means “deterministic this beat.”
  • Defense in depth on outputs: schema parse → code-side length clipping → the engine independently re-validates every label (digit guard, length, enum membership) before acting. Three layers deep, the model still can’t move a coordinate.

Determinism as the reproducibility contract

The no-LLM world is byte-identical per seed — a property held under test, not by hope. Every stochastic system (monster spawns, the director, myth naming, conversation rotation) draws from its own mulberry32 PRNG keyed on (seed, beat/day) rather than a shared cursor, so systems can’t perturb each other. All LLM-fed state — plans, goals, vendetta targets — exists only on the LLM path, so the deterministic guarantee survives every cognition feature. The action enum lives in three places (shared engine, standalone serverless function, dev server) and a parity test parses the serverless source to assert set-equality, so schema drift is caught at dev time rather than shipping as a silently-dead brain. The suite is ~400 tests, including a 14-simulated-day balance run asserting the free brain keeps ≥35 of 60 souls alive with at least one autonomous construction project.

Honest status

Deployed and live: a 1600-unit procedural island with nine real biomes (~370 resource nodes, biome-keyed — jungles feed, rocklands quarry), sixty agents with the full perceive→recall→decide→act→remember→reflect loop, discoverable groves and ruins, whisper vendettas verified end-to-end, executed plans verified against real models, and a deterministic brain watchable indefinitely at zero spend. Honest warts: sixty souls cycle twelve names (so “Halek talks to Halek” happens and makes named whispers ambiguous), cooperation is stigmergic rather than negotiated, and the sim still runs on the render thread. Those are next, and they’re scoped. The discipline is the one I hold everywhere: keep the deterministic core honest and reproducible, and let the LLM be the smallest, most-rationed, most-replaceable part of the machine.

Stack

TypeScript · three.js · simplex-noise · Vite · Vercel AI SDK · Zod · @ai-sdk/openai + @ai-sdk/anthropic · deterministic seedable Sim (byte-identical per seed) · Generative-Agents memory stream (importance-weighted eviction, relevance·importance·recency retrieval, no embeddings) · utility-AI daytime brain + survival floors + AI-director pacing · plan-and-execute LLM cognition (typed ≤3-step plans, reactive interrupts) · trust-gated rumor model with provenance · deterministic belief→myth crystallization with seeded name grammar · salience-gated LLM scheduling · per-instance rate-limit + spend-ceiling guardrails · Vercel serverless functions.