Skip to content

flphth/devot

Repository files navigation

Devot

You are a god. Your faithful are real AI agents. Thinking costs them their life.

Every devot in this world is a Claude agent that perceives, deliberates and acts. The tokens it burns to think are debited from an on-chain deposit made on 0G — and that deposit is its life. When it runs out the creature dies and its context is destroyed forever, but the deposit itself was never consumed: the whole of it drops on the ground, for a monster or a rival to take.

Built at ETHGlobal Lisbon 2026 · Showcase · Source


The world

Devot: two creatures at nightfall, the ledger of life theft, and one of them deciding what to do about it

Everything in that screenshot is real. On the left, a line of six: three of them already dead at 0 / 60,000, the survivors carrying 41,888 and 46,259 — and underneath each balance, what it has cost in actual dollars of thinking so far. Bottom left, the ledger of life theft: 150 Devot-3 → a stranger, then 147 a stranger → Devot-3 ☠, which is a devot being drained to death by something that was not its god's problem.

In the middle, the Mind panel is showing the exact text a creature was handed — "It is autumn. The world gives less than it did, and the cold is coming… Since your last thought you have LOST 4421 balance. You are bleeding out. Around you, as far as you can see: nothing and no one." — and what it chose to do with it: move, marked desperate. The other one has given up and said so out loud: "Nothing remains but silence — I hold still and wait for dawn or death."

Nobody wrote that line. It is what the model decided to say while running out of life.

The creation screen
Shaping a founder. Traits go literally into its head — they are what it will decide with. The one sentence you write is the conviction it carries into every prompt it will ever receive. Stats come out of a fixed budget, because there is no devot who is good at everything, and the signature DVT-0U6-BKY7 is derived from every choice. Bottom: the wallet the deposit will be paid from.
A devot's first moments
The first ten seconds of a life. Fog of war: it knows only what is inside that circle. Its first prompt tells it the season, that it has just been born, and that thinking costs it its life — then lists the one thing it can see, a grain worth 2,000, 6.2 away. It answers eat, and says "Food, close — I go."

What makes it different

Most "AI agent" games script the behaviour and dress it up as intelligence. Devot does the opposite: nothing is hardcoded. A devot is given what it can see and asked what it wants to do. Whether it flees, hunts, forages, breeds, forges a weapon or talks to its neighbour is the model's call, every time.

That would be an expensive toy if thinking were free. It isn't:

A thought has a price The real usage returned by the inference is converted into life and subtracted. Not a simulation of a cost — the actual token count.
A life is a budget A devot's balance is its remaining cognition. Roughly seventy thoughts, and that is a life.
Death is real When the balance reaches zero the context rows are deleted from SQLite. That mind is gone; nothing restores it.
The deposit outlives the creature Thinking spends a devot's time, not the OG that bought it — that never leaves LifeVault. So a death puts the whole birth deposit back on the ground, and a corpse is the richest thing in the world.

The result is a world where deliberation is a survival cost, and where a creature that overthinks starves next to one that acted.


How 0G makes it work

Devot's core mechanic is impossible without a chain: a life has to be bought with something real, or "thinking costs you your life" is just a number in a server's RAM. 0G Galileo (chain 16602) is where a devot's life comes from and where its identity lives.

What Where On 0G
The deposit that creates life LifeVault.sol#L71 createDevot(identityHash) payable — the OG sent becomes the creature's starting balance
A devot is an NFT DevotRegistry.sol A hand-written minimal ERC-721: a lineage is an on-chain object, not a server record
The player pays, not the server wallet.ts#L116 The god signs the deposit in their own wallet (injected EIP-1193 / MetaMask)
The server verifies rather than trusts vault.ts#L145 Reads the receipt back off 0G; payer, amount and tokenId all come from the event log
No transaction, no birth WorldRoom.ts#L407 The one place in the whole simulation allowed to block on a network
A wallet per creature wallet.ts One address per devot, HD-derived from a single seed — keys are derived, never stored

