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:
| Task | Model | Why |
|---|---|---|
| Per-tick actions, importance scoring | qwen-flash | the firehose — every memory gets scored |
| Dialogue, social posts | qwen-plus | needs voice, not genius |
| Daily plans, reflection synthesis | qwen3-max | rare, deep, worth paying for |
| Memory embeddings (1024-dim) | text-embedding-v4 | relevance 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.

##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.
##Links
- ▸Live: qwensociety.andresio.com
- ▸Repo: github.com/AndresCarreonDiaz/QwenCity
- ▸Built for the Global AI Hackathon Series with Qwen Cloud, Agent Society track.