Inspiration
AI agents act like a haunted house: something happens, you can't see why, and running it again gives a different result. That makes them hard to trust anywhere a decision matters: pricing, support, games, operations. We wanted agents whose every decision can be replayed exactly and questioned with "what if?".
What it does
Hauntrace is a lean skeleton for deterministic, explainable agents. You define typed state, observations, ordered rules and a fallback. A seeded RNG and an injected clock make runs reproducible, and each step leaves a "ghost": a hash-chained record of the input, the rule that fired, the reason, and the state before and after.
- Replay re-runs a trace and confirms it matches exactly.
- verifyChain detects any tampered step.
- What-if changes inputs and shows which decisions would differ.
- Adapters: export/import trace files, an HTTP endpoint, and an LLM tool adapter where a model proposes an action and the rules decide, flagging every place the rules overruled the model.
Two distinct apps run on the same skeleton:
- Short-let pricing (
apps/shortlet-pricing): nightly prices from rules like last-minute, weekend, high demand and event surge, on example data. - Haunted innkeeper NPC (
apps/haunted-npc): a ghost innkeeper whose dialogue, mood and trust are rule-driven and seeded. Ask "what if the player had brought the lantern?" and watch her answers change.
The Séance viewer is a static web app that loads any exported trace (or the bundled examples): a step timeline, step detail, a hash-chain verified/broken badge, and a what-if ghost diff that re-runs the real rules in the browser. No network calls, no analytics, built to work on an iPad.
How we built it
Spec-driven with Kiro. .kiro/steering/ holds the product, tech (determinism rules: no hidden I/O, no Date.now/Math.random) and structure guidance; .kiro/specs/ has requirements, design and tasks for core-engine, shortlet-pricing and haunted-npc; .kiro/hooks/ runs the determinism tests on save.
After the initial scaffold, the Kiro CLI implemented spec tasks one at a time from the repo root, so it loaded the steering and specs: trace export/import, the HTTP adapter, the LLM tool adapter, the Séance viewer, viewer polish, and the whole haunted-NPC app (Kiro wrote that spec first, then built it). Each step went through a reviewed pull request with tests. KIRO_USAGE.md logs every session and any hand edits.
Honest note: the initial scaffold (core tasks 1–5 and the short-let demo) was written outside Kiro, following the Kiro steering and specs. Everything after that was built by Kiro; hand edits were limited to docs and one npm script line.
Challenges we ran into
- Keeping everything truly deterministic: no hidden clock or randomness anywhere, so replay matches byte for byte.
- Making the viewer's what-if re-run real agent rules in the browser. An early version only patched inputs and never re-ran the rules, so every scenario showed "no change". Review caught it and Kiro fixed it.
- Kiro CLI service throttling cut off a few long sessions; we waited and resumed with short "continue" prompts.
- Making raw trace data readable on an iPad screen.
Accomplishments that we're proud of
- One skeleton, two genuinely different apps: pricing and a game NPC.
- Tamper-evident, replayable traces with what-if, viewable in a browser with no server.
- 299 passing tests at the time of writing (139 engine and app tests, including property-based determinism tests, plus 160 viewer tests).
- A transparent record of how Kiro was used, including what wasn't Kiro.
What we learned
Writing the spec before the code made Kiro's output much more predictable, and small focused prompts worked better than big ones. Determinism is a design choice you make up front: inject the clock and the RNG and almost everything else gets easier to test.
What's next for Hauntrace
More demo apps (e.g. support ticket triage), a trace diff between two real runs, publishing the core as an npm package, and an MCP server so agents can call Hauntrace as a tool.
Built With
- fast-check
- kiro
- node.js
- typescript
- vercel
- vite
- vitest
Log in or sign up for Devpost to join the conversation.