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.
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.
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).
| Command | What 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 weekly | The 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. |
| Command | What it does |
|---|---|
fable strategy show | Prints strategy.md plus active rules grouped by the strategy section they serve. |
fable strategy compile | Validates 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 lint | Validate-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/. |
| Command | What it does |
|---|---|
fable experiment propose | Registers 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 list | Confidence per hypothesis (Beta-posterior over validation cycles). |
| Command | What it does |
|---|---|
fable signals ingest | Validates JSON events in signals/inbox/ (seo.signal, creative.asset.ready, website.change) → lineage; invalid files → rejected/ with reasons. |
fable signals pull-gsc | Pulls Search Console (read-only), detects brand-keyword spikes/drops/vanishes vs the prior window, emits seo.signal events into the inbox. |
fable signals list | Ingested signals from lineage. |
fable lineage list | show <id> | trace <id> | The append-only audit trail; trace walks causal parents ("why did this happen"). |
| Command | What it does |
|---|---|
fable setup · --set k=v · --check | Run 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 amazon | Monthly Amazon search-term export to durable off-repo storage (65-day retention clock). |
fable assets request | list | Machine-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).
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.
fable setupInteractive 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.
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).
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.
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).
Architecture, issues, dispatch, evidence verification. Writes no product code.
All implementation, tests, verify runs, PRs. One disposable worker per issue.
Reviews each PR at its exact head SHA. Different model family than the Coder, by design.
Merges fire automatically once every gate is green: CI + DCO + APPROVE review, all on the exact head SHA. Any red gate blocks.
fable setup
Merged
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.
| Channel | CLI | Status |
|---|---|---|
| Google Ads | google-ads-pp-cli | LIVE — OAuth2, Basic access, 4 accounts |
| Amazon Seller (SP-API) | amazon-seller-pp-cli | LIVE — store “Babanuj”, US/CA/MX/BR |
| Amazon Ads | amazon-ads-pp-cli | Pending — API access submitted, refresh token outstanding |
| Search Console | google-search-console-pp-cli | LIVE — feeds the SEO-signal contract |
| Google Analytics | google-analytics-pp-cli | Configured |
| Meta | ~/.config/meta-ads | Config present — CLI surface to be confirmed |
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.
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.
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.
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.
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.
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.
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.
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.
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.