DEV Community

Cover image for I got tired of paying for AI NPCs, so I built my own runtime - now it's more than that
BearlyStable
BearlyStable

Posted on

I got tired of paying for AI NPCs, so I built my own runtime - now it's more than that

I've always liked the idea of NPCs that actually remember things - not scripted dialogue trees, but characters that hold a real sense of who they are, what happened to them, and what they believe, across an entire playthrough. So a while back I went looking for existing products that add AI to game NPCs. There are some solid ones out there. But almost every one I found had the same catch: you're locked into their hosted model, their API, their pricing. No option to point it at a local model. That mattered to me for a very unglamorous reason: I was broke. I couldn't justify a recurring AI subscription just to tinker with NPC behavior in my spare time. What I did have was a machine that could run a local model through LM Studio or Ollama. So the seed of Empneon wasn't "let's build a cognitive architecture for game characters" - it was "let me hack together something that lets me use the model I already have, for free, on my own computer." That's genuinely how it started: a personal tool, for myself, born out of being cheap rather than any grand vision.

Where it went from there

Once I had characters talking to a local model, I ran into the problem that I suspect is why most commercial solutions don't expose raw model access in the first place: language models lie. Not maliciously - they just generate plausible text, and if you treat that text as ground truth, your NPC will confidently "remember" things that never happened. Say a character decides to hide a key in the barn, and if you're not careful, the model's own sentence about hiding the key becomes canon - whether or not the in-game action actually succeeded. Fixing that properly meant building actual infrastructure: separating what a model proposes a character should do from what the game confirms actually happened, and only writing confirmed outcomes into memory. Once I had that, I needed identity, relationships, emotional state, and recall to make the memories mean anything. Then I needed a way to avoid burning compute on every trivial interaction, so I added tiered cognitive levels - from cheap deterministic rules up to full model reasoning - with a resource governor that admits requests against declared RAM/VRAM/latency budgets instead of just firing every request at the biggest model available. Somewhere in there, "personal hack so I don't have to pay for a subscription" turned into a project with a real architecture. And once it had one, it stopped feeling like something only useful to me.

What Empneon is now

Empneon Core is a game-agnostic cognitive runtime: identity, memory, relationships, and grounded decision-making, backed by either deterministic rules or a language model of your choice — local or hosted, your pick. You can install it with:

pip install empneon
Enter fullscreen mode Exit fullscreen mode

Provider support isn't tied to any single vendor. Point it at LM Studio, Ollama or any OpenAI-compatible endpoint, or a cloud API key, or write a small adapter for whatever you're already running. That flexibility is still the core design principle it started from.

Current status, honestly:

  • Core — beta. The identity/memory/decision runtime is the most exercised part of the project and where most of the design effort has gone.
  • Sims 4 adapter — alpha. Functional, less battle-tested than core.
  • Godot 4 adapter — proof of concept. Enough to prove the core contracts are genuinely engine-agnostic, not production-ready yet. I've tried to be upfront in the docs about what's implemented versus what's still a hypothesis - including a case where an early adaptive-scheduling policy I built failed its own predeclared success threshold until I tracked down a flawed workload design. I'd rather show that than pretend everything landed on the first try.

If you want to poke at it

Everyone's welcome to try it, break it, and tell me what's wrong with it. I'd genuinely like feedback from people who've hit similar problems trying to give game characters persistent behavior, especially if you're running local models and have felt the same "why can't I just use what I already have" frustration that started this whole thing. Contributions are welcome too - whether that's a new provider adapter, a new game adapter, or just filing an issue about something confusing in the docs.

  • Repo: https://github.com/mf-rl/Empneon
  • License: Apache 2.0
  • Requirements: Python 3.11+ Happy to answer questions about the architecture, the propose/confirm memory model, or why I went with typed decisions instead of parsing raw model text, in the comments.

Top comments (0)