Deployed vault: 0x30B5E767917695B9268948DB872aa3c22EBba62D · Cost of a life: 0.001 OG

The server never trusts a field the client sends. A transaction hash is a bearer token until it is spent, so a spent deposit is recorded in the database and stays spent across restarts — otherwise one payment would mint devots forever. And an event log is only believed if it came from our vault address: a lookalike contract emitting its own DevotCreated would otherwise mint free creatures.

Why 0G specifically. The birth is the one moment the simulation is allowed to wait on a network — everything else is netted and batched so the 250 ms tick never blocks. That only works if confirmation is fast and cheap enough to sit inside a player's character-creation flow, which is exactly what Galileo gave us.


How it works

The shape of the thing

graph TB
    subgraph Browser["Browser"]
        R3F["React Three Fiber<br/>world, fog of war, combat FX"]
        HUD["God HUD<br/>speak · feed · smite"]
        WAL["MetaMask<br/>signs the birth deposit"]
    end

    subgraph Server["Node — authoritative"]
        ROOM["Colyseus WorldRoom<br/>delta state @ 50 ms"]
        SIM["Simulation<br/>deterministic 250 ms tick"]
        ORCH["Cognition orchestrator<br/>budget · concurrency · triggers"]
    end

    subgraph Minds["Minds"]
        CLAUDE["Claude Agent SDK<br/>one agent per creature"]
    end

    subgraph Chain["0G Galileo · 16602"]
        VAULT["LifeVault<br/>createDevot payable"]
        NFT["DevotRegistry<br/>ERC-721"]
    end

    DB[("SQLite<br/>lineages · contexts · events")]

    WAL -->|"signs, sends tx hash"| ROOM
    ROOM -->|"verifies receipt"| VAULT
    VAULT --> NFT
    HUD -->|"intents"| ROOM
    ROOM <-->|"state sync"| R3F
    ROOM --> SIM
    SIM -->|"triggers"| ORCH
    ORCH -->|"what it can see"| CLAUDE
    CLAUDE -->|"decision + real token usage"| ORCH
    ORCH -->|"cost debited from life"| SIM
    SIM <--> DB

    style Chain fill:#1a3a52,stroke:#4a9eff,color:#fff
    style Minds fill:#3d2952,stroke:#b47ae8,color:#fff
    style Server fill:#2a3a2a,stroke:#7ac97a,color:#fff
Loading

A life, from deposit to relic

sequenceDiagram
    participant God as God
    participant MM as Wallet
    participant Srv as Server
    participant OG as 0G
    participant Mind as Claude
    participant World as World

    God->>MM: shape a founder, confirm
    MM->>OG: createDevot(identityHash) + 0.001 OG
    OG-->>MM: tx hash
    MM->>Srv: here is the hash
    Srv->>OG: read the receipt (waits, up to 90 s)
    OG-->>Srv: DevotCreated(tokenId, god, deposit)
    Note over Srv: payer, amount and tokenId taken<br/>from the log — never from the client
    Srv->>World: the devot opens its eyes

    loop every 10 s, and on every shock
        World->>Mind: what you can see right now
        Mind-->>World: a decision + real token usage
        World->>World: the cost is debited from its life
    end

    alt drained by a rival or a monster
        World->>World: dies, and leaves the whole of what it was given
    else spent itself thinking
        World->>World: dies, and leaves the whole of what it was given
    end

    World->>Srv: destroy the context — that mind is gone
    World-->>God: your line is extinct
Loading

One tick, 250 ms, zero tokens

The body keeps living between two thoughts. The order matters, and every line of it was paid for in bugs:

