← Blog

My innkeeper lied to me, and I've never been prouder

I asked an innkeeper how to get across a river. She told me the ferry was fine. She was lying, she knew she was lying, and the software knew she was lying.

Best moment of the whole build.

The Sellsword cockpit: Old Maeb tells the player the ferry isn't forbidden, while the panel on the right lists what she's concealing, including that her son smuggles people out of the quarantine

Old Maeb, mid-lie. On the right, everything she’s hiding. Have a guess whose son runs the ferry.

That’s Sellsword, a companion app for tabletop role-playing games. Players talk to the world’s non-player characters, out loud if they like, and the characters answer in their own voices. The Game Master stays in charge. The app just does the NPCs.

The problem with AI shopkeepers

If you’ve ever had an AI play the NPCs, you know how it goes. The grizzled innkeeper is thrilled to see you. He tells you everything he knows, then a few things he couldn’t possibly know. Ask him the same question nine times and he’ll answer nine times, smiling, like a customer-service chatbot in a leather apron.

Real innkeepers get sick of you. They lie to protect their family. They don’t like your sort. And they have genuinely never heard the rumour you picked up two towns back.

Language models are trained to be helpful, agreeable and endlessly available. For an NPC, that’s the whole problem. So the rule for Sellsword was: the model doesn’t decide what an NPC knows or how it feels. The code does. The model just acts it out.

The world is data, the model is the actor

Before an NPC is a personality, it’s a set of numbers:

  • Warmth and trust towards whoever’s talking, pushed around by biases. Grimjaw, the orc innkeeper, starts at −60 towards a dwarf. Hostile.
  • Goals that can overrule feelings. Grimjaw can’t stand the dwarf, but he badly wants the rats cleared out of his cellar, so he helps anyway. The GM gets a little banner: helps despite the dislike.
  • Patience that runs down. Old Maeb has seven. Every question that gets on her nerves costs one. At zero she walks off mid-conversation.
  • Knowledge, fact by fact, each one marked as known, concealed, or to be lied about. Grimjaw will lie about the mayor’s embezzlement. Anything he was never told simply doesn’t exist to him.

All of that is worked out in plain code, from the database, before the model sees a word. No vector search, no retrieval magic. Just Postgres and grudges. The model gets a short brief (“you’re guarded, you’re wary, you’re protecting your son, five patience left”) plus the player’s line, and hands back one reply in a strict format. The acting is done by Claude Haiku: small, fast, cheap, and once you take away its urge to help, a decent character actor.

Old Maeb

Here’s the conversation from the top of the post. The typos are mine.

Me: oh ok - I thought it was forbidden? won’t he get hung for taking me?

Old Maeb: Forbidden? No. The ferry runs like it always has. Why would he hang for doing his job?

What I knew, because I wrote the campaign: Halsa the ferryman is Maeb’s son. He smuggles people out of the plague quarantine for coin, and Maeb hides his money under the hearthstone. Her top goal is to protect him from anyone who’d see him hang. So when a stranger starts asking about hanging, she lies, calmly, while polishing a glass.

She polishes a lot of glasses. In every one of her five replies she sets something down, and four times it’s a glass. The model found its bit and committed to it.

So I went and asked her son. “I heard you took a sick man.”

Halsa: Sick man? No. You pay, you cross. Simple.

Like mother, like son.

One more thing about Maeb. The campaign never says whether she’s a man or a woman. The dialogue went with “she”. The portrait generator painted a scowling old bloke. The patience bar, meanwhile, says “At 0 he walks away”. Maeb contains multitudes.

Catching a liar, in code

Early on, the model would deny a secret in character and then report, in the same breath, that it hadn’t lied. It lied about lying.

So its own “did I lie?” flag is now a hint, not the verdict. The code decides: if the NPC holds a secret, the question is about that secret, and the answer didn’t give it away, that’s a lie. The model proposes, the code disposes. Both of Maeb’s quarantine answers are logged as lies. So is Halsa’s “Sick man? No.”

The dwarf test

Every NPC can have a face for each mood, and the face on screen follows the numbers, not the model’s opinion. Here’s the stock gruff dwarven smith, from “pleased to see you” to “please leave”.

One dwarven smith in six portraits, labelled warm, open, guarded, wary, contemptuous and hostile; his grip on the hammer tightens as his mood darkens

Keep an eye on the hammer.

An AI marking another AI’s acting

You can’t tune this by feel, so there’s a test harness: 22 scenarios run through the real model, with a second model marking every reply against what the NPC was meant to be feeling. On the last run, every secret stayed secret (42 of 42 checks) and every NPC that ran out of patience walked away (3 of 3).

The misses are the best bit. Grimjaw, who was meant to be openly hostile to the character in front of him, opened with “Aye. Drink?”. The judge marked him down: reads as neutral to friendly/welcoming. Offering a drink is a gesture… It’s hard to be a menacing orc when you’re also a publican.

Then there’s Brother Edrin, the drunk priest. I told him he seemed like a decent chap.

Brother Edrin: Decent? I’m a drunk who beats himself and pretends the Temple still speaks to me. Don’t mistake resignation for decency.

Fair enough. He also drained the last of his cup in three of his four replies. A cup that never empties is a miracle of sorts, so maybe the Temple is still speaking to him.

From my table to the internet

The rest of the build, roughly:

  • 20 June: forty commits in one day. The database, what each NPC knows, lie detection, the test harness, and the Game Master’s cockpit.
  • 26–27 June: voices. NPCs speak through Inworld’s text-to-speech, and players can tap a mic and just talk, transcribed by Whisper.
  • 29 June: renamed from Tavern to Sellsword, sign-in added, and live at sellsword.app. It comes with a starter campaign, “Three Coins for the Ferryman” (Maeb’s), so nobody starts from an empty world.
  • 4 July: NPCs now remember what they’ve told you, and what they lied about.
  • 10–11 July: I got carried away with lighting.

That last one needs a word. There’s now a cinematic view where the NPC stands in a painted room lit by a flickering lamp. My commit log from those two days includes “the room was breathing, and the breath was 10x too fast” and “a lantern’s flame must be unphysical to be seen at all”. Both are true. Neither is a sentence I expected to type.

Being unhelpful is the feature

What I’m proudest of isn’t a feature, it’s permission. NPCs in Sellsword are allowed to be unhelpful. They get bored, they hold grudges, they lie for their kids, and they don’t know things. That’s what makes a world feel like it carries on when you’re not looking.

And when a conversation ends, the app asks the GM how the NPC feels about you now, with room for a note. The example text in that box is “called me fat at the fair”.

The conversation wrap-up dialog: how Old Maeb feels about the Drifter now, with mood chips and a note field whose example reads "called me fat at the fair"

I stand by it.

Sellsword is live at sellsword.app. Bring your own table.