DDS Vibe Academy · Masterclass · Advanced
From Prototype to Published
Three AIs. One repo. 26 briefs.
Ashaveth is a free grimdark browser RPG, built by three AI agents working the same repository — Cowork, Claude Code, and a Google Antigravity agent. This is the verified method end to end: how the work was split, how the agents were kept from colliding, and every time the AI was wrong — with the corrections.
A worked case study from a shipped game · Every figure re-derived from the repository · The game is free at ashaveth.ddsboston.com (18+).
Quick answer
This free masterclass reconstructs the multi-agent workflow behind Ashaveth, a published browser RPG. Three AI surfaces shared one repository: Cowork orchestrated, researched, and verified in a live browser; Claude Code wrote, tested, committed, and deployed; a Google Antigravity Gemini agent ran parallel non-colliding work. You learn dispatch briefs, write-allowlists, audit gates, and a full log of the AI's own errors.
Key takeaways
- Three AI surfaces, one repository, 26 dispatched briefs in a single session — average about 3.5 kB, the largest 9,338 characters.
- The split was forced by infrastructure, not preference: a mount that silently truncates reads and writes once committed a 183-line file whose real length was 218. Every commit moved to the agent on the native filesystem.
- Every parallel brief carries a write-allowlist, and containment is proven by a git diff that must return empty — never the agent's self-report.
- Two independent balance passes disagreed — 60–72% against 0–10%. The independent harness was the bug; corrected, it returned 98–100%. Suspect the measurement first.
- The AI was wrong at least six documented times, including inverting a mobile threat model and inventing a quota blocker that did not exist. Each one is logged below with what caught it.
- Only the global ceiling of 5,000 requests/day bounds the bill. Per-user limits shape behavior; they do not protect a wallet.
- An unbounded namespace needs a fallback, not more content: AI-minted item names are infinite, so a 24-cell rarity matrix replaced an impossible art batch.
- A count is only as clean as the tree it ran on: the suite reports 487 with an untracked scratch file present and 480 without it. The committed number is the one that reproduces. Measure a commit, not a working directory.
- Ashaveth v1.1.0 ships 171 art assets, passes 480 unit tests across 20 test files with 0 TypeScript errors, and compresses a 355 kB entry bundle to 117 kB gzipped — every figure produced by running the command, not by rereading the last page.
1 Division of labour
The three surfaces
Ashaveth was built by three AI surfaces sharing one repository. Each was assigned work its constraints allowed, not work it was nominally capable of. Cowork orchestrated, researched, and verified. Claude Code wrote code, ran tests, committed, and deployed. A Google Antigravity Gemini agent took parallel work that could not collide with the code.
Cowork
Claude desktop agent Has: research, live browser, APIs, no terminal on the target box- Research gate before any deploy — found the Chrome behaviour that killed a feature, and sourced the citation study
- Live browser verification through the Chrome extension. Chrome is granted read-only to desktop control, so the extension was the only path — a constraint that shaped the tooling
- Independent Monte-Carlo balance simulations in its own sandbox
- Shopify Admin API: 74 art files uploaded by URL pull, zero byte-shuttling
- Orchestration: writing the briefs, holding gates closed, reconciling audits
Claude Code
Opus 4.8, in the Antigravity terminal Has: the native filesystem, a terminal, the test runner- Every commit and every deploy — see module 7 for why this was not a preference
- All game code: the combat rebalance, the alternate route, the inventory fixes
- The test suite, the production build, the release
- Ran on the user's own subscription, in his IDE, on his machine
Antigravity / Gemini
Gemini 3.1 Pro agent Has: a parallel seat in the same IDE, in-chat image generation- Open-source documentation set, while code work continued in parallel
- SEO metadata and the structured-data graph for the v1.1.0 bump
- Image generation on its free in-chat quota
- Ran concurrently with Claude Code — under an explicit lane (module 3)
Assign work by constraint, not by capability. The most important question is not "can this agent do it" but "what is this agent structurally unable to get wrong?"
2 How work moves
The dispatch loop
Work moved between agents as written briefs, not conversation. Twenty-six briefs were dispatched in one session, averaging roughly 3.5 kB, the largest 9,338 characters. A brief is not a request — it carries intent, constraints, a write-allowlist, and acceptance criteria that can fail.
The orchestrating agent had no terminal on the target machine, so it drove the other agents through the interface: focus the window, screenshot to confirm state, click the prompt, paste the brief. That sounds primitive. It is also why every instruction had to be complete and self-contained — there was no cheap follow-up. Constraint produced discipline.
Every brief has the same four parts. Intent: what "done" means, in one sentence. Constraints: the rules the result must obey, including what not to touch. Acceptance criteria: a command with a pass condition — if it cannot fail, it is not a criterion. Lane: the paths this agent may write.
Write briefs, not chat. Intent, constraints, acceptance criteria, lane. If your acceptance criterion cannot fail, you have written a wish.
3 Concurrency
Lanes, and the diff that must be empty
Two agents editing one repository at once is a merge-conflict problem wearing a speed win's clothes. The answer is a lane: every parallel brief names the exact paths that agent may write, and containment is then proven with a command rather than trusted from a self-report.
A real lane, quoted from a live brief: "Another agent is ACTIVELY EDITING CODE in this repo RIGHT NOW. Do not collide with it. You are READ-ONLY on all code. WRITE ONLY to: docs/**." And for the parallel art run: "This runs in PARALLEL with Claude Code's balance work, so stay strictly in your lane. Adding unused image files is safe and non-colliding." Note the reasoning in that second one — the work was chosen because it could not collide.
Self-reports are not evidence. Containment was verified as code: a git diff scoped to everything outside the allowlist, with the pass condition stated in advance — that diff must be empty.
# 1. what it was allowed to touch $ git diff HEAD --stat -- docs docs/CONTRIBUTING.md | 84 +++++++++ docs/SECURITY.md | 61 ++++++ 2 files changed # 2. everything else. this MUST be empty. $ git diff HEAD --stat -- . ':(exclude)docs' (empty) ← containment proven
The companion rule is stage explicitly; never git add -A. That rule exists because it was earned: a handoff written by the orchestrator carried a trailing git add -A that would have swept 19 unrelated files into an unrelated commit. The coding agent caught it and asked before running it. Agents checking each other is not redundancy — it is the design.
Parallelism is a collision problem. Give every agent a written lane, stage explicitly, and prove containment with a diff that must be empty. Never accept "I stayed in my lane" as evidence.
What does the player actually download?
The shipped build measured 355 kB uncompressed and 117 kB gzipped. Change the bundle and watch the band.
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
Three claims from the build.
Every delta is computed from its own two values.
Compression made a marginal difference to the entry bundle.
measured delta -238 kB -67.0%
Two agents simulating the same fight independently will land on the same answer.
measured delta -12 percent -16.7%
Briefs between agents were short instructions.
measured delta +5.84 kB +166.9%
Your answers stay in this browser. Nothing is sent anywhere. Reset clears them.
4 The gate
Two independent passes, then reconcile
The audit gate is the load-bearing part of the loop. Each phase defines intent, constraints, and acceptance criteria; agents execute; then a gate runs that can send the work back. For anything expensive to get wrong, two agents audit the same thing independently and the results are reconciled — because the point of a second pass is the disagreement.
The game-math audit ran this way: two agents, same code, neither seeing the other's findings, both under a hard instruction of audits and testing only — report, do not fix. That constraint existed because it had been violated before, and the correction stuck: a later brief opens "(We got burned before by an 'audit' that turned into edits; not this time.)"
Agreement between passes tells you very little — two agents can share a blind spot. Disagreement is the product. It localizes the error to one of three places: pass A, pass B, or the thing being measured. Every one of those is worth knowing, and you cannot learn any of them from a single pass.
| Catcher | What it caught |
|---|---|
| Cowork → Gemini | Reported done at 27 of 45 items, having run no completeness check |
| Cowork → Claude Code | A guess that a paid plan was required for analytics. The documentation said otherwise — the upgrade was never needed |
| Claude Code → Cowork | A trailing git add -A in a handoff that would have swept 19 unrelated files into a commit |
| Cowork → Cowork | Its own balance harness — the subject of module 5 |
| Sub-agent → Cowork | A stale figure about to ship to a public page: "it caught an error I'd have shipped" |
| The human → everyone | Repeatedly, and decisively. See module 6 |
Run the expensive judgements twice, independently, then reconcile. Agreement is weak evidence; disagreement is a finding. And an audit that edits is not an audit.
5 The best lesson here
When the harness is the bug
The combat rebalance had a target: every class should beat the final boss without the special weapon, but not comfortably. Two agents simulated it independently. One reported the dragon winnable 60 to 72 percent of the time. The other reported 0 to 10 percent. One of them was badly wrong — and it was the auditor, not the game.
# win% at 5 Full Restores, NO Godsbane, prepared L50 build boss Warrior Rogue Mage Golgoth L20 100% 100% 100% Malakor L30 100% 100% 100% Xel'Nath L40 14% 74% 74% Ashaveth L50 0% 3% 10% ← unwinnable?
The tempting move is to raise the alarm: the other agent's rebalance is broken, the boss is unbeatable, escalate. The correct move is to audit your own instrument first. The reasoning, written at the time: "before I alarm anyone, I have to check my model — a wrong harness is worse than none."
It was. The simulation had never modeled the Mage's defensive barrier or any brace-before-a-heavy logic, and it allowed only five potions. Those are not edge cases; they are how the fight is actually played. Modeling them changed everything:
# dragon win% by potion count, defensive barrier now modelled pots Warrior Rogue Mage 5 99% 98% 100% 10 100% 100% 100% # verdict: my first sim was the bug, not the balance.
Two things make this the strongest lesson on the page. First, the correction was self-initiated — the disagreement triggered self-audit before escalation. Second, the honest caveat that followed was volunteered, not extracted: a hand-rolled model is extremely sensitive to the assumed play policy — that is precisely why the first run looked broken — so the harness can confirm the shape of the difficulty curve but must not be tuned to a specific percent. The real number comes from playtesting.
Reconciled: the coding agent's 60–72% sits inside the independent bracket, between an optimal-play ceiling near 100% and a no-defensive-play floor near 0%. Both passes were right about different players.
When two passes disagree, the first suspect is the measurement. Audit your instrument before you audit the work — and never tune a real system to a simulated number.
6 Honesty
The corrections log
This section exists because the corrections are the curriculum. The orchestrating AI was wrong at least six documented times during this build. None of the errors were exotic — each was confident, plausible, and reasoned. What matters is which mechanism caught each one, because those mechanisms are the transferable part.
The threat model, inverted
AI was wrongCaught by: research gateThe AI wrote an install card warning iOS users about a 7-day storage eviction that would delete their save.
The truth: home-screen web apps are exempt from that eviction. Installing is the protection, not the risk. The card was telling users to fear the thing that saves them. It corrected itself in writing and added a second, better discipline: "do not overclaim: we have NOT verified the partition behaviour on a device."
"Once again, only you saw the key"
AI was wrong twiceCaught by: the human, twiceThe AI inflated the risk of a leaked bot-detection secret, and repeated the claim after being corrected — twice.
The truth: that secret is used server-side to validate a challenge token; it cannot mint one. Tokens come only from the client-side challenge widget. There was no exploit path. Rotation was still correct hygiene, but the danger was invented. The admission: "you pushed back on it twice while I repeated it. That's on me, not you." The fix went further than an apology — the correction was written into the next brief so the coding agent would not inherit the bad model.
"Correction — only in the inventory do the items not show"
AI blamed the userCaught by: the user's phoneTold the new item art was missing, the AI diagnosed a stale browser cache.
The truth: a real bug, corrected in eleven words by the man holding the phone. The art rendered in the shop and not in the inventory, because a UI pass had wired the art into the bag grid and the detail panel but missed the three equipment slots — and on a fresh save the bag is empty, so those three icon-only slots are the entire screen. "It's your cache" is the AI equivalent of "have you tried turning it off and on again." Two more bugs surfaced from those same screenshots that a fresh save could never have revealed.
The blocker that did not exist
AI invented a constraintCaught by: the humanThe AI announced the image batch was quota-blocked until tomorrow and planned a day of delay around it.
The truth: it had imported one agent's constraint into another agent's pipeline. The free in-chat image quota belonged to the Gemini surface; the art script called a paid API that had already been approved. Nothing was blocked. In multi-agent work, constraints are per-surface — and an inherited constraint invents blockers and burns real time.
The spec that killed the feature
AI's own spec was the bugCaught by: research gateThe AI specified an empty service-worker fetch handler as a hard constraint, with a sound-sounding rationale about not caching the entry page.
The truth: Chrome ignores no-op fetch handlers precisely because developers kept doing exactly this — so the feature would have been dead on arrival on Android. It was caught only because a standing rule required research before deploying. The same research surfaced a bigger, unrelated, already-live bug. A confident architectural constraint is still a hypothesis.
The stale number, caught at the door
AI would have shipped itCaught by: sub-agent auditA public page was ready to ship carrying figures the AI had not re-derived.
The truth: an independent audit found the art count understated by 66% and a test count that could no longer be verified at all. The response was the only correct one: "it caught an error I'd have shipped." This page is the successor to that one, and its numbers are re-derived — see Receipts.
Log your agent's errors and publish them. An agent that cannot show you its corrections is not showing you its work — and the human holding the actual device is ground truth, every time.
7 Infrastructure decides
The filesystem that lies — and commits the lie
The most consequential architectural decision in this build was not made for a good reason. It was forced by a disk. The repository lived on a Windows drive exposed to the orchestrating agent through a mount that silently truncated both reads and writes — sometimes. That single fault decided which agent was allowed to commit.
It surfaced the worst possible way: a commit landed containing 183 lines of a file whose real length was 218, cut off mid-line. Nothing errored. The staging was clean. The commit succeeded. The blob was simply wrong.
And the obvious recovery is a trap. Restoring the file from git would overwrite the good working copy with the bad committed blob — the truncation would win. The verification pattern that actually works: syntax-check the working file, re-commit, then pipe the committed blob back out of git and syntax-check that.
# the working file is fine... $ node --check scripts/pregen-items.mjs ok # ...but what actually got committed? $ git show HEAD:scripts/pregen-items.mjs | wc -l 183 ← the real file is 218 lines $ git show HEAD:scripts/pregen-items.mjs | node --check SyntaxError: Unexpected end of input # do NOT git checkout — that overwrites the good file with the bad blob.
This is why every commit and every deploy in this project ran on the agent with the native filesystem. Not because it wrote better code — because the other surface could not be trusted to write bytes. The lesson generalizes past this one weird disk: your agents' capabilities are not the only thing that matters. Their substrate is part of the architecture, and it gets a vote.
Verify the artifact, not the source. Check the committed blob and the deployed origin — a filesystem, a cache, or a CDN can all lie about what you think you shipped.
8 Safety you can prove
Test the guard by defeating it
The prototype's most expensive defect was one string: its API key, sitting in the browser bundle. The fix is architectural — every model call routes through a Cloudflare Worker holding the key as a Worker secret, and the bundle ships a placeholder. But "we moved it" is a claim, and claims decay. So the guard is tested by trying to defeat it.
A postbuild step scans the built bundle for credential patterns and fails the build if one appears. Then the team did the part most teams skip: they poisoned a build on purpose — inlining a fake key and unsetting the proxy URL — and confirmed the scanner exited non-zero.
# deliberately poison: fake key inlined, proxy URL unset $ npm run build && node scripts/check-secrets.mjs scanning dist/ for credential patterns... FAIL credential pattern found in dist/assets/index-*.js build aborted — a key must never reach the client $ echo $? 1 ← the guard fires. now it is evidence, not a hope.
That experiment exposed a distinction worth internalizing: the dangerous path is conditionally dead, not dead. The build inlines the key only when the proxy URL is unset — so the code is not deleted, it is dormant. One empty environment variable re-enables it. A guard that a configuration mistake can switch off is exactly the guard you must keep testing.
Spend is bounded in layers: an origin allowlist, a model allowlist, a per-session quota, and a per-IP quota. Only one of them protects the wallet — the global ceiling of 5,000 requests per day. Per-user limits shape behaviour; attackers rotate identities. The original per-player quota keyed on a client-supplied header that any user could simply change, which is why the fix pairs a bot-detection challenge with an HMAC-signed session token whose session id is generated on the server. The quota key must not be a value the client gets to choose. And limits are keyed on that signed id rather than on IP, because schools and carriers put thousands of real people behind one address.
An untested guard is a guess. Poison a build and watch it fail. Then remember only the global ceiling bounds your bill, and never key a quota on a value the client controls.
9 Generative systems
The unbounded namespace
Late in the build, a player's inventory was full of flat placeholder icons instead of art. The reflex — generate more images — was mathematically doomed. When a monster mints an item name at runtime, the namespace is infinite, and no finite offline batch can ever cover it. That is a resolver problem, not a content problem.
The game generates loot with AI-invented names. Anything not in the static item list becomes a custom item, named on the spot. An endgame bag is therefore mostly items that by construction have no pre-generated art. Shipping more images would have raised the hit rate slightly and fixed nothing.
The fix is a chain: exact art, then a 24-cell fallback matrix covering every item type crossed with every rarity, then a type icon. Every AI drop now resolves to real, rarity-appropriate art, generated once, with zero runtime image calls — preserving the free game's spending guarantee.