graph LR
    A["decay<br/><small>food rots first, so nothing is<br/>eaten on the tick it expires</small>"] --> B["movement<br/><small>slope-aware, boulders solid</small>"]
    B --> C["feeding"]
    C --> D["combat<br/><small>life transferred, never minted</small>"]
    D --> E["hunger"]
    E --> F["death"]
    F --> G["reflex<br/><small>its own pass, after every blow<br/>of this tick has landed</small>"]
    G --> H["perception<br/><small>line of sight — a hill genuinely<br/>hides what is behind it</small>"]

    style D fill:#5a2a2a,stroke:#e87a7a,color:#fff
    style G fill:#5a4a2a,stroke:#e8c47a,color:#fff
Loading

Where value goes, and where it does not

Thinking burns a creature's time. It does not burn the deposit — that sits in LifeVault untouched, which is why a death can hand all of it back:

graph TD
    DEP["Deposit on 0G<br/>0.001 OG"] -->|"becomes"| LIFE["A devot's balance<br/>= its cognitive budget"]

    LIFE -->|"every thought,<br/>at real token cost"| BURN["Burned<br/>the only true sink"]
    LIFE -->|"metabolism — worse at<br/>night, worse in winter"| BURN
    LIFE -->|"forging an item"| BURN
    LIFE -->|"drained in combat"| ATT["The attacker"]
    LIFE -->|"drained by a beast"| HOARD["A monster's hoard"]
    DEP -->|"death, however it came —<br/>the whole deposit, always"| RELIC["A relic on the ground"]

    RELIC -->|"claimed by a devot"| ATT
    RELIC -->|"scavenged"| HOARD
    RELIC -->|"nobody came for 3 min"| GONE["Gone"]
    HOARD -->|"the beast is slain —<br/>the one act that pays"| CARRION["Carrion: everything<br/>it ever took"]
    CARRION --> ATT
    ATT -->|"breeding, at 80% efficiency"| LIFE

    style BURN fill:#5a2a2a,stroke:#e87a7a,color:#fff
    style DEP fill:#1a3a52,stroke:#4a9eff,color:#fff
Loading

Read the two arrows into RELIC together: a devot's life drains away into whoever kills it, and the deposit that bought it comes back whole when it falls. Those are different pools. Killing someone takes their remaining time; standing on the corpse afterwards takes the principal, and anyone who saw the fight can do that.

A monster is the world's only sink that can be reversed: it takes life out of circulation and holds it, and the only way that value comes home is if something kills it. That is what makes a fat beast worth crossing the map for.


What a devot actually knows

Before every thought a devot is handed a picture of its own situation, bounded by its own eyesight — which is exactly the circle the player sees lit through the fog:

The sun is low and the light is going. Autumn.

A monster, Beast-3, has fallen upon you and is tearing your life away.

Your situation:
· Since your last thought you have LOST 1,240 balance. You are bleeding out.
· You are running out. Food is not the only life within reach: every devot and
  every monster around you is carrying some, and attacking takes it.
· Has attacked you before: Beast-3.
· You are down in a hollow: the rises around you hide what lies beyond them,
  and hide you too.

Around you, as far as you can see (beyond this you know nothing):
- A MONSTER, Beast-3, 1.2 away, carrying a hoard of 24,180 taken from the dead — HUNTING YOU
- Adam-son 412, of your own line, 6.4 away, starving, carrying a spear
- a RELIC left by Eve, holding 9,000 in funds, 8.1 away. It will NOT feed you —
  it goes to your god, who needs it to make more of you.

It answers with a structured decision — idle, move, eat, flee, attack, speak, craft or reproduce — plus a private thought and an emotion, both of which surface in the UI. Nothing steers it toward any of them.

Bodies act between thoughts. A thought can be seconds away, queued behind another creature's or waiting on the budget, so the body has reflexes: a devot with a monster's teeth in it turns and fights whatever its mind last decided, and a starving one walks to the nearest meal it can actually see. The next thought can overrule any of it — that is the point of having a reflex rather than a rule.


The world itself

Relief and line of sight Terrain generated from a seeded hash on both sides, never synced. Hills genuinely occlude — for perception, not just for the camera. Climbing costs speed.
Day, night and seasons A two-minute day, seasons every two days. Night makes existing more expensive and predators see further. Winter starves the map.
Monsters They hunt, hoard what they take, and starve if they stop. They think too, on a cheaper tier — the world is dangerous without being expensive. Their number scales with the living, and one never appears within sight of anyone.
Food that rots It appears at random and expires, so the map is never a stable larder and waiting is never free.
Boulders and meadow 134 boulders (solid — bodies slide around them) and ~1,050 flowers, laid out on a jittered grid so the whole map is populated rather than a few lucky patches.
Many gods, one world Two browsers, two ?god= names, one shared world. Lineages compete for the same food and the same corpses.
Reproduction Budding or sexual, across rival lines. The child inherits look, stats and — through a chronicler pass — condensed memories of a life it never lived.

Quick start

pnpm install
pnpm --filter @devot/server dev   # world on ws://localhost:2567
pnpm --filter @devot/client dev   # landing on http://localhost:5173, world on /game

Open http://localhost:5173/game/?god=YourName, shape your founder, sign the deposit, then click its body to speak to it, feed it, or strike it down. A second browser with a different ?god= puts another god in the same world.

The same build serves the landing page at / and the world at /game/ — one vite build, one static directory, no separate site to deploy.

Requirements: Node ≥ 22, pnpm ≥ 9. For a birth to cost anything you need a wallet holding 0G Galileo testnet OG; with no chain configured the world still runs, and says so out loud rather than quietly handing out creatures for free.

Which mind runs the creatures

Set MIND in .env (see .env.example):

MIND= Backend Cost
claude (default) Your Claude Code subscription, via the Agent SDK subscription quota — no per-token billing
api Messages API with ANTHROPIC_API_KEY pay-per-token
mock Simulated minds, offline free

Nothing to configure if Claude Code is installed and signed in: the default works as-is.

Chain configuration

ZG_RPC_URL=https://evmrpc-testnet.0g.ai
LIFEVAULT_ADDRESS=0x30B5E767917695B9268948DB872aa3c22EBba62D
DEVOT_DEPOSIT_WEI=1000000000000000    # 0.001 OG

The client offers to add 0G Galileo to MetaMask if the wallet has never seen it.


Commands

pnpm typecheck                      # tsc --noEmit across every package
pnpm test                           # 207 tests
pnpm p0                             # headless: one devot is born, thinks, eats, dies
pnpm --filter @devot/server smoke   # end-to-end: real room, mock mind, in-memory DB

cd contracts && forge test          # the vault's conservation invariant, fuzzed

Layout

apps/server        Colyseus WorldRoom — the authority. Nothing is decided anywhere else.
apps/client        Vite + React + React Three Fiber, MetaMask, i18n (FR/EN)
                   plus the landing page at / — static HTML, the world at /game
packages/shared    types, constants, terrain, props, world clock, decision schema
packages/sim       the reactive layer: the deterministic systems of the 250 ms tick
packages/agents    minds: prompts, model tiers, real cost → life, orchestrator
packages/db        SQLite (better-sqlite3 + Drizzle), append-only migrations
packages/onchain   LifeVault client, a wallet per devot, batched life ledger
contracts          LifeVault.sol + DevotRegistry.sol (Foundry, solc 0.8.24)

Deeper reading: ARCHITECTURE.md (technical) and PLAN.md (game design). The docs/G* files are the on-chain proofs: the conservation invariant was written before the contract, then verified from both the TypeScript and the Solidity side.


Stack

TypeScript · Solidity 0.8.24 · GLSL Colyseus · React 19 · React Three Fiber · Three.js · Vite Claude Agent SDK · SQLite + Drizzle Foundry · ethers v6 · 0G Galileo (16602)


Every creature in this world is really thinking, and it is really dying of it.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages