Babanuj · Paid Ads Orchestration

Holistic paid-ads agent — build plan & live progress

An agent-first CLI that manages Google Ads, Amazon, Meta, and TikTok against one strategy — optimizing the whole portfolio, not any single platform. Built by a code factory: Claude plans and verifies, Codex implements, a second Claude reviews, Moe merges.

46PRs merged
Strategy ACTIVE
Implementation queue done
⚙️Awaiting fable setup

Token meter — Codex (Coders): 2.19M across 28 runs, measured from worker logs · Claude (Reviewers + merge executors): ≈1.65M from run reports; Chief session metered separately in Claude Code (/usage) · Marginal cost: $0 — both run on flat-rate subscriptions (ChatGPT plan / Claude plan). Updated 2026-07-11.

Command reference

Every fable command and what it does. Run from the repo root (~/marketing). Default data is fixtures; add --live-read for real accounts (reads only — every action stays dry-run). For live runs, source the channel's env first (see the README's per-channel sections).

The loop

CommandWhat it does
fable run --tick daily [--channel g|m|a] [--live-read]The daily tick: ingest signals inbox → compile strategy → channel agents propose actions → budget guard → dry-run execution, all lineage-logged; flags strategy drift (exit 2 + warning); regenerates the per-campaign strategy state file.
fable run --tick weeklyThe weekly tick: evaluates experiments whose windows closed, scores cycles against the strategy baseline, updates Beta-posterior confidence, emits candidate rule drafts at ≥0.80 / ≥3 cycles. (A fuller weekly performance review with budget proposals is coming as fable review weekly — issue #76.)
fable plan --channel google|meta|amazon [--live-read]One-shot: what would the agent propose right now for this channel, with the rule each action derives from. Writes action_proposed lineage events only.

Strategy

CommandWhat it does
fable strategy showPrints strategy.md plus active rules grouped by the strategy section they serve.
fable strategy compileValidates strategy.md + all rules (including proposed drafts) and writes the canonical strategy.lock.json agents consume. Any invalid rule or dangling strategy_ref fails.
fable strategy lintValidate-only (no lock write).
fable strategy status --channel <ch> [--live-read]Writes strategy/state/<ch>-<account>.md: per campaign — stance (scale/hold/reduce/testing/not-targeted), the rules and strategy sections applying to it, metrics snapshot, running experiments.
fable strategy learn --channel google [--live-read]Generates the evidence document (up to 12 months): monthly aggregates, per-campaign ROAS/CPA trends, seasonality, top movers → strategy/evidence/.

Experiments

CommandWhat it does
fable experiment proposeRegisters a bounded experiment: hypothesis, pre-registered metric + direction, window (default 7d), isolation mode, optional --campaign-id.
fable experiment start <id>Starts it (dry-run): budget guard enforced (≤5% of channel spend, unbypassable), isolation moves computed and lineage-logged, nothing executed.
fable experiment evaluate <id> [--rules-directory]Scores a closed window against the strategy baseline; threshold-passing hypotheses become draft rules under strategy/rules/proposed/ — never auto-activated.
fable experiment list|show <id>Experiment lifecycle state (proposed → running → evaluating → concluded).
fable hypotheses listConfidence per hypothesis (Beta-posterior over validation cycles).

Signals & audit

CommandWhat it does
fable signals ingestValidates JSON events in signals/inbox/ (seo.signal, creative.asset.ready, website.change) → lineage; invalid files → rejected/ with reasons.
fable signals pull-gscPulls Search Console (read-only), detects brand-keyword spikes/drops/vanishes vs the prior window, emits seo.signal events into the inbox.
fable signals listIngested signals from lineage.
fable lineage list | show <id> | trace <id>The append-only audit trail; trace walks causal parents ("why did this happen").

Settings, review & jobs (shipped)

CommandWhat it does
fable setup · --set k=v · --checkRun this to bring the anchors alive. Intake: margins (→ anchor math), budget envelope, threshold overrides, approval classes, seasonal-calendar dates. Validates on write; unset params fail loudly at compile.
fable review weekly [--live-read]Weekly performance review — spend vs envelope, metrics vs anchors, propose-first suggestions with the arithmetic. Writes strategy/reviews/. Cron doc in README (install with your consent).
fable jobs export-search-terms --channel amazonMonthly Amazon search-term export to durable off-repo storage (65-day retention clock).
fable assets request | listMachine-readable creative-asset requests to signals/outbox/ + arithmetic QC gate (strategy §7).

Useful env vars: FABLE_GOOGLE_CUSTOMER_ID (client account override), FABLE_AMAZON_PROFILE_ID, FABLE_AMAZON_REPORT_PATH (metrics artifact), FABLE_LINEAGE_DB / --db (lineage database path).

Needs Moe — one action, at your pace

The strategy is written, approved, and active (PR #78, merged 2026-07-12). Seven intake decisions are recorded and the settings layer enforces them. The single remaining human action is fable setup — filling the numbers the doctrine deliberately doesn't contain: margin structure (→ anchor math), monthly budget (total + per-platform split), and this year's seasonal-calendar dates. Everything else is optional defaults. Once margins land, the anchors go live and Meta's reactivation recommendation starts.

Run fable setup

Interactive or flag-driven (fable setup --set break_even_acos.google=…). Writes strategy/intake.yaml; validates on write; the web dashboard will later expose the same file as a settings page. Thresholds, approval matrix, and the seasonal window list are pre-filled — you only need margins, budget, and dates. Nothing executes against real accounts until a separate, explicit live-write milestone you sign off on.

Optional, whenever

Unpause the Brand Defense campaign in the Google UI (the agent flags it but can't act until live-write) · TikTok connectivity if you want that channel · a newer amazon-ads-pp-cli with v3 report endpoints (would make Amazon metrics self-sufficient vs. the daily-sync artifact).

All three questions from 2026-07-11 are answered and shipped: objective (strategy.md), scope (account 160-437-6990), never-touch (none yet, standing section below). Both blocked channels are now live — Meta and Amazon connectivity confirmed and adapters merged. Nothing blocks the factory; it builds the strategy's implementation queue (#73 → #76 → #74 → #75) autonomously.

Never touch

Hard constraints no agent proposal may violate — protected campaigns, budget floors, seasonal locks, off-limits products or regions. Each entry here is also encoded as a guardrail rule in strategy/rules/, so it binds the agents mechanically; this section is the human-readable mirror.

Steady state (2026-07-12): every filed issue is shipped except one trivial rendering item (#71). The queue Moe approved landed in full: live reads verified on all three channels, Amazon metrics flowing from the daily sync, experiment→campaign attribution wired, the Meta reactivation guard merged (safe to unpause), and the freshness gate correctly splitting fixture from live state. The factory idles on one input: Moe's strategy.md. Repo is now private; the real 13-month evidence document is committed for strategy writing.

No constraints defined. Moe confirmed "nothing special" on 2026-07-11. To add one, tell Claude in the factory session (e.g. "never reduce the budget of campaign X" or "brand campaigns are off-limits") — it lands here and in the strategy rules in the same change.

Plan & Progress

Phase 1 is complete. The full loop runs in dry-run with zero spend risk: fable run --tick daily ingests signals, compiles strategy, proposes and dry-executes Google actions under the budget guard, and flags strategy drift; --tick weekly evaluates closed experiment windows, accumulates Beta-posterior confidence, and emits draft rules for human review. Every step lineage-logged; the whole loop is an e2e test inside make verify. Next: hygiene batch, then Phase 2 (live read-only against the Babanuj CLIs).

ChiefClaude Fable 5 · high

Architecture, issues, dispatch, evidence verification. Writes no product code.

CoderCodex gpt-5.6-sol · medium → high on retry

All implementation, tests, verify runs, PRs. One disposable worker per issue.

ReviewerClaude subagent

Reviews each PR at its exact head SHA. Different model family than the Coder, by design.

Merge authorityChief, under Moe's standing authorization

Merges fire automatically once every gate is green: CI + DCO + APPROVE review, all on the exact head SHA. Any red gate blocks.

Phase 1 — Google Ads vertical slice (dry-run)
#1 Scaffold TypeScript CLI + wire make verify — PR #9 Merged Landed on main as f7dfaa3 · CI pass, verify exit 0 twice, Reviewer APPROVE · follow-up hygiene in #10
#2 Strategy rules: schema, parser, strategy compile Merged Landed on main as 5da10a7 after review → drift-bug fix → rebase → byte-level re-audit · verify now fails on stale locks, proven twice
#3 Decision lineage store (append-only event log) Merged Landed on main as 90cc7d4 · Reviewer APPROVE after exercising the CLI against a real store · hardening notes → issue #13
#4 External signal contract + file inbox Merged Landed on main as 2266cfa · feature + post-rebase APPROVE, byte-level audit clean · hardening notes → #15
#5 Channel agent interface + Google Ads adapter (fixtures) Merged Landed on main as d9067f5 · Reviewer recomputed the decision arithmetic from raw fixtures; determinism + propose-only proven · follow-ups → #17
#6 Bounded experiments: isolation + budget guard Merged Landed on main as 8934ea3 · Reviewer recomputed the 5% cap from raw fixtures and proved the guard unbypassable · built by gpt-5.6-sol · follow-ups → #19
#7 Strategy-diff evaluator + confidence scoring Merged Landed on main as 8997816 · Reviewer hand-verified the Beta-posterior math (5 exact cases) and falsification-tested the human-approval gate
#8 Orchestrator loop: daily + weekly ticks (end-to-end) Merged Landed on main as 12a4e73 · Reviewer ran the full pipeline manually and mutation-tested drift detection (review caught it untested; fix proven by breaking the code and watching the test fail) · follow-ups → #22
Phase 2 — real data, still zero spend risk
#28 Live read-only Google Ads behind --live-read Live on all accounts Full four-account matrix passes: main account (37 campaigns incl. SHOPPING) runs the complete pipeline on real data · smoke test found 3 real-world gaps, hardening merged same-day (#40 → PR #41) · manager account fails with a clear, actionable message · read-only invariant falsification-tested twice
#29 Search Console bridge → seo.signal events Merged Reviewer recomputed thresholds by hand; emitted events survive the real ingest with zero rejections · vanished-query follow-up → #39
#30 Meta channel adapter (fixtures, propose-only) Merged Reviewer hand-recomputed the decision math (exact match) and verified propose-only + loud degradation empirically · fable plan --channel meta works · live read-only follows
#31 Amazon Ads adapter (fixtures, propose-only) Merged Reviewer hand-recomputed the decision math and byte-diffed Google/Meta outputs across the refactor (identical) · fixture shapes are schema-derived (live list endpoints 404/415ed) — validation deferred to live-read #54 · approved queue running: #53 live Meta → #54 live Amazon → #47 evidence → #50 attribution
#60–64 🎉 First real recommendation from real data Milestone fable plan --channel amazon --live-read proposed a genuine budget shift — low-ROAS campaign → high-ROAS campaign, computed from 14 days of real spend/sales (5,697 report rows via the babanuj-ops daily sync) · dry-run, lineage-logged · reasoning still uses the placeholder rule: Moe's strategy.md gives it a real north star
#53–59 Live reads verified on ALL connected channels Milestone Meta: real account read end-to-end (9 paused campaigns, honest warnings; bare-digit id fix #57 found by live smoke) · Amazon: real profile with 3,890 campaigns / 13.5K entities read in 13s after the worker cracked the CLI's media-type puzzle · Google: verified earlier on all 4 accounts · every proposal remains dry-run · pre-Meta-reactivation guard fix queued (#56) · next: Amazon performance metrics via authorized report requests (#60, Coder running)
Phase 3 — the real strategy (Moe's Q&A, 2026-07-11)
#45 strategy.md — the north star agents read, reason about, follow Merged Rules must cite the strategy section they serve; strategy edits invalidate the lock — both proven empirically in review · skeleton awaits Moe's content (Moe is authoring it himself)
#46 Per-campaign strategy statement Merged strategy/state/google-160-437-6990.md: stance + rules + evidence per campaign, regenerated every tick, hash-logged to lineage · Reviewer cross-checked every stance against what fable plan actually proposes · follow-ups → #50
#47 Evidence pipeline — 12 months of account …6990 history Merged fable strategy learn · real pull ran: 8 campaigns, 13 months, the March 31.8x Ramadan/Eid peak surfaced · gave Moe the data to write against
⭐ Strategy active — Moe's doctrine is the north star (2026-07-12)
#78 Babanuj Paid Ads Strategy v1 Merged = approved Moe's multi-platform template, restructured under the enforced anchors, doctrine only · seven intake decisions woven in · every number a settings reference · agents now trace proposals to Moe's sections, not placeholders
#72 / #77 / #79 Intake settings layer + fable setup Merged Margins/budget/thresholds/approvals/seasonal calendar as validated config · Amazon budget automation schema-locked out · unset params fail loudly · awaits Moe's numbers via setup
Phase 4 — implementing the strategy ✓ complete
#73 Playbook action types Merged negative_keyword · bid_step_down · placement_modifier · graduate_keyword · Reviewer probed every invariant: Exact-no-negatives type-locked, graduation-negate-source unconstructable, Amazon kill-first (budget shifts schema-locked propose-first)
#76 Weekly performance review Merged fable review weekly — composes the experiment tick + performance review; Reviewer set anchors and hand-recomputed the arithmetic; degrades honestly to "unavailable pending fable setup" until anchors exist
#74 Amazon search-term export Merged Monthly, off-repo, beats the 65-day retention clock · read-only invariant even stronger than required (pure HTTP, no subprocess)
#75 Creative asset-request contract + QC gate Merged §7 outbound handoff · QC gate probed as pure arithmetic (spec-diff on violation, zero aesthetic judgment) · winner/loser feedback loop → #86
Open — decisions & future arcs
#81 Decision: graduate_keyword approval class? Needs Moe (no rush) It launches new spend — auto or propose-first under live-write? Nothing emits it yet.
#71 · #83 · #86 Polish: 0.00-ROAS rendering · freshness-gate hardening · asset LIVE/TAGGED feedback loop Queued (low) Small correctness + the creative-agent winner/loser return path (future §8 wiring).
Later: product dashboard (settings + never-touch editor) · adstock/MTA attribution · cross-channel engine · incrementality · live-write mode · TikTok Not yet filed Live-write — letting approved proposals actually execute — is the biggest future decision and gets its own safety design + Moe's explicit go.

Existing plumbing (Babanuj)

Recon of ~/Babanuj and ~/babanuj-ops confirms the spec's claim: connectivity is already built. These CLIs (Go binaries in ~/go/bin) become the live data sources behind the ChannelDataSource port when we flip off dry-run.

ChannelCLIStatus
Google Adsgoogle-ads-pp-cliLIVE — OAuth2, Basic access, 4 accounts
Amazon Seller (SP-API)amazon-seller-pp-cliLIVE — store “Babanuj”, US/CA/MX/BR
Amazon Adsamazon-ads-pp-cliPending — API access submitted, refresh token outstanding
Search Consolegoogle-search-console-pp-cliLIVE — feeds the SEO-signal contract
Google Analyticsgoogle-analytics-pp-cliConfigured
Meta~/.config/meta-adsConfig present — CLI surface to be confirmed

Architecture

Canonical copy lives in the repo at docs/ARCHITECTURE.md; this is the same content, condensed. Three foundational decisions (Moe, Jul 6): TypeScript core, Google Ads first, dry-run on fixtures for v1.

1 · Strategy is Markdown rules, compiled

One rule per file under strategy/rules/ — YAML frontmatter (id, status, scope, channels, metric, confidence, provenance, approved_by) over a human-readable rationale. fable strategy compile validates everything against a JSON Schema and emits one canonical strategy.lock.json that channel agents consume.

Rules live in git: an agent-proposed change is a PR; human approval is the merge. The approve-loop and its audit trail come free, and an invalid strategy fails make verify.

2 · Experiments are ring-fenced by the framework, not the channels

An abstract experiment carries a hypothesis, a pre-registered metric and direction, a window, and an isolation request. Channel adapters declare which isolation modes their platform supports (budget-slice, geo-split, audience-split) and translate. A central budget guard rejects anything over the cap (default 5% of channel spend) — enforced above channel code so no adapter can bypass it.

3 · Confidence is a Beta-posterior over validation cycles

A validation cycle is one completed experiment window (default 7 days) scored against the strategy-diff baseline: did the portfolio move toward the strategy, relative to baseline. When P(effect > 0) ≥ 0.80 across ≥ 3 cycles, the hypothesis becomes a draft rule with provenance links — never an automatic change.

Deliberately simple and auditable; the model gets smarter only when lineage data proves it's miscalibrating.

4 · External agents speak versioned JSON events

SEO, creative, and website-change signals arrive as CloudEvents-style JSON validated against schemas in contracts/, through a file inbox the orchestrator polls. Transport can become a queue later without touching consumers. Every ingested signal gets a lineage entry so attribution can cite it.

5 · Attribution ships lineage-first

v1: tactics/strategy separation + decision lineage + strategy-diff evaluation, no causal claims. v2: adstock / multi-touch attribution from GA. v3: periodic geo-holdout incrementality tests.

The later layers are unauditable without lineage, so lineage ships first.

6 · One orchestrator, stateless channel agents

A single orchestrator owns budget guards, cross-channel gating, and lineage writes. Channel agents are stateless per tick: read strategy.lock.json + channel data, emit proposed actions. Cadence: daily tactical tick, weekly experiment-evaluation tick.

Product invariant — cross-channel gating. Cross-channel signals are hypothesis-only until they earn ≥ 0.80 confidence across ≥ 3 cycles. They never silently alter per-channel strategy; they enter it only as human-approved rule changes with provenance. This is a hard constraint, not a tuning default.

Learnings

Mirror of docs/LEARNINGS.md — the log that makes disposable agents cumulative. Every regression, failed review, or workflow failure becomes an entry; every agent reads the log in full before working; proven rules get promoted into AGENTS.md.

2026-07-07 · Three headless-Codex failure modes, found and fixed in sequence

Symptom: the first Coder sat ~18 hours with zero output, no branch, no PR. Two re-fires surfaced two more failures before the worker finally ran clean.

Root causes, peeled in order: (1) backgrounded codex exec inherits an open stdin pipe and waits forever for input — the primary hang; (2) approval_policy = "on-request" would deadlock any headless run at the first sandbox approval, since nobody exists to answer it; (3) the pinned model gpt-5.5-codex isn't available on this machine's ChatGPT-account Codex — the account runs gpt-5.5. Chief monitoring compounded #1 by piping worker output through tail, which buffers until exit — nothing to see mid-run.

New rules: headless workers launch with stdin closed (< /dev/null), approval_policy = "never", network enabled; logs stream unbuffered; a silent or non-growing log for 10–15 minutes means presumed-dead — kill, diagnose, re-fire; and smoke-test the exact dispatch configuration, not a simplified variant.

Outcome: fixes promoted into .codex/config.toml; Coder model is gpt-5.5 (high; xhigh on escalation) until the account gains codex-model access. Exact-config smoke test passes; worker re-fired with error + stall monitors.