Answer
The Plays engine writes every applicable Play for a brand from its brief and ledger, checks each number and claim, turns each Play into cards as it lands, and keeps Plays current on a capped weekly budget; this page settles the specs, the order, the model, the caps and the rewrite rules.
Today
In code, files, routes and ids a Play is still called a move. People see Plays. memory brew-qa-round2-2026-10-24
Specs
- 66 specs in
data/move-specs.json: 0 the index, 1 to 50 core, 51 to 65 listening-era (written once their evidence exists). Groups: reach 16, convert 12, know 11, found 11, keep 9, expand 6, index 1. brew/data/move-specs.json - Each spec names its group, skill, kicker, discipline, title and stat hints, dependencies, evidence, lookups, an exemplar and a timing. brew/data/move-specs.json
- Dynamic Plays 100 to 999 (a festival at its plan point, a research gap, replies that reached a threshold) and weekly cadence Plays from 1001 (listening and automation, never reused). brew/moves-engine.js:1-3 brew/moves-cadence.js:9, 274
status.totalis the applicable playbook (1 to 999, not the index, not cadence). memory brew-lean-pipeline
Writing one Play
writeMove(n) = the house prompt + evidence contract + the discipline's skill file + brand brief + ledger slice (plus listening, pulse, video and calendar lines) + dependency digests + one Arata Play as depth exemplar. brew/moves-engine.js:4-7
| Setting | Value | Source |
|---|---|---|
| Model | GEMINI_MOVES_MODEL (gemini-3.1-pro-preview) on Gemini; Opus on Claude |
brew/moves-engine.js:17 brew/deploy.sh:39 |
| Effort | MOVES_EFFORT, default medium; a max-tokens hit retries at low |
brew/build.js:398 brew/moves-engine.js:473-479 |
| Cache | System and brand block cached for an hour | brew/moves-engine.js:473 |
| Search | MOVES_SEARCH, default 1 |
brew/build.js:398 |
| Concurrency | MOVES_CONCURRENCY 8 on staging and production; a second pass at 4 for failures |
brew/deploy.sh:39 brew/build.js:404-406 |
| Order | Wave A first: 1, 2, 3, 9, 10, 4, 5, 6, 7, 8; then dependency order | brew/build.js:58 |
Checking one Play
verify() = a deterministic number scan against the ledger, a fast-model claims check on the lines the Play rests on, one targeted fix. verified is true only when the claims check ran with no issues, no stat is unbacked, no number is unresolved and no ledger key is unknown. brew/moves-engine.js:553-626
Cover numbers are checked again at load (stat-check.js): an entry price that does not match the catalog, a "cited in reviews" figure not in a review, or an unbacked stat becomes "(to confirm)". brew/docs/audit-2026-10-24-design-partners.md
As each Play lands
It is saved as moves/NN.json and in moves.json, turned into 1 or 2 cards, Play 21 starts the static ads, progress and pace are saved, and the team is emailed once wave A is done. PDFs are drawn when someone opens one (pdfOn), versioned by PDF_V 12. brew/build.js:378-395, 1745, 1777
Caps
| Cap | Value | Source |
|---|---|---|
| Build per brand | BUILD_CAP_USD 45 |
brew/deploy.sh:39 brew/tools/prod-env.sh:9 |
| All builds per day | DAILY_BUILD_CAP_USD 250 |
same |
| Pro brand build | max(150, 2.5 × cap) | brew/build.js:115 |
| Failed Plays | retried up to BUILD_RETRY_MAX 6 times, then the brand is Ready with them named |
brew/build.js:110, 418-421 |
Keeping Plays current
Reading is cheap and frequent; rewriting is batched, needs a real change, and is capped. memory brew-move-freshness
| Trigger | What it does | Cap | Source |
|---|---|---|---|
| A fresh read or fact change | refresh.js finds material changes (price, product, rival, claim, policy, live ads, 10%+), a fast model judges which Plays change, flags them |
brew/refresh.js:1-6 | |
| Out of stock or removed product | flags every Play naming it, high priority, no AI | brew/build.js:1600-1640 | |
| A rival's 10%+ price move or launch | rewrites the Play it hits hardest, at most one a day | brew/server.js:510-514 | |
| First connected account | rewrites every Play privately after a 30-minute settle and the first read | CONNECT_CAP_USD 40 |
brew/build.js:1070-1072 memory brew-connect-refresh |
| Connector numbers move 10% from the base | flag, then the weekly run | LIVE_CAP_USD 30 a week |
brew/build.js:1076 |
| Learnings | high and medium impact now; low waits for the week | LEARN_CAP_USD 30 |
brew/build.js:1075 |
| Hourly stale sweep | queues flagged Plays within the weekly pot, best kind first; never Plays written in the last 24 hours | the pot | brew/build.js:1570-1595 |
The weekly pot per brand is $10 on Flash, $40 on Pro and $50 on Claude; each kind may spend only down to its share (learnings 1.0, events 0.9, check 0.8, connected 0.7, low 0.6, new 0.5). brew/rewrite-pot.js:14-21
Rewrite priority in the queue: learnings, then events, manual and connected, then stale, weekly and live, then new. brew/build.js:1156
Weekly cadence Plays
Listening Plays (2 to 4 from what was heard) and automation Plays (2 to 4 routines written as plans the team runs; Brew never runs them). One run per brand across instances by a 30-minute mark. brew/moves-cadence.js:1-10, 501
Quality and cost
On 20 Oct, 54 Arata Plays compared: Gemini Pro median $0.18, 2.4 minutes, about 2,250 words; Opus median $0.83, 5.7 minutes, about 5,450 words with far more facts and sources. Opus read clearly better, but it also had the team's uploaded audit. memory brew-model-compare-2026-10-20
Tomorrow
| Item | Version | Tag |
|---|---|---|
| Every applicable Play is kept; cost cuts make each Play cheaper, never fewer | v1 | Stays memory keep-all-moves |
| Plays on Gemini Pro, the rest on Flash | v1 | Changes once Param picks the launch model |
| "Verified" reached on most Plays | v2 | New (needs the claims check and fix to converge) |
| Dedupe of the same ask across Plays at write time (today only at serve time) | v2 | New |
| One weekly rewrite budget across every source | v1 | Stays (built as the pot) |
How we do it
- Never propose fewer Plays to save money; cut tokens, caching and repeat evidence calls. memory keep-all-moves
- New flag sources only flag; the stale sweep and the weekly run rewrite. memory brew-move-freshness
- Check a matcher against real brand data (variants) before deploying; one 15 ml variant once flagged 54 Arata Plays. memory brew-move-freshness
- One-off rewrites use the ops switch, then confirm
meta.rewrittenAtchanged:
gcloud run services update shopos-feed --region=asia-south1 --update-env-vars=OPS_REWRITE=<slug>:<n,n>
memory brew-qa-audit-2026-10-20
- Before a long paid run, check credit and expect other sessions to deploy; runs resume, but plan for it. memory brew-arata-rewrite-2026-10-15
Numbers
| Metric | Definition | Where measured |
|---|---|---|
| Cost per Play | meta.usd × 1.5 overhead |
moves/NN.json meta; rewrite-pot.js OVERHEAD brew/rewrite-pot.js:24 |
| Build cost per brand | Total for a full build | cost.json |
| Verified share | Plays with meta.verified true |
moves.json (Proposed to show on the ops page) |
| Stale Plays | Flagged and not yet rewritten | updates.json stale |
Open
- Which model writes Plays at launch? Proposal: Opus when Anthropic credit exists, since the 20 Oct comparison favoured it; run the fair 5-Play test first (about $1).
- Should an unverified Play show to the brand? Proposal: yes, with "N claims to review" as today, never hidden.