Skip to content
Back to Writing
[ WRITING / LOG-2026-008 ]

I Ran an AI Soap Opera on Qwen Cloud for a Week. It Cost $0.36.

A builder's journal from the Qwen Cloud hackathon: building Qwen City, a 24/7 town of generative agents whose memories the audience can rewrite — and what the usage dashboard taught me about my own architecture.

6 min read·READ 0%

A builder's journal from the Global AI Hackathon Series with Qwen Cloud — Agent Society track.

(Honest accounting up front: $0.36 is what I'll actually pay this month; free-quota credits absorbed roughly $21 more. The full breakdown is at the end.)

##The itch

When Stanford's Generative Agents paper (Park et al., UIST 2023) came out, I did what everyone did: watched the Smallville replays. Twenty-five agents with memory, reflection, and planning — and I kept catching myself wanting to reply. Smallville is a closed replay; nothing you feel about the characters matters. I wanted the inverse: a live, perturbable society where an outside message becomes a real memory with a measurable causal effect. Qwen City is that experiment dressed as a 24/7 pixel-town soap opera. Eight residents, a broadcast camera, a town feed. You type a reply; if it clears moderation, that character's next decision carries a flag on the broadcast: acting on your message.

##July 8: building against a model that doesn't exist

My DashScope API key was still pending when I started, so the rule became: build offline-first. Every model call goes through one ModelAdapter interface, and the first implementation was a deterministic mock: bag-of-words hashed embeddings, a poignancy heuristic, completions that visibly quote retrieved memories. The entire cognitive loop (perceive → store → retrieve → reflect → plan → act) was built and tested against it. The suite is a moving number — 75 tests at go-live, 117 today — but it has always run in under a second.

The core is one importance function scoring every memory: I(m) = 0.5·recency + 3·relevance + 2·importance, the released Generative Agents weighting. It ranks the top-8 memories pulled into every decision, and it earns its keep twice: the day's top-importance beats are the daily recap. No separate editor model.

Audience replies enter the same economy with a +2 importance bias. That alone wasn't enough. In early testing, the first injected reply surfaced in retrieval but ranked #2, so the agent answered someone else's thought. Now a pending reply is force-surfaced into the character's very next decision, which turns the causal loop from probable into guaranteed. Measured in an ablation: one moderated reply changed 25% of the society's next actions — concretely, two of the eight agents changed course — versus 0% in the deterministic no-audience control.

##One environment variable

When the key landed, going live was MODEL_BACKEND=dashscope. DashScope exposes an OpenAI-compatible endpoint, so the production adapter is a thin fetch with Bearer auth, retry on 429/5xx, and task→model routing. The live smoke test passed on the first try: text-embedding-v4 returned a real 1024-dim vector, qwen-plus said "Hey there—great to see you again!", and qwen-flash scored a poignant memory 9 and a mundane one 1. The seam held.

One thing the mock could not teach me was calibration. The mock compressed importance scores to 5-or-1; live Qwen scores 6–9. Every threshold I tuned offline had to be re-tuned against the real distribution. Reflection — the paper's default trigger is 150 accumulated poignancy — runs at 200 in the live town, or the cast would be having epiphanies hourly.

##Bugs only a running society can show you

Reflection produced zero insights for an entire iteration. My parser expected insights shaped like insight (because of 1, 5) — with the citation anchored to end-of-line. Every model, mock and real, emits a trailing period. Un-anchoring one regex took the town from 0 reflections to 9. Nothing in the code review would have caught it; the town just quietly never thought.

The mock's embeddings hash-collided into nonsense. At 64 dimensions, a seeded rumor scored 0.0 relevance while a memory about the weather scored 1.0 — and relevance is weighted 3×, so the weather won every retrieval. Raising the space to 1024 dims (matching text-embedding-v4) fixed the ranking. Then gossip degraded like the telephone game: each retelling nested attribution (X said Y said…) and a 90-char truncation clipped the actual fact off the end. A coreFact() unwrapper let a rumor survive any number of retellings — verified by a diffusion sim where it spreads 1 → 2 → 3 → 4 through a conversation chain.

##Routing by cognitive load

The Qwen tiering is the part I'd defend in a design review. Not every thought costs the same:

TaskModelWhy
Per-tick actions, importance scoringqwen-flashthe firehose — every memory gets scored
Dialogue, social postsqwen-plusneeds voice, not genius
Daily plans, reflection synthesisqwen3-maxrare, deep, worth paying for
Memory embeddings (1024-dim)text-embedding-v4relevance vectors for every node

The world runs in fast-forward — a 30-sim-minute tick every 5 real minutes — but within a tick, cognition is event-driven: agents only think when something around them changed. Cheap models do the constant noticing; the expensive model thinks a few deep thoughts per day.

Qwen City architecture: audience and frontend, backend on Alibaba Cloud ECS, Qwen Cloud Model Studio

##July 15–16: the city-planning phase of my life

The town grew from 4 residents to 8, then the map grew to match: five new districts, +47 buildings, +200 props. Then came the feedback: "several buildings are in the middle of the road." I had been eyeballing zoomed-out screenshots, where overlaps hide. A collision script computing real sprite footprints against road bands found 32 overlaps my eyes had missed. The fixes were humbling urban planning: a whole avenue deleted because no east-west road actually fit between uptown's two rows (it's a pedestrian plaza now), Leo's house swapped for a narrower sprite because it was crashing into the tailor. Don't trust a glance at the whole map; crop and inspect every region.

##Deploys that look like outages

The whole thing runs on a single Alibaba Cloud ECS instance behind nginx with a Let's Encrypt cert. The server binds its port only after ~3 minutes of world initialization, so every deploy reads as a 502 until you learn to poll /health and breathe. And an uncapped memory store would have grown the heap unbounded across the judging window — importance-based forgetting now caps it at 6,000 nodes, meaning the town literally forgets its most boring moments first. I find that architecturally and personally relatable.

##What the usage dashboard taught me about my own architecture

The Model Usage console over the last week: 233K successful calls, 26,292K tokens, an average of 113 tokens per request across four models. These are self-reported console numbers, not an audited invoice, but they read the architecture back to me. Tiny requests at enormous frequency is what a salience engine looks like on the wire: high-frequency cognition, not long-form generation.

The per-model split is the tiering made visible: text-embedding-v4 117,224 calls, qwen-flash 105,253, qwen3-max 7,167, qwen-plus 3,820. Embeddings and flash-scoring dominate; the expensive model is 3% of traffic, exactly as designed.

And the bill: $0.36 payable this month, with free-quota credits absorbing the roughly $21 of catalogue cost I flagged up top. A living town — eight minds remembering, gossiping, reflecting, and occasionally being steered by a stranger on the internet — for the price of a sip of coffee. That's the number that convinced me this architecture isn't a hackathon trick.