Inspiration

In most games, every match is a clean slate. The NPC who lost to you five times greets you like a stranger. We wanted the opposite: AI rivals with receipts. Same opponent, different history.

What it does

Soul Link is a turn-based anime 1v1 arena where both fighters are autonomous AI agents. You pick two of six fighters and watch them go:

  • Each turn, a fighter picks one of five moves (light, heavy, special, block, dodge) based on its personality, the live HP, what just happened — and its memory of past fights against this exact opponent.
  • When a match ends, a summary is saved to long-term memory. Next time those two meet, the grudge is real: Rei remembers Kaito always opens heavy, so she opens with a dodge and calls him out for it.
  • Every fighter trash-talks in short, Unreal-Tournament-style one-liners, voiced by a custom-designed voice per character, with an arena announcer calling FIRST BLOOD, RAMPAGE and GODLIKE.
  • Matches end with a blood-red victory screen, a K.O., and the result written straight back into memory.

How we built it

  • AWS Bedrock (Claude): one decision per turn. Claude must answer through a strict tool schema — exactly one of 5 legal moves plus a ≤8-word taunt — so it can't go off the rails.
  • SurrealDB Agent Memory: every finished match is written as a plain-English summary; SurrealDB extracts entities and actions (who dodged what, who won). At match start we query it and keep only memories for this exact pair of fighters.
  • Daytona: the whole FastAPI backend — and the hosted game itself — runs in one Daytona sandbox behind a public preview URL.
  • Arize AX: every turn is one AGENT span with readable inputs (HP, memories used) and outputs (move, taunt, damage), the Claude call nested inside, and each match grouped as a session — so any weird decision can be audited.
  • ElevenLabs: 7 voices built with Voice Design from character briefs (a Nordic dwarf, an ancient elven master, a smug shadow assassin…), gender-matched and pitch-checked, with fixed seeds so each character keeps the same voice. TTS is proxied through the backend so the API key never reaches the browser.
  • Frontend: plain Vite + vanilla JS, pixel-arcade style: comic move callouts, floating damage numbers, hit flashes, announcer banners, and original arena-techno music synthesized live with the Web Audio API.

Safeguards

  • The agent can only act through 5 legal moves; the server, not the model, resolves damage.
  • 40-turn cap; timeouts and safe fallbacks if the LLM or memory is slow or down.
  • PG-13 trash-talk rule; fighters may only quote stats that actually appear in their prompt — no invented history.
  • Memory is filtered to the exact matchup, so fighters never act on someone else's history.
  • All keys stay server-side; the TTS proxy only accepts our 7 voices and short lines.
  • Every decision is traced in Arize.

Challenges we ran into

  • Semantic memory leaked across matchups: a brand-new pairing retrieved another rivalry's fights. We now filter results to facts that name both fighters.
  • Sandbox networking: our Daytona org tier initially blocked outbound calls to SurrealDB; after an upgrade, stale environment placeholders broke auth until we made .env authoritative.
  • Voices that didn't match characters: a voice designed as an "ethereal elf" came out sounding female. We wrote a small pitch analyzer to measure every voice and regenerated until each one matched.
  • Noisy traces: framework auto-instrumentation buried the real decisions under untyped HTTP spans; we disabled it and added one meaningful span per turn.

Accomplishments that we're proud of

  • Grudges you can actually see: the memory panel, the moves, and the taunts all change based on past fights.
  • Five sponsor tools working together in one live loop, end to end, publicly hosted.
  • Seven distinct character voices that stay consistent line to line.

What we learned

  • Memory is only useful if retrieval is scoped — "similar" isn't the same as "relevant".
  • Constraining an agent (strict schemas, server-side rules) makes it more fun to watch, not less.
  • Good traces are designed, not just enabled.

What's next for Soul Link

  • Measure the effect of memory with controlled matches (with vs. without history).
  • Register per-matchup memory scopes so filtering happens in SurrealDB.
  • Tournaments and leaderboards where grudges carry across a whole bracket.
  • Let players coach their fighter between rounds.

Built With

  • anthropic
  • arize
  • aws-bedrock
  • claude
  • daytona
  • elevenlabs
  • fastapi
  • javascript
  • python
  • surrealdb
  • vite
  • web-audio-api
Share this project:

Updates

Submission history