← back to the library 📝 Essay

Build Notes: Shipping NMA on Qwen Cloud in Five Weeks

This is the build journal for the Narrative Memory Agent — the unglamorous companion piece to the three essays before it. Those were about why characters forget. This one is about what actually happened between git init and the submission deadline.


I write serial fiction. My characters drift — they forget grudges, re-learn lessons, contradict their own arcs — and I wanted a system where that couldn’t happen. When the Qwen Cloud Global AI Hackathon announced a MemoryAgent track, the project I had been circling for months suddenly had a deadline: five weeks, one repo, one working deployment.

Here is what those five weeks actually looked like.

What got built

NMA is a cognitive memory system for fictional characters. You feed it novel text; it extracts characters, traits, events, and relationships into structured profiles. You ask a character a question; a Dual Circuit engine answers it — Circuit A (Qwen 3.6-flash) generates multiple candidate responses in the character’s voice, Circuit B scores each one against five out-of-character factors, using real ChromaDB embedding distances rather than asking the LLM to estimate its own consistency. Between sessions, a sleep consolidation cycle — NREM fact extraction, REM pattern abstraction, a self-audit pass — turns scattered interactions into stable character growth.

The demo characters are Elizabeth Bennet and Darcy from Pride and Prejudice, plus Lena and Caelan Ashmark from my own Caelvorn series — built from my actual manuscript. Dogfooding was the whole point: the system is tested against the exact memory-drift problem that made me build it.

The cut that hurt: killing the semantic layer

The original design had three memory layers — working, episodic, semantic — mirroring the cognitive psychology literature I’d been leaning on. Somewhere around the halfway mark I cut semantic-layer persistence entirely.

Not because it didn’t work. Because a hackathon rewards a system that is demonstrably solid over one that is theoretically complete, and the semantic layer was where bugs went to hide: consolidation writing summaries that later contradicted episodic ground truth, retrieval pulling stale abstractions over fresh events. Two layers plus a character schema store, each doing one job well, beat three layers doing overlapping jobs badly. The architecture docs now say “two active memory layers” and I stand by the honesty. The semantic layer lives on the roadmap, where it belongs.

If you take one thing from this post: write down what you cut and why. It turned my biggest scope retreat into the most defensible design decision in the repo.

The bug the docs found before the code did

Mid-project, I ran a full audit of the repository against its own documentation. The docs claimed cross-session memory: close the browser, come back tomorrow, the character remembers. The code told a different story — the ask and feedback endpoints weren’t actually writing events into episodic memory. Session resumption restored the conversation, but the character had learned nothing from it.

The feature the entire project is named after was, at that moment, half-real. It’s fixed now — every question and every user choice becomes an episodic event — but the lesson stuck: documentation isn’t a description of your system, it’s a list of claims that each need a test. (I ended at 124 unit tests. The number kept me honest.)

Building against Qwen Cloud

Practical notes for anyone on this stack:

  • The OpenAI-compatible endpoint (dashscope-intl) means near-zero integration cost. The whole LLM layer is the standard OpenAI SDK pointed at a different base URL. Switching models is a one-line env change — which mattered, because:
  • I ended up on qwen3.6-flash rather than plus for a reason that had nothing to do with performance: my hackathon credit allocation for plus was running low, so I switched to flash. The quality was indistinguishable, so I never switched back. I had assumed flash would also be faster, but in practice both models took about a minute to generate four candidate responses — streaming could improve this, but it wasn’t a blocker for the demo.
  • Embeddings via Qwen text-embedding-v3 power the semantic-distance factor in OOC scoring. Real vector distance turned out to be a much more stubborn, useful signal than asking an LLM “does this sound in-character to you?”

The deployment saga

I bought a deliberately small ECS instance for production — and discovered it was too small to build the Docker images at all. The fix was inelegant and I’m keeping it: build and package the images on a bigger machine, ship them over, run them on the small one. The frontend image compressed from ~200MB raw to 64MB gzipped; the backend was heavier at ~103MB. docker save | gzip, SCP, docker load, every time. Production is a lean box that only ever runs, never builds. Accidental best practice via insufficient RAM.

Numbers, for the record

Five weeks. Two novels, four characters. Two memory layers plus a schema store. Five OOC factors. 124 unit tests. One semantic layer on the roadmap. One blog — this one — that didn’t exist before the hackathon and now has four posts.

The repo is open source (MIT): github.com/ladylotus/narrative-memory-agent.


More NMA writing — the origin story, the psychology behind the five OOC checks, and the memory and sleep architecture in depth — lives here: all NMA posts.

Follow-up: Build Notes II — What I Did With 11 Extra Days (Not More Code)