The shipping discipline matters as much as the design. The resolver shipped before the images existed — every matrix lookup would 404 and fall through to the icon, which is byte-identical to the previous behaviour. A safe degrade means the deploy cannot regress anyone, and the art can land whenever it lands.
An unbounded namespace needs a fallback, not more content. And ship the resolver before the assets, so the safe degrade is the deploy's worst case.
10 Reality
Publishing, and the proof it worked
"It builds" is a small part of "it's live." Shipping this game means a proxy Worker with a hand-set secret, a quota store, a container image, a Cloud Run service, DNS, a TLS certificate, a storefront page, and a smoke test that can actually fail. The build is one step of eight, and not the riskiest.
The deepest trap in the whole project lives here. The AI proxy rejects the temporary cloud URL by design — it is not on the origin allowlist — and the game falls back to pre-written dialogue that is deliberately indistinguishable from live AI. So a developer can test on the temporary URL, see a beautiful, fully playable game, and ship with the signature feature completely dead. The only proof is a specific observable: a 200 from the proxy, in the network tab, on the real origin.
Two more traps from this build, both about things that lie. A .dockerignore once excluded the scripts directory — which would have stripped the secret scanner out of the build environment entirely. A guard that does not exist where the build runs is not a guard. And a PWA icon survived a correct deploy twice: an installed shortcut bakes its icon at install time, and worse, the icon URLs were unversioned while the server sent immutable, max-age=31536000. Same URL, same cache entry, held for a year — even across a reinstall. Cache immutability is a one-year commitment to a mistake. Versioned URLs fixed it.
The server being correct is not the same as the user seeing it.The icon, twice
Every deploy step is a command with a pass condition, and nothing proceeds on a warning. Your smoke test must be a specific observable on the real origin — not "the game works."
Reproducible
The loop, end to end
Tool-agnostic, and every step is a scar. Each names the decision, the acceptance test — a command with a pass condition, because if it cannot fail it is not a test — and the failure mode it exists to prevent.
Assign by constraint, not capability
Ask what each surface is structurally unable to get wrong. Let infrastructure decide, and write down why.
Accept: every task type has a named owner and a stated reason.
Failure: a truncating mount silently commits a 183-line version of a 218-line file.
Write briefs, not chat
Intent, constraints, acceptance criteria, lane. Complete and self-contained.
Accept: every brief contains a command with a pass condition.
Failure: an agent optimizes for the thing you said instead of the thing you meant.
Give every parallel agent a lane, and prove it
Name the writable paths. Never trust the self-report.
Accept:
git diffoutside the allowlist returns empty.Failure: a trailing
git add -Asweeps 19 unrelated files into a commit.Audit twice, independently, then reconcile
Report only. An audit that edits is not an audit.
Accept: two passes, neither seeing the other, reconciled in writing.
Failure: one confident pass and a shared blind spot.
Suspect the measurement first
On disagreement, audit your own instrument before escalating.
Accept: the harness models the tools a real player actually uses.
Failure: "the boss is unwinnable" — when your simulation forgot the defensive skills.
Log corrections and push them downstream
When you find your own error, write the correction into the next brief.
Accept: the corrected model appears in the dispatch, not just the apology.
Failure: a second agent builds on your bad premise.
Verify the artifact, not the source
Check the committed blob and the deployed origin.
Accept:
git show HEAD:fileparses; the live URL returns what you expect.Failure: a clean working file and a corrupt commit.
Test the guard by defeating it
Poison a build; watch the scan fail.
Accept: exit code 1, on purpose, before you trust it.
Failure: a credential path that is conditionally dead, one variable from live.
Cap spend globally
Layer the limits, but know which one is load-bearing.
Accept: a hard daily ceiling, and a quota key the client cannot choose.
Failure: per-user limits and an identity the user can rotate at will.
Give unbounded namespaces a fallback
Generated names cannot be covered by a finite batch.
Accept: ship the resolver first; every miss degrades to the old behaviour.
Failure: generating art forever and never catching up.
Key caches on stable ids, never display names
Display names are free to change. Cache keys are forever.
Accept: rename an entity; confirm no asset is orphaned.
Failure: a find-and-replace becomes an art-regeneration project. See the receipt below.
Re-derive every public number at release
No figure reaches a marketing asset without a command behind it.
Accept: each claim carries a command; unverifiable claims are omitted.
Failure: a page that teaches verification while carrying stale numbers.
The receipt for step 11
This project paid for that rule and then proved it. The game's final boss was renamed during an IP sweep — but its art cache key was pinned to the pre-rebrand string on purpose, with a comment in the code explaining why: renaming the key would orphan the shipped portrait. So the display name moved freely to "Ashaveth, the Red" while the cache key stayed exactly where it was, forever. The same duality runs through the whole bestiary — several monsters carry grimdark display names over legacy art keys, deliberately.



Assign by constraint; brief, don't chat; lane every agent and prove it; audit twice and suspect the instrument; verify the artifact, not the source; and never trust a green signal you have not tried to break.The loop in one sentence
Receipts
Every number, re-derived
This page was built by running the checks it describes. An independent audit re-derived every figure against the repository at a known commit, and two claims did not survive it: the test count and the bundle size. Neither could be executed in the audit environment, so both shipped marked UNVERIFIED rather than guessed — a page about verification that hides its own gaps has taught nothing. Both have since been run on a machine that could execute them, and the numbers are below. The wait was the point: the stale figure was "154 tests," a first re-measurement said "487," and the number that actually reproduces from the commit is 480. Guessing would have been wrong three ways.
Verified against Ashaveth v1.1.0 at commit d2daf49, on a clean tree. Every figure below has a command behind it and no figure is estimated; when a check could not be executed it stayed marked unverified until it could be. The rule does not bend for a number that would look good.
- 0 TypeScript errors, strict —
tsc --noEmit, exit 0 - 171 art assets — 72 monster, 68 item, 26 character, 5 location
- 24 fallback matrix cells — 4 types × 6 rarities, all live
- 72 monsters — 67 non-boss + 5 bosses, 1:1 with 72 art files
- Global ceiling 5,000 requests/day — the wallet guarantee
- Relic drop 0.05% — 1 in 2,000, and only while the AI is live
- No key in the bundle — credential scan of the build, 0 matches
- 74 files uploaded — CDN coverage taken to 171 of 171
- 480 unit tests pass across 20 test files —
npm test(vitest run), 0 failed, 2.25s - 355,422 byte entry bundle, 116,835 gzipped —
rm -rf dist && npm run build, chunkassets/index-[hash].js
Questions
Frequently asked
What does this masterclass teach?
It reconstructs the multi-agent workflow that built and shipped Ashaveth, a free grimdark browser RPG. You learn how to divide work across several AI agents, write briefs that can fail, keep parallel agents from colliding, run independent audit passes, and verify what actually shipped. It includes a full log of the errors the AI made during the build.
Which three AI applications built Ashaveth?
Three surfaces: Cowork, the Claude desktop agent, which researched, audited, drove the live browser, handled the Shopify API, and orchestrated; Claude Code running Opus 4.8 in the Antigravity IDE terminal, which did the repository code, tests, commits, and deploys; and a Google Antigravity Gemini agent, which handled parallel non-colliding work such as SEO metadata and in-chat image generation.
Why did one agent do all the commits?
Infrastructure, not preference. The repository sits on a Windows drive exposed to the orchestrating agent through a mount that silently truncates reads and writes. It once committed a 183-line file whose real length was 218 lines. Because commits from that surface could not be trusted, every commit and deploy was routed to the agent running on the native Windows filesystem.
How do you stop parallel agents from colliding in one repository?
Give every brief an explicit write-allowlist naming the paths that agent may touch, and never trust the agent's self-report. Containment is proven with a command: a git diff excluding the allowed paths, which must return empty. Staging is always explicit — never git add -A, a rule that exists because a trailing git add -A in one handoff would have swept 19 unrelated files into a commit.
What is an audit gate?
A checkpoint that closes a phase and can send the work back. Each phase defines intent, constraints, and acceptance criteria that can fail, then agents execute, then the gate runs. The gate is the load-bearing part: it is what turned the claim that the API key had been moved into a poisoned-build proof that the guard actually fires.
What happens when two AI agents disagree?
Suspect the measurement before the code. During the combat rebalance, one agent's simulation reported the final boss was winnable 60 to 72 percent of the time; an independent simulation reported 0 to 10 percent. The independent harness was the bug — it had never modeled the Mage's Arcane Barrier or brace logic. Corrected, it returned 98 to 100 percent. The value of a second pass is the disagreement, not the agreement.
How do you ship AI features without exposing the API key?
All model calls route through a Cloudflare Worker holding the key as a Worker secret, so the browser bundle ships a placeholder rather than a credential. A postbuild step scans the built bundle for credential patterns and fails the build if one appears. The guard was then proven by deliberately poisoning a build and confirming it exited non-zero.
What is the difference between dead code and conditionally dead code?
The build inlines the API key only when the proxy URL is unset, so the dangerous path is not deleted — it is dormant. One empty environment variable re-enables it. A guard that can be switched off by a configuration mistake is exactly the guard you must keep testing.
Why does per-user rate limiting not protect your bill?
Because attackers rotate identities. Ashaveth's proxy caps spend in layers — an origin allowlist, a model allowlist, a per-session quota, and a per-IP quota — but only the global ceiling of 5,000 requests per day actually bounds the bill. Per-user limits shape behavior; the global ceiling is the wallet guarantee.
Why was a Turnstile session token needed?
The original per-player quota keyed on a client-supplied header, which any user could rotate to reset their own limit. The fix pairs Cloudflare Turnstile with an HMAC-signed session token whose session id is generated server-side, so the quota key cannot be chosen by the client. Rate limits are keyed on that signed id rather than on IP, because schools and carriers share addresses.
Can an offline art batch cover AI-generated loot?
No, and this is a common mistake. When a monster mints an item name at runtime, the namespace is unbounded, so no finite batch of pre-generated images can ever cover it. The fix is a resolver chain rather than more content: exact art, then a 24-cell type-by-rarity fallback matrix, then a type icon. Every AI drop gets real art without a runtime image call.
Why did an icon persist after a correct deploy?
Two causes compounded. An installed home-screen shortcut bakes its icon at install time, so no deploy repaints it. Worse, the icon URLs were unversioned while nginx served them with immutable, max-age=31536000 — the same URL means the same cache entry, held for a year, even across a reinstall. Cache immutability is a one-year commitment to a mistake. Versioned URLs fixed it.
How can passing tests certify a bug?
When the fixture cannot reach the failing state. A shipped bug in the ending survived a clean compiler and a green suite because the test fixture seeded an empty journal for every character, so the harness could only ever exercise the empty path. Coverage counts what you wrote, not what you can reach. For every invariant, ask what state would make this check fail, then confirm your fixture can reach it.
Why does the masterclass publish the AI's mistakes?
Because the corrections are the curriculum. The orchestrating agent inverted a mobile threat model, overstated a security risk twice after being corrected, misdiagnosed a real bug as a browser cache issue, and invented a quota blocker that did not exist. Each error was caught by a specific mechanism — a research gate, user pushback, a second pass, or an audit — and those mechanisms are the transferable part.
Why does the class not say which agent wrote which code?
Because the repository records a single git author with no per-commit trailers, so per-agent attribution is unrecoverable. Attribution that cannot be verified is not a fact worth publishing. The class teaches the method and treats the model roster as an implementation detail.
What is the 2026 stack behind Ashaveth?
React 19 and TypeScript 5.8 in strict mode, built with Vite 6 and tested with Vitest 4. AI dialogue runs through the Google Gemini API behind a Cloudflare Worker. The game ships as a static bundle in a multi-stage Docker image served by nginx on Google Cloud Run, with a Shopify storefront page. The current release is version 1.1.0.
What makes the game logic testable?
Purity. The combat engine has no clock and no random source — randomness is injected as a parameter — and the reducer is the only authority on state changes, re-validating its own preconditions rather than trusting the component that dispatched the action. Dependencies point one way: components to state to services to data, and nothing points back up.
Is Ashaveth free, and who can play it?
Ashaveth is free to play in the browser with no download and no signup, at ashaveth.ddsboston.com. It is a grimdark RPG intended for adults and is self-rated 18+ by Design Delight Studio; it carries no ESRB or PEGI rating.
Bottom line
Multi-agent building is not about having better models. It is about designing the mechanisms that catch a confident agent being wrong — a research gate, a lane with a diff that must be empty, two independent passes, a guard you have watched fail, and a human with a real device. Ashaveth shipped because those mechanisms existed, and every one of them earned its place by catching something. Run the loop on your own prototype; the corrections you log will be the most valuable output.
Play the proof. Then run the loop.
Ashaveth is free in the browser — no download, no signup. Then take the method into your own build at the DDS Vibe Academy.
Free to play · 18+ · Built and published by Design Delight Studio, Boston.
