Auto Marketing Engine · the master plan

The Roadmap: Building a Self-Driving Marketing Department

Every section of the engine — what it genuinely does today, the numbered steps that take it to autonomy, and a copy-ready build prompt behind every item. Specific enough to build from tonight.

The Auto Marketing Engine is organized as a marketing department: fourteen specialist roles working over a shared workspace per business. This roadmap walks the department's own eighteen sections plus two foundations. For each: what exists today (grounded in shipping code), numbered build steps with an unambiguous “done when,” the open-source tooling chosen for it (every repo verified live via the GitHub API, stars as of July 2026), and — behind every item — a build prompt: paste it into a working session on the engine and that item gets built to its acceptance criterion.

The autonomy scale, and where the industry actually is

Each section carries two chips: now: A1 (honest current level) and target: A4 (where the roadmap takes it):

LevelNameMeaning
A0ManualNot built, or a human does it entirely.
A1AssistedThe engine analyzes and recommends; a human does the work.
A2ShadowThe engine produces the finished change, diffed against live, but takes no action. Rejections train it.
A3SupervisedThe engine acts; a human approves each move; everything staged and reversible.
A4ConditionalPre-approved classes of action run inside guardrails; exceptions escalate.
A5AutonomousSet goals and budget; review reports. The loop self-tunes.

Context: Gartner's 2026 survey expects AI-driven automation of marketing work to grow from 16% to 36% by 20281; only ~30% of organizations call their AI readiness mature2; industry maturity models place “autonomous” as the terminal stage almost nobody has reached3; and Forrester-line analysis expects fewer than 15% of firms to enable genuinely agentic automation near-term — governance, not capability, is the constraint4. That is why half this roadmap is guardrails.

The eight build stages (the spine)

The method — Musk's algorithm, Tesla's shadow-mode rollout, test-to-failure — is argued with sources in Self-Driving SEO. One line: automate last.

S1
Perception — read every relevant page reliably (paced fetching, full crawl, render pass, vitals).
S2
Measurement — Search Console, Analytics, rank history; every claim gets a number.
S3
Prioritization — rank work by opportunity (traffic × position × confidence ÷ effort), not severity.
S4
Generation — produce the finished artifact, quality-gated.
S5
Shadow — assemble every change the engine would make; humans review diffs; rejections train it.
S6
Supervised execution — publish adapters with staging and one-click rollback; everything logged.
S7
Attribution — watch each change's target metric; credit or blame it; feed back to S3.
S8
Autonomy — high-confidence classes run inside guardrails; the loop self-tunes per site.

Tags: shipped now (buildable immediately) next (needs a prior step) later (needs an unreached stage). The ▸ build prompt chip under each item opens a paste-ready working prompt.

Network

01 Portfolio now: A1 target: A4

What exists today The network view runs the audit engine across a whole roster (the live-site list), renders a sortable scoreboard with per-site scores, progress chips and re-run controls, and computes portfolio stats — count, average, strong / needs-work / rough, and “unreached” as its own honest category.
  1. Scheduled re-audits. now Cron-driven cadence (weekly default, per-site override) re-runs every portfolio site through the paced fetcher, storing each run.every site has a dated audit history and no run was triggered by a human.
    build prompt
    Context: Auto Marketing Engine on the droplet — /opt/autoengine/server.py (service `autoengine`, 127.0.0.1:8932), workspaces in /opt/autoengine/workspaces/<domain>.json, roster at /var/www/wholetech.com/livesites.json.
    Task: add scheduled re-audits. Create /opt/autoengine/reaudit.py that reads the roster, skips any domain audited <7 days ago (read analysis.analyzed_at in its workspace), and calls server.analyze_and_store() for the rest, sequentially, respecting the per-host pacer. Store nothing new on failure — the engine already preserves the last good workspace when a fetch fails; keep that behavior. Add a per-domain override key `reaudit_days` in the workspace. Wire a cron entry (03:30 CT nightly). Log one line per site to /var/log/ame-reaudit.log.
    Constraints: backup server.py before any edit; python3 -m py_compile; systemctl restart autoengine only if server.py changed; never fabricate scores.
    Done when: two consecutive nights of logs show automatic runs and each workspace's analyzed_at advances on its cadence.
  2. Delta detection & alerts. next Diff each new audit against the last; push only material changes (score moves ≥5, new failures, site unreachable) to a notification channel.a regression on any portfolio site produces an alert within 24h naming the specific check that changed.
    build prompt
    Context: /opt/autoengine — reaudit.py (nightly), workspaces/<domain>.json contain analysis.checks (list of {area,label,state,why}) and analysis.scores.
    Task: before reaudit.py overwrites a workspace, snapshot prior scores+checks; after the new run, diff: overall/sub-score moves ≥5, any check that changed state (pass→warn/fail or back), and reached→unreached transitions. Aggregate the night's material deltas into one digest and POST to ntfy topic `wholetech-ame-alerts` (curl -d). No deltas → no message. Persist each run's {date, scores, check_states} into a new per-workspace `history` array (append-only, cap 90 entries).
    Done when: seeding a deliberate title change on one of our own sites produces next-morning digest naming that site, the check, and the score move.
  3. Fleet learning. next When a fix class proves out on one site, surface every other portfolio site with the same finding as a one-click candidate.the portfolio view answers “where else does this exact problem exist?” with a list.
    build prompt
    Context: workspaces contain analysis.checks with stable labels (e.g. "Title tag", "Breadcrumb schema"). The network UI is drawn by drawNetTable() in /var/www/automarketingengine.com/app.js from a server endpoint.
    Task: add GET /network/finding?label=<check label>&state=fail|warn to server.py returning every roster domain whose latest workspace has that check in that state. In app.js, make each check chip in a workspace clickable → opens the portfolio filtered to "same finding elsewhere" with count. Read-only; no execution.
    Done when: clicking "Breadcrumb schema: fail" on one site lists every other site sharing it, and the list matches a manual grep of workspaces.
  4. Segment intelligence. now Slice the portfolio by category, traffic band, and score band; every network stat becomes answerable per segment.“average AI-Search score for Tech sites over 1k visits” is a two-click answer.
    build prompt
    Context: roster livesites.json has per-domain category + visits; workspaces have analysis.scores.
    Task: extend the /network summary endpoint with group-by: ?by=category|traffic_band|score_band (bands: 0, 1-99, 100-999, 1k-9.9k, 10k+). Return per-segment {count, avg overall, avg per sub-score, worst 3 domains}. In app.js add a segment selector above the network table reusing the existing chips UI. Pure aggregation — no new fetches.
    Done when: segment stats render live and one spot-checked segment average matches a hand computation.
  5. Cross-site rollouts. later Apply an approved fix class portfolio-wide through the publish adapters (S6), with per-site verification and rollback.one approval safely fixes the same issue on N sites, each site's log showing the verified result.
    build prompt
    Prereqs: S6 publish adapters + section 16 class-level approvals exist.
    Task: build /opt/autoengine/rollout.py — input: fix-class id + approved site list. For each site: stage the change via its adapter, verify staging, apply, re-fetch the page, confirm the check now passes, log {site, artifact, before, after, verdict} to the audit trail; halt the rollout on first verification failure and alert. Strictly sequential, paced, resumable (state file).
    Done when: a 3-site alt-text rollout on our own network completes with three verified logs, and killing it mid-run resumes cleanly.
Get started

02 Onboarding now: A1 target: A3

What exists today Intake captures the business's basics and carries forward on re-audit; domain checking and activation (with coupon flow) work; brand and niche are auto-detected with word-boundary matching and domain-aware overrides after earlier false-positives were found and fixed.
  1. The one-hour setup. now One wizard capturing the five inputs autonomy needs: goals, monthly ad-budget ceiling, brand guardrails, access keys (requested, never required), approval preferences.URL → configured workspace with initial audit in under one hour, measured.
    build prompt
    Context: onboarding UI lives in /var/www/automarketingengine.com/app.js (backToOnboard, applyWs, intake handling); server.py has _clean_intake and analyze_and_store(raw_url, intake).
    Task: extend intake to a 5-step wizard: (1) goals — pick 1-3 of leads/sales/rankings/visibility + free text; (2) monthly ad ceiling (number, may be 0); (3) brand guardrails — voice adjectives, do-say list, never-say list; (4) connections — GSC/GA/store, each optional with "unlocks X" copy; (5) approval preference — review-everything | weekly-batch | classes-later. Persist under ws["intake"] with schema versioning; never overwrite on re-audit (existing peek/carry-forward logic already does this — extend, don't replace). Stamp setup_started/setup_completed timestamps.
    Done when: a fresh domain walks the wizard to a stored intake and audit, and the elapsed timestamps prove <60 min.
  2. Confirm, don't guess. now Detected brand/niche shown for one-click confirmation; corrections persist and are never re-overwritten.no workspace displays an unconfirmed auto-detected niche as fact.
    build prompt
    Context: server.py detect_niche()/brand_name() set ws brand/niche on every analyze_and_store; a past bug had "shipped" niche-matching "boating".
    Task: add ws["confirmed"] = {brand: bool, niche: bool}. In the UI, render unconfirmed values with a dotted underline + "detected — confirm or correct" affordance; a correction writes the value AND sets confirmed:true. In analyze_and_store, never overwrite a confirmed brand/niche with a detected one (keep detected value in analysis.* for reference).
    Done when: correcting a niche survives three re-audits, and the UI visually distinguishes detected from confirmed.
  3. Progressive access. next Each granted connection visibly unlocks capabilities; none demanded up front.zero-connection workspaces function at A1, and each key changes the visible capability list.
    build prompt
    Context: ws["connections"] = {ga, gsc, social} booleans exist; UI shows connection state.
    Task: define a capability map in one place (server-side, served to app.js): each capability lists required connections and its section (e.g. "query-level prioritization" needs gsc). Render a per-workspace capability panel: unlocked (green), locked (grey, with which key unlocks it). When a connection flips true, the panel updates without reload.
    Done when: toggling gsc on a test workspace visibly moves its capabilities, and no feature silently no-ops for lack of a key.
  4. Connection wizard (real OAuth). next Guided per-provider flows for Search Console and GA4 that end in stored, refreshable credentials — not a boolean.a non-technical owner connects GSC in under five minutes without leaving the app.
    build prompt
    Context: connections are currently booleans. Target: real Google OAuth for Search Console + GA4 Data API, minimal read-only scopes.
    Task: implement OAuth code flow server-side in server.py (or a small companion service): /connect/google/start?ws=<domain> → consent URL; /connect/google/callback stores {refresh_token, scopes, connected_at} encrypted at rest in the workspace (never in the repo, never logged). Add token refresh helper + a /connect/google/test endpoint that lists the verified properties the token can see and stores the matched property id. Update the boolean only from real token state.
    Done when: revoking the token in Google flips the workspace connection to a visible "reconnect needed" within one scheduled self-check.

03 Department now: A1 target: A4

What exists today Fourteen roles — strategist, manager, SEO, AEO, content, social, email, paid, CRO, design, research, outreach, analytics, board — with per-role identities in the UI, department activation, and deliverables produced under role headings.
  1. Role-based task routing. now Every finding, recommendation, and deliverable owned by exactly one role, with a visible per-role queue.the department view answers “what is each role working on and what's blocked” from live data.
    build prompt
    Context: role keys in app.js (strategist, seo, aeo, content, social, email, paid, cro, design, research, outreach, analytics, board, manager); checks/priorities/deliverables live in the workspace.
    Task: add a routing table in server.py mapping check areas + deliverable types → role key (SEO checks→seo, schema/AEO→aeo, calendar items→content, social posts→social, CTA/form findings→cro, etc.). Stamp `role` on every priority, checklist item, and deliverable at generation time. In app.js, render a per-role queue view (count + items) using the existing card components.
    Done when: every item in a fresh audit carries a role, and the department view groups them correctly with zero "unassigned".
  2. Role capability contracts. next Each role declares what it produces, what it needs, and its autonomy level — machine-readable, driving the UI.the manager plans a day by querying contracts, not hard-coded assumptions.
    build prompt
    Task: create /opt/autoengine/roles.json — for each of the 14 roles: {key, produces:[artifact types], needs:[connections/inputs], autonomy_now, autonomy_ceiling, escalates_to}. Outbound roles get autonomy_ceiling:"A3" hard-coded (draft-only rule). Serve via GET /roles; refactor the daily-run planner to read produces/needs from this file before proposing role work (skip roles whose needs are unmet, and say so in the plan). UI: role cards show their contract.
    Done when: removing "gsc" from a test workspace makes the planner visibly skip query-dependent work with the reason "needs Search Console".
  3. Per-role autonomy dials. later Autonomy granted per role, not globally — SEO at A4 while paid stays A3.the approvals screen shows and enforces a distinct dial per role.
    build prompt
    Prereqs: roles.json contracts + section 16 class rules.
    Task: add ws["autonomy"] = {role_key: "A1".."A4"} (default A1, ceiling from roles.json — reject attempts above ceiling). Every execution path checks the acting role's dial: A2 renders diff only, A3 requires per-item approval, A4 consults class rules. Approvals screen gets a per-role dial row with the ceiling greyed out. Log every dial change to the audit trail with actor + timestamp.
    Done when: setting seo=A4 and paid=A3 on a test workspace produces observably different behavior for the two roles' items in one daily run.
Operations

04 Overview now: A1 target: A2

What exists today A readiness gauge, phase strip, progress bar, and a live checklist with persisted done-state; the overview grades the workspace at a glance.
  1. Truthful status, everywhere. now Every overview number traces to a dated source; anything unmeasured says “unmeasured,” never zero.clicking any overview stat reveals when and how it was measured.
    build prompt
    Context: overview widgets in app.js (gaugeSVG, ov-progress, ov-checklist) read workspace fields; the audit layer already refuses to score unreadable pages and reach uses None for unmeasured.
    Task: introduce a provenance envelope for every displayed stat: {value, measured_at, method, source}. Server: wrap overview payload fields in envelopes (audit → analyzed_at + "page audit"; traffic → stats period + "network analytics"; checklist → last toggle). UI: hovering/tapping a stat shows its envelope; any field lacking one renders "unmeasured" in muted style — never 0.
    Done when: every stat on the overview answers "when + how" on click, and a workspace with no traffic feed shows "unmeasured", not zero.
  2. The weekly brief. next Auto-generated one-pager — what changed, what shipped, what moved, what's proposed — written for a business owner.a non-marketer reads Monday's brief in three minutes and knows what to approve.
    build prompt
    Prereq: per-workspace history array (01.2).
    Task: add /opt/autoengine/brief.py — for one workspace, compose: (1) score deltas this week from history; (2) checks that changed state; (3) deliverables approved/shipped; (4) top 3 proposals for next week, each citing its finding; (5) blockers. Render to /var/www/<site brief dir>/brief/<YYYY-WW>/index.html using the wt/dept design system, plain-language voice (no jargon; every number carries its provenance). Cron Monday 07:00 CT; ntfy ping with the URL.
    Done when: two consecutive Mondays produce accurate briefs a non-marketer can act on, verified against the raw workspace.
  3. Owner view vs. operator view. next One workspace, two lenses: the owner sees outcomes and approvals; the operator sees checks, queues, and logs.toggling lenses changes information density without hiding any approval-relevant fact from the owner.
    build prompt
    Context: app.js renders one overview for everyone.
    Task: add a lens toggle (persisted in localStorage). Owner lens: gauge, plain-language deltas, approvals inbox count, weekly brief link — no raw check tables. Operator lens: everything current plus per-role queues and run logs. Both lenses share the same data payload — this is presentation only; never omit pending approvals or executed-change notices from the owner lens.
    Done when: both lenses render from one payload and an owner can reach any approval in ≤2 clicks.

05 Manager now: A1–A2 target: A4

What exists today A daily-run system at workspace and network level: the manager produces daily proposed actions and deliverables landing in a pending queue with counts and per-item approve/act controls. The seed of orchestration.
  1. The daily standup, formalized. now Each daily run produces a structured plan: yesterday's outcomes, today's proposals (each tied to a role, a skill, and a finding), open blockers.every proposed action cites the finding or metric that justifies it.
    build prompt
    Context: server.py run_daily_ws/run_daily_all produce proposals; role routing from 03.1; skills library on the site.
    Task: restructure the daily-run output to {date, yesterday:[{item, outcome}], today:[{role, skill, action, justification:{check_label|metric, value}}], blockers:[{item, reason, needs}]}. Reject (server-side) any proposal lacking a justification pointer — log it as a planner error instead of showing it. Persist each day's plan in the workspace under daily[] (cap 30).
    Done when: a week of plans shows every proposal with a real justification, and the planner-error log is empty on our own sites.
  2. Exception escalation (disengagement). next The manager detects when it's out of its depth — conflicting signals, low confidence, unreachable site, guardrail near-miss — and hands the item to a human with context.low-confidence items reliably route to humans instead of shipping, with the reason stated.
    build prompt
    Task: define escalation triggers in server.py as data, not scattered ifs: unreachable ≥2 consecutive runs; two checks giving contradictory guidance on the same element; any generated artifact failing its validator; any action within 10% of a guardrail cap. On trigger, create an escalation item {trigger, evidence, recommended question for the human} in the approvals queue flagged "needs human", and suppress the related auto-proposal. ntfy on new escalations.
    Done when: simulating each trigger class on a test workspace produces a correct escalation and no shipped action.
  3. Attention digest. next One daily network-level note: which sites need a human today, and the single highest-leverage pending item overall.an operator triages the whole portfolio from one message.
    build prompt
    Prereqs: daily plans (05.1) + escalations (05.2) exist per workspace.
    Task: extend run_daily_all to emit one digest after all workspaces run: sites with escalations (grouped by trigger), sites with >N pending approvals, the top opportunity item network-wide (by S3 score once built; until then, highest-weight failing check on the highest-traffic site). Deliver via ntfy `wholetech-ame-alerts` + a dated page under the network dashboard. No news → "all quiet" one-liner.
    Done when: the digest correctly triages a seeded escalation above routine approvals two days running.
  4. Cadence intelligence. later The manager learns each site's natural rhythm from attribution data.two different sites run on measurably different, data-justified cadences.
    build prompt
    Prereq: S7 attribution records exist.
    Task: per workspace, compute from history: how often audits find new material deltas, how long changes take to show attributable movement (GSC lag), and content-decay half-life. Derive recommended cadences {reaudit_days, content_slots_per_week, review_batch} with the evidence attached; write them as *proposals* into the approvals queue (never silently change cadence). Approved values feed reaudit.py and the planner.
    Done when: one high-velocity and one static site receive different proposed cadences, each defensible from its own history.

06 Audit & Report now: A1 (hardened) target: A2

What exists today The most battle-tested part: ten on-page SEO checks (including heading document order and structured data across JSON-LD and microdata/RDFa), content / AI-search / social / technical sub-scores, real-traffic reach, honest refusal to score unreadable pages, adaptive rate-limit pacing, and a shipped sitemap-driven multi-page crawl that detects site-wide patterns (SKU-only product titles, brand-doubled titles, per-template missing meta, heading disorder, thin alt coverage). Verified this month against independently-parsed ground truth plus an adversarial second pass.
  1. Multi-page crawl in the product. shipped→now The crawl module works; wire it in as the default audit with site-wide findings first-class in the report.a new audit shows per-template findings (“9 of 10 product pages…”) without CLI involvement.
    build prompt
    Context: /opt/autoengine/crawl.py exists (sitemap-driven, classify(), audit_page(), site_issues aggregation) and reuses server.fetch/analyze_html; analyze_and_store() currently audits only the homepage.
    Task: integrate: analyze_and_store gains crawl=True default → runs crawl.crawl(domain, per_type=4, cap=16) after the homepage analysis and stores the result under ws["analysis"]["site"] = {sitemap_by_type, pages_read, site_issues, pages:[trimmed per-page rows]}. Surface site_issues as first-class checks in the report ("Product titles", "Template meta coverage", …) with pass/warn/fail thresholds. Timebox: if the crawl exceeds 120s, store what completed with crawl_partial:true — never block the audit.
    Done when: a fresh audit of a Shopify site shows "N of M product pages titled by SKU" in the UI with zero manual steps.
  2. Render pass for JS themes. now Optional headless render (desktop node, never the production server) for sites whose content/schema exists only after JavaScript.a client-rendered page scores identically to its rendered DOM, and the report notes when the render pass ran.
    build prompt
    Context: network rule — no browser scanners on the droplet; Playwright runs from a Windows box. crawl.py flags candidates: page ok but word_count < 150 while raw HTML > 200KB, or schema_types empty while the page loads app JS bundles.
    Task: build render-worker.ps1 + render.py for the Windows box: read a queue file of {url, workspace} the droplet writes to /opt/autoengine/render-queue.json; Playwright-render each (wait networkidle), save rendered DOM to the droplet via scp, then the droplet re-runs analyze_html on the rendered HTML and stores both raw and rendered signals with rendered:true. Queue drains on a scheduled task; paced, 1 concurrent.
    Done when: a known client-rendered page's stored signals show real title/schema from the rendered DOM, flagged rendered:true in the report.
  3. Core Web Vitals. now Field data via the public CrUX/PageSpeed API for LCP/INP/CLS as its own technical section; lab audits via Lighthouse (30.5k★) / site-wide via Unlighthouse (4.7k★) from the desktop node.every audit includes vitals with Google's thresholds, or an honest “no field data for this origin.”
    build prompt
    Context: droplet may call the PageSpeed Insights API (free key) — that's an API call, not a browser. Lab runs use GoogleChrome/lighthouse or harlan-zw/unlighthouse on the Windows node only.
    Task: (1) server.py: fetch PSI field data per audited origin (LCP/INP/CLS p75 + verdicts), store under analysis.vitals with provenance; absent field data → vitals:"no field data" (never zeros). Surface as a "Performance" check trio using Google's published thresholds. (2) Windows node: unlighthouse run per site monthly writes a summary JSON the droplet ingests into the workspace as lab_vitals.
    Done when: an audit shows field vitals with thresholds for a high-traffic site AND an honest no-data state for a tiny one.
  4. Internal-link graph. next From the full crawl: orphan pages, dead ends, click-depth outliers; hub/authority structure feeding prioritization.the report lists every orphan page and the shortest path to fix it.
    build prompt
    Context: crawl.py fetches pages; sitemap gives the full URL set.
    Task: extend audit_page to also extract internal hrefs (same-host, canonicalized). Build graph.py: nodes = sitemap URLs, edges = observed links; compute inbound counts, click depth from home (BFS), orphans (in sitemap, zero observed inbound), dead ends (zero outbound). Store under analysis.site.link_graph {orphans:[], deep:[{url,depth}], stats}; add checks "Orphan pages" and "Click depth" with thresholds (orphans=0 pass; depth ≤3 pass). For each orphan, suggest the 2 highest-authority related pages to link from (shared path segment or title-word overlap).
    Done when: seeding one orphan page on our own site is caught on the next crawl with a sane link suggestion.
  5. Duplicate & thin content. next Fingerprint the crawl to catch near-duplicate templates and sub-200-word money pages.duplication clusters appear with canonical-choice recommendations.
    build prompt
    Task: in crawl.py, per page compute a shingle fingerprint of the main text (strip nav/footer via the largest-text-block heuristic; 5-word shingles; minhash 64 perms — pure stdlib, no deps). Cluster pages with Jaccard > 0.7; flag thin pages (<200 words, type product/collection/page). Store analysis.site.duplication {clusters:[{urls, similarity}], thin:[urls]}; checks "Duplicate content" / "Thin pages". Recommend the canonical per cluster: highest inbound links, else shortest URL.
    Done when: two known near-identical pages on a test site cluster together with a sensible canonical pick, and a seeded 80-word page lands in thin[].
  6. The diffable audit. next Every audit stores its full check-level result; any two runs diff into “what changed.”“show me what this month's work changed” is one click, citing specific checks.
    build prompt
    Prereq: history[] per workspace (01.2) storing {date, scores, check_states}.
    Task: add GET /diff?domain=&from=&to= returning {score_deltas, checks_changed:[{label, from, to}], site_issue_deltas, pages_gained_lost}. UI: a "compare runs" picker on the audit view rendering the diff with the existing pass/warn/fail chips and plain-language summary line ("4 checks improved, 1 regressed"). The weekly brief (04.2) consumes this endpoint.
    Done when: the diff between pre- and post-fix runs on a real site names exactly the checks that were fixed, verified by hand.
  7. Coverage cross-check vs. the open canon. now Reconcile the check set annually against the 2.7k★ open SEO checklist and Google's Search Essentials — every item covered, consciously excluded (reason recorded), or added.a published coverage table maps every canon item to a check, an exclusion note, or a roadmap entry.
    build prompt
    Sources: github.com/marcobiedermann/search-engine-optimization (checklist, 2.7k★) + developers.google.com/search essentials.
    Task: fetch the checklist README; enumerate its items; build /var/www/automarketingengine.com/audit-coverage/index.html — a three-column table (canon item → our check | excluded-with-reason | roadmap link), generated by a script (/opt/autoengine/coverage.py) that reads the live check labels from server.py so it can't drift. Legitimate exclusions get honest reasons ("keyword-density: obsolete guidance", "server log analysis: needs log access — roadmap 12.x"). noindex the page? No — it's a trust asset; index it.
    Done when: the table renders with zero unclassified canon items and regenerating it after adding a check updates it automatically.

07 Roadmap (in-app) now: A1 target: A3

What exists today The engine generates a phased plan per workspace from its checks, sub-scores, niche, and intake — severity-ranked, rendered with a persisted checklist.
  1. Opportunity-weighted ranking. next Expected impact = query impressions × current position × fix confidence ÷ effort, using GSC data.every roadmap item shows the number that earned its rank.
    build prompt
    Prereq: GSC wired (12.1) — per-page queries, impressions, positions.
    Task: in server.py roadmap()/priorities(), join each finding to the pages it affects and their GSC rows. Score = Σ(impressions × position_gain_potential) × fix_confidence ÷ effort, where position_gain_potential favors positions 5-20 (page-2 gold), fix_confidence is per check class (alt text 0.9, structural 0.5), effort is a per-class constant. Fall back to severity ranking when GSC is absent — and label which ranking mode is in effect. Every item stores its score inputs for display.
    Done when: on a GSC-connected site, a page-2 high-impression title fix outranks a cosmetic homepage warn, and the item shows its math.
  2. Dependency awareness. next Items know their prerequisites; the plan orders itself.no generated plan proposes a step whose prerequisite isn't met or scheduled.
    build prompt
    Task: add a `requires` field to roadmap item classes (data, not code): e.g. attribution items require "history≥2 runs"; A/B items require "beacon connected + traffic≥1k/mo"; content-refresh requires "editorial memory". At plan-generation, items with unmet requires move to a "blocked — needs X first" section instead of the active plan, with the unlock named. The checklist can't check a blocked item.
    Done when: a zero-connection workspace's plan shows measurement wiring first and every blocked item names its unlock.
  3. The approvable plan. later Approving a plan item at class level authorizes the S8 loop for that class on that site.plan approval state directly gates what the engine may do unattended.
    build prompt
    Prereqs: class rules (16.2) + per-role dials (03.3).
    Task: unify: a roadmap item of an autonomy-eligible class renders an "authorize this class" control (owner lens); approving writes the class rule AND records it on the plan item. The S8 executor consults only class rules — the plan is the human-facing surface of the same state, never a second source of truth. Revoking from either surface revokes both.
    Done when: authorizing "meta description fixes" from the plan makes exactly that class run unattended, visible in the audit trail, and revocation stops it within one cycle.

08 Content now: A1 target: A4

What exists today A 12-frame calendar generator with channel assignments and an answer-first instruction per piece, plus brief cards in the UI. Honest: template-grade until grounded — the proof of grounded quality is the hand-built 30-day plan at /polymagnet-content/.
  1. Grounded calendars, generated. now The generator consumes the full crawl (products, categories, FAQs, competitor gaps) so every slot names real topics for this business.no generated slot could be pasted onto a different company.
    build prompt
    Context: server.py content_calendar(niche, brand) currently fills 12 generic frames; the crawl (06.1) yields real products, categories, FAQ topics; the reference-quality bar is /polymagnet-content/ (four pillars: foundations/AEO, applications, decision, authority).
    Task: rewrite content_calendar(ws) to take the whole workspace: extract entities from crawl pages (product names, category nouns, FAQ questions found, materials/specs vocabulary); build a 4-week pillar plan where every slot cites its grounding {source_url or finding}; keep frames as fallback ONLY when the crawl is empty, and label the calendar "generic — connect a crawl" in that case. Each slot keeps the answer-first instruction + channel.
    Done when: calendars for two different real sites share zero slot titles, and every slot's grounding link resolves.
  2. Brief → draft. next Each slot expands into a full draft via the skills library, grounded in workspace facts, answer-first.a draft cites only facts present in the workspace; anything else is marked for human input.
    build prompt
    Prereq: grounded calendar (08.1).
    Task: add draft generation per slot: assemble a grounding pack (the slot's source pages' actual text from the crawl store, brand guardrails from intake, target question) and produce a draft with: 40-60 word direct answer first, then structure per the relevant skill (how-to/comparison/FAQ), internal links only to real URLs from the sitemap, and a FACTS-NEEDED block listing any claim the pack couldn't support (prices, dates, testimonials — never invented). Store drafts under ws["drafts"] with status:pending_review.
    Done when: a generated draft contains zero unsupported specifics and its FACTS-NEEDED list is accurate for a page a human checks.
  3. The quality gate. next Every draft scored before a human sees it — grounding, voice, usefulness, originality; below-bar drafts auto-rejected. Aligned with Google's people-first guidance.the gate demonstrably rejects a planted slop draft and passes a good one.
    build prompt
    Task: build gate.py: score each draft 0-100 on four axes — grounding (every named fact matches the grounding pack; unsupported claim = heavy penalty), voice (guardrail adjectives present, never-say list absent), usefulness (does the first 60 words answer the slot's target question — testable: does it contain the question's object + a verb + a specific?), originality (n-gram overlap vs. the 12 generic frames and vs. existing site pages < threshold). Gate < 70 → status:auto_rejected with axis breakdown (these train 08.1/08.2). Include two fixture drafts in a test: one deliberate slop (generic, unsupported superlatives), one good — assert reject/pass.
    Done when: the fixture test passes and a week of real drafts shows rejections with actionable axis reasons.
  4. Decay & refresh detection. next Published pieces are monitored; decaying winners get refresh proposals before they fade.a declining page triggers a refresh proposal citing its query-level decline.
    build prompt
    Prereq: GSC history (12.2).
    Task: monthly job: for each indexed content URL, compare trailing-28-day clicks/impressions vs. the prior period; a >30% decline on a page that previously earned >100 impressions/mo = decay candidate. Proposal = refresh brief citing the declining queries, current top-ranking competitors for them (13.2 when built), and the page's last-modified date. Lands in the approvals queue under the content role.
    Done when: a genuinely declining page on our own network produces a refresh proposal whose cited numbers match GSC's UI.
  5. Publish adapters. later Approved content ships via S6 — staged, verified live, reversible.approve-to-live requires zero copy-paste and every publish has a rollback.
    build prompt
    Prereqs: quality gate (08.3), approvals queue (16.1), audit trail (16.3).
    Task: adapter interface {stage(draft)→preview_url, publish()→live_url, rollback()}: implement first for static-webroot sites (write into /var/www/<site>/<section>/, regenerate both sitemaps per the network's two-sitemap standard, verify HTTP 200 + title match); then WordPress (REST, application password, status:draft→publish); then Shopify (Admin API, blog articles, published:false → owner clicks live or approves push). Every publish logs artifact + inverse op.
    Done when: one approved article flows draft→staged→live→verified on a network site, and rollback() cleanly removes it.
  6. Editorial memory. later The content role knows what's published, what ranked, what cannibalizes — before proposing.no proposal duplicates a live piece's target query without an explicit consolidation plan.
    build prompt
    Task: maintain ws["editorial"] = per published piece {url, target_query, published_at, gate_score, gsc_trend}. At proposal time (08.1/08.4), check new slots against editorial memory: same target query → propose refresh/consolidation instead of a new piece; adjacent query → require a differentiation line in the brief. Cannibalization check: two live pieces splitting impressions for one query → consolidation proposal.
    Done when: proposing a deliberately duplicate topic on a test workspace yields a consolidation plan, not a second article.

09 SEO & AI Search now: A1 (verified) target: A5

What exists today The flagship, and the deepest section of this roadmap. Shipping now: the ten verified on-page checks, AEO signals (question headings, extractable answer blocks, llms.txt / AGENTS.md / agent-surface probes, schema completeness), the multi-page crawl with site-wide pattern detection, adaptive pacing, and honest failure. Below, the full build-out in five sub-groups — on-page, technical, AI search, authority, and the execution loop — each item with its tooling and its build prompt. The engineering rationale and staging live in Self-Driving SEO.
A · On-page & structured data
  1. Fix artifacts, not advice. now For each finding, the exact deployable change: the rewritten title, the complete JSON-LD block, the meta at length, the alt-text set — grounded in the page's real content, validated before display.a finding's fix can be applied verbatim by a non-technical owner with zero editing.
    build prompt
    Context: server.py checks produce {label, state, why, fix} where fix is advice text. Signals available per page: title, meta, h1s, schema_types, crawl page text.
    Task: add generate_fix(check, sig, page_text) returning a typed artifact: {kind: title|meta|jsonld|alt_set|heading_plan, content, grounded_on:[fields used], validator_passed}. Titles: ≤60 chars, primary keyword from H1/page nouns first, brand once. Meta: 150-160 chars, contains the page's direct answer phrase. JSON-LD: built from real page data only (see 09.2). Validators run before display (length, JSON parse, schema required-fields). UI: each failing check shows its artifact in a copy box (reuse copyText()).
    Done when: for a real audited site, every failing on-page check displays a paste-ready artifact that passes its validator, with zero invented facts.
  2. The schema factory. now Per-type JSON-LD generators — Organization, WebSite+SearchAction, BreadcrumbList, Product/Offer, FAQPage, Article, LocalBusiness — built from crawl data against the schema.org vocabulary (6.2k★ repo) and validated.generated blocks pass Google's Rich Results test for every type the site legitimately qualifies for.
    build prompt
    Reference: schema.org vocabulary (github.com/schemaorg/schemaorg); the network already has JSON-LD repair precedent in /root/fix_jsonld.py — reuse its patterns.
    Task: schema_factory.py with one generator per type, each taking (workspace, page) and refusing to emit when required source data is absent (a Product block without a real price → return needs:["price"] instead of inventing). Organization pulls name/logo/sameAs from intake+crawl; FAQPage only from question-formed headings with answer blocks actually on the page; Breadcrumb from the URL path + real page titles. Validate: json.loads + required-props per type + no empty strings. Wire into generate_fix (09.A1).
    Done when: emitted blocks for two real sites validate in Google's Rich Results test, and a data-poor site yields honest needs[] lists, not hollow markup.
  3. Title/meta engine with pixel-width truth. now Titles and metas validated by rendered pixel width (what Google actually truncates on), not just character count.no generated title/meta truncates in a SERP preview at desktop or mobile widths.
    build prompt
    Context: char-count checks (40-60 / 130-160) approximate what is really a ~580px desktop title budget.
    Task: embed a static per-character width table for Arial 20px (titles) / 14px (descriptions) in server.py (ASCII + common punctuation; wide-char fallback avg). px_width(s) sums it. Checks and generators (09.A1) enforce: title ≤ 580px desktop / ≤ 920px mobile-safe, meta ≤ 990px. Check `why` strings report both chars and px. Add a SERP preview snippet in the UI (plain HTML styled like a result, no external calls) rendering the proposed title/meta at both widths.
    Done when: a 58-char title full of wide caps correctly flags over-budget while a 62-char narrow one passes, and the preview matches.
  4. Image SEO, complete. now Context-grounded alt generation, filename sanity, dimension/lazy-load checks, and modern-format flags across the crawl.every meaningful image on sampled pages has a grounded proposed alt, and decorative images are correctly marked empty-alt.
    build prompt
    Context: crawl.py counts imgs + alt coverage; pages carry surrounding text.
    Task: per image in sampled pages capture {src, current_alt, nearest heading, preceding paragraph snippet, in-link?}. Propose alt from context (product name + variant nouns near it), ≤125 chars, no "image of"; images inside links get destination-describing alt; genuinely decorative (spacers, background art) → propose alt="" deliberately. Also flag: filename gibberish (IMG_1234), missing width/height attrs (CLS risk), no loading=lazy below the fold, and legacy formats where >200KB. Output as an alt_set artifact per page (09.A1).
    Done when: the polymagnet product page's 4 images yield sensible grounded alts (verified by eye) and the logo correctly proposes brand alt, not "logo".
  5. Internal-link recommender. next From the link graph: specific from→to link insertions with anchor text, prioritized by authority flow to money pages.each recommendation names the exact source page, insertion context, and anchor.
    build prompt
    Prereq: link graph (06.4).
    Task: recommender: for each high-value target (product/collection pages, top GSC pages), find source pages with topical overlap (shared title/heading nouns) that don't yet link to it; rank by source inbound-count. Recommend {source_url, target_url, anchor (target's primary keyword, varied), insertion_hint (the source paragraph mentioning the topic)}. Cap 5 recs per target; never recommend nav/footer links (body links only); no reciprocal spam patterns.
    Done when: recommendations for one site are human-judged sensible ≥80% and each names a real paragraph that mentions the topic.
B · Technical
  1. Indexation control. next One view reconciling what should be indexed (sitemap) against what is (GSC), with robots, canonicals, redirect chains, and 404s audited together.the report names every sitemap URL Google hasn't indexed and the most likely reason.
    build prompt
    Prereq: GSC wired (12.1 — URL inspection quota is limited; use the index-coverage dimensions of the Search Analytics + sitemaps APIs first).
    Task: indexation.py per workspace: (a) sitemap set vs GSC-indexed set → not-indexed list; (b) per not-indexed URL, check crawl data for the likely cause in order: noindex meta, robots block, canonical→elsewhere, redirect (follow chain ≤5, flag chains≥2), 4xx/5xx, thin/duplicate (06.5), orphan (06.4); (c) inverse: indexed-but-not-in-sitemap. Store analysis.indexation; check "Indexation coverage" pass ≥90%.
    Done when: the not-indexed list for a real site matches GSC's UI count ±5% and each row's stated reason survives a spot check.
  2. hreflang & i18n validation. next Detect localized URL patterns and validate the hreflang mesh — or its absence.a multi-locale site shows a complete hreflang map or a specific list of missing/broken pairs.
    build prompt
    Context: the polymagnet audit found locale-prefixed paths in robots.txt with zero hreflang emitted — that exact pattern is the target.
    Task: detect locale patterns from sitemap+robots (/{xx}/ or /{xx-YY}/ prefixes, ccTLD variants from intake). If localized URLs exist: fetch a sample pair set; validate hreflang link tags both directions + x-default, flag missing return links, wrong region codes (ISO 639-1/3166 tables in-code), and hreflang pointing at redirects/404s. No localization → check passes as n/a (not silently).
    Done when: a known-good international site validates clean and the seeded broken-pair fixture is caught with the exact pair named.
  3. Redirect & error hygiene. next Site-wide sweep of redirect chains, loops, mixed 301/302 usage, and soft-404s.every chain ≥2 hops and every soft-404 is listed with its fix.
    build prompt
    Task: extend the crawl with a HEAD-first sweep of all sitemap URLs + all internal link targets (paced): record status + Location chains (cap 5). Flag: chains ≥2 (recommend direct 301), loops, 302-where-permanent (heuristic: stable >30 days across runs), and soft-404s (200 with <100 words + "not found"-family phrases). Store analysis.site.redirects; checks "Redirect chains"/"Soft 404s".
    Done when: a seeded 3-hop chain and one soft-404 on our own network are both caught with correct fixes on the next run.
C · AI Search (AEO/GEO)
  1. llms.txt generation. now Generate a spec-conformant llms.txt per site from the crawl — per the llms.txt spec (2.5k★, AnswerDotAI) the engine already probes for.every network site serves a valid llms.txt whose links resolve, and the check that probes it passes.
    build prompt
    Reference: the llms.txt spec at github.com/AnswerDotAI/llms-txt — H1 site name, blockquote summary, H2 sections of [title](url): description lists.
    Task: llmstxt.py generates from workspace: name (confirmed brand), summary (from confirmed niche + homepage answer block — no marketing fluff, information-dense per the spec's guidance), sections: Products/Services (crawl product/collection pages), Guides (blog/FAQ pages), Company (about/contact). Only URLs verified 200 in the crawl. Write to the webroot; verify serve; re-run the engine's existing llms probe to confirm the check flips to pass. Batch mode for the whole roster (the network already targets Agents-First scores).
    Done when: three network sites serve conformant llms.txt with all links live, and their AI-Search sub-scores reflect it on re-audit.
  2. Answer-block optimization. now Per money page: a question-formed heading plus a 40–60 word extractable answer, generated as fix artifacts — the format answer engines actually quote.each target page has a proposed answer block whose facts all exist on that page.
    build prompt
    Context: analyze_html already measures q_headings and answer_blocks (120-180 word paras); the gap is generation.
    Task: for each crawled money page lacking an answer block: derive the page's primary question (from its title/H1 + GSC top query when wired), draft a 40-60 word direct answer USING ONLY facts on the page (grounding pack = page text; unsupported → needs[] not invention), formatted as <h2>question</h2><p>answer</p> artifact for insertion after the H1. Validate: contains a specific (number/name/behavior), no first-person fluff, ≤60 words.
    Done when: generated blocks for five real pages are fact-checked clean against their pages, and the q_headings/answer_blocks signals improve on a staged test page.
  3. AI-citation tracking. next Beyond Google rank: is the site cited by AI answer engines for its money queries, tracked over time.the workspace charts AI-citation status alongside classic rank for the same queries.
    build prompt
    Constraints: subscription-only economics — no per-query paid APIs; keep volume tiny and honest.
    Task: define per workspace ≤10 tracked queries (from GSC top queries + intake goals). Monthly, from the Windows node (not the droplet), run each query against the free/consumer surfaces available to us and record ONLY verifiable outcomes: {engine, query, cited: yes/no/unknown, evidence_url_or_screenshot_path, checked_at}. "unknown" is a legitimate value — never coerce. Store in the history layer; chart presence over time next to GSC position. Revisit tooling quarterly as AI-search APIs mature (this space is moving; the design isolates the collector so it's swappable).
    Done when: two monthly cycles produce a citation timeline for one site with evidence artifacts for every "yes".
  4. Entity & sameAs footprint. next Establish the business as a machine-readable entity: consistent Organization data plus a sameAs graph across its real profiles.the site's Organization block links every verified profile and identical NAP data everywhere the engine controls.
    build prompt
    Context: the network's AEO precedent (sameAs pushes on prior projects) + schema factory (09.A2).
    Task: entity.py: collect candidate profiles (intake asks; crawl finds social links; verify each URL 200 and actually-owned via link-back or handle match). Build the canonical entity record {legal name, url, logo, sameAs[verified only], NAP if local}. Emit via schema factory into the Organization block; diff NAP consistency across the site's own pages (footer vs contact vs schema) and flag mismatches. Unverifiable profiles → needs[] for the owner, never guessed into sameAs.
    Done when: one site ships an Organization block with ≥3 verified sameAs links and zero NAP mismatches site-wide.
  5. Agent-surface completeness. next The full Agents-First stack the engine already probes — llms.txt, AGENTS.md, index.md, OpenAPI, MCP card — generated, not just checked.a network site scores full agent-surface coverage from generated files that honestly describe it.
    build prompt
    Context: server.py probes llms.txt/AGENTS.md/openapi.json/.well-known/mcp-server-card.json/index.md; the network's af-deploy precedent targets 90-100 AF scores.
    Task: extend llmstxt.py into agent_surface.py generating the full set per site from workspace truth: AGENTS.md (what an agent can do here: read guides, check availability, contact — only true affordances), index.md (markdown mirror of the homepage's answer content), and where the site has real endpoints, a minimal honest openapi.json; skip MCP card unless a real MCP exists (never stub capabilities that don't work). Verify each serves + re-probe.
    Done when: the AI-Search sub-score reflects full surface on two sites and every declared affordance actually works when exercised.
D · Authority
  1. Backlink intake via GSC. next The links Google already reports — top linking sites, most-linked pages — pulled into the workspace, no paid vendor.the workspace shows its real link profile and flags toxic-pattern anomalies for human review.
    build prompt
    Prereq: GSC OAuth (02.4). Note: link data comes via the Search Console UI export surfaces — automate what the API exposes; where API coverage is thin, a monthly manual-export ingest path (drop CSV in a watched dir) is acceptable and labeled as such.
    Task: links.py ingests {top linking sites, top linked pages, anchor sample} into ws["links"] with pulled_at provenance. Flags for review (never auto-action): sudden linking-domain spikes, anchor-text over-optimization (one commercial anchor >30%), linked-page 404s (reclaim opportunity: propose redirect). Surface in the report as an Authority section.
    Done when: one site's link profile matches its GSC UI and a seeded linked-404 produces a reclaim proposal.
  2. Linkable-asset pipeline. later Content engineered to earn links — original data, tools, definitive guides — proposed from what the niche's linkers actually cite.each proposed asset names the specific pages/sites that link to comparable assets.
    build prompt
    Prereqs: competitor tracking (13.2) + links intake (09.D1).
    Task: for the niche, identify link-magnet patterns: what asset types earn links to competitors (calculators, spec tables, original stats, glossaries) — evidence from their most-linked pages. Propose ≤2 assets/quarter with {format, working title, the evidence list of linkers-of-comparables, effort estimate}; route through content pipeline (08) with the quality gate; outreach handoff to 14.3 once live.
    Done when: one proposal ships with real evidence and, post-publish, its link acquisition is tracked in 09.D1.
E · The execution loop
  1. Rank & visibility history. now GSC position history as the backbone; optional self-hosted SerpBear (2.0k★) for daily spot-checks on the few queries that matter most.any tracked query charts position over time from stored data, with source labeled.
    build prompt
    Prereq: GSC wired (12.1). Optional: towfiqi/serpbear self-hosted on the droplet for ≤10 daily-tracked queries/site (respect its scraper config; keep volume tiny).
    Task: rank_history.py: weekly GSC pull per workspace stores {query, page, position, impressions, clicks} into an append-only SQLite (one DB, table keyed by domain) — SQLite because history outgrows JSON workspaces. Charting endpoint /rank?domain=&query= returns the series; UI sparklines on the workspace (reuse the dataviz conventions). If SerpBear is deployed, ingest its daily positions with source:"serpbear" vs "gsc" labeled distinctly (they measure differently — never blend).
    Done when: eight weeks of history renders for a site and the two sources, where both exist, display as separate labeled series.
  2. Shadow-mode SEO changesets. next Every generated fix assembled into a reviewable changeset diffed against live, per-class accept/reject — rejections captured as training signal.an owner reviews a complete changeset in one sitting; every rejection records a reason.
    build prompt
    Prereqs: fix artifacts (09.A1-A4), approvals queue (16.1).
    Task: changeset builder: after each audit, bundle all validated artifacts into ws["changesets"][] = {id, created, items:[{check, page, kind, before (live value), after (artifact), class}]}. UI: a changeset review screen — per-item before/after diff (title: strikethrough→new; jsonld: pretty-printed), accept/reject per item AND per class, rejection requires a reason (dropdown + free text) stored on the item. Accepted items move to the execution queue at the site's autonomy level; nothing executes from this screen at A2.
    Done when: a full changeset for a real site is reviewable end-to-end in <15 min and rejects carry reasons into the workspace.
  3. Supervised → conditional execution. later Accepted changesets ship via adapters; a class earns A4 on a site after enough accepted-and-attributed wins — alt text and meta first, structural last.one fix class runs unattended on one site with a clean 30-day attribution record.
    build prompt
    Prereqs: publish adapters (08.5 pattern applied to page-level edits), attribution (12.4), class rules (16.2).
    Task: execution engine: pull accepted items; per adapter apply → verify (re-fetch page, confirm the check now passes) → log with inverse op. Class-graduation rule (data-driven, per site): a class is A4-eligible when ≥20 accepted items, ≥95% verification success, and no attributed regressions in 30 days; eligibility surfaces as a proposal in approvals — a human still flips the switch. Order of eligibility hard-coded: alt_set → meta → title → jsonld → heading_plan (structural last).
    Done when: alt-text runs unattended on one of our own sites for 30 days with every action verified, logged, and reversible.
  4. The SEO regression suite. now Everything this month's hardening taught, frozen as tests: the fabrication bug, the sitemap-index bug, the microdata miss, the brand-"Home" bug, SKU detection.the suite runs on every engine change and each historical bug has a test that would have caught it.
    build prompt
    Context: real fixture pages exist from the hardening work (saved homepage/product/collection HTML + sitemap index).
    Task: /opt/autoengine/tests/test_seo.py (pure stdlib unittest): fixtures in tests/fixtures/. Cases: (1) 429-with-empty-body → run_engine returns error, workspace untouched; (2) sitemapindex → deep count, not 5; (3) microdata-only page → schema_types non-empty; (4) "Home - Brand" title → brand ≠ "Home"; (5) SKU title → sku_like_title true; (6) brand-doubled true positive AND the false-positive case (repeated common noun ≠ brand) — fix _brand_doubled to compare against the workspace brand token if this fails; (7) heading skip detection. Add a make-style runner + a pre-restart hook: py_compile + tests must pass before systemctl restart.
    Done when: the suite passes, and reverting any one historical fix makes exactly its test fail.
  5. Coverage cross-check vs. canon — see Audit 06.7; the same table governs this section's completeness.

10 Paid Media now: A0–A1 target: A3–A4

What exists today Honest answer: the least-built section. Paid exists as a role and in recommendations; there is no ad-platform integration — deliberately, because real money demands measurement and attribution first. Design decision already made: budget is a setup parameter, enforced engine-side and platform-side.
  1. Read-only first. next Google Ads (then Meta) connected read-only: campaigns, spend, performance into the workspace.the workspace reports last month's actual spend and cost-per-result with no write scopes granted.
    build prompt
    Prereq: OAuth plumbing (02.4 pattern).
    Task: Google Ads API integration, read-only scope only: nightly pull per connected workspace of {campaign, ad group, spend, impressions, clicks, conversions, cost/conv} into the history SQLite with provenance. Workspace Paid panel renders spend vs. the intake budget ceiling (10.0) and flags: spend > ceiling, zero-conversion spend > $50/mo per query, disapproved ads. All flags are report-only. No write scope is requested anywhere in this step — assert it in code.
    Done when: a connected test account's numbers match the Ads UI and the write-scope assertion test passes.
  2. Shadow campaigns. later Complete campaign plans — structure, keywords, negatives, creative from the content layer, budget split — as reviewable artifacts. Nothing runs.a full plan can be applied manually by a human and later compared projected-vs-actual.
    build prompt
    Prereqs: read-only data (10.1), grounded content (08.1), GSC queries (12.1).
    Task: campaign planner: from GSC queries that convert organically + product pages, build {campaign structure, ad groups by intent cluster, keywords + match types, negative list (from GSC queries with impressions and zero relevance), RSA ad copy drawn from quality-gated content lines, landing URL per group, budget split within the intake ceiling, projected CPA range from the account's own history}. Render as a reviewable artifact (approvals queue) + exportable Google Ads Editor CSV. No API writes.
    Done when: a human applies one shadow plan manually and 30 days later the projected-vs-actual comparison renders from 10.1 data.
  3. Supervised spend. later Per-campaign approval; hard caps enforced twice; anomaly cutoffs pause first, ask later; daily spend report.a runaway-spend simulation triggers the automatic pause before the daily cap is exceeded.
    build prompt
    Prereqs: 10.1-10.2 proven; write scope granted per-workspace explicitly in approvals; kill switch (16.4) live.
    Task: execution: apply an approved shadow plan via the Ads API with platform daily budgets set AND an engine-side monitor (hourly pull; projected daily spend > cap × 0.9 → pause campaign via API, escalate). Every mutation logged with inverse. Anomaly rules: spend-rate 3× trailing average → pause+ask; CPA > 3× projection for 3 days → propose pause. Daily spend digest to the owner. Simulation harness: a dry-run mode that replays a synthetic runaway day against the monitor.
    Done when: the simulation pauses before cap breach, and one real supervised campaign runs a week with every action in the audit trail.
  4. Waste patrol at A4. later The first conditional class for paid: pausing proven waste — the lowest-risk action in ads.waste-pausing runs unattended with a monthly dollars-saved report, enabled by a one-line approval.
    build prompt
    Prereqs: 10.3 stable; class rules (16.2).
    Task: define the waste class narrowly: search terms with ≥$X spend and 0 conversions over 60 days → add as negative; exact-duplicate keywords across ad groups → pause the worse performer. Class rule "paid.waste" enables it per site. Monthly report: actions taken, spend redirected, and a no-touch list (brand terms always protected — from intake guardrails). Every action reversible and logged.
    Done when: one month unattended on a test account yields only defensible actions (human-reviewed after the fact, 100% agreement) and a dollars-saved figure.
  5. Creative library. later Every ad line, image, and its performance in one place, cross-linked to the content that spawned it.the planner reuses proven lines and retires fatigued ones on evidence.
    build prompt
    Prereq: 10.1 data flowing.
    Task: creative store: every RSA asset pulled with its per-asset performance rating; link each to its source (content piece or generated line). Fatigue detection: CTR decay >30% over 4 weeks on a previously-performing asset → rotation proposal citing the numbers. The shadow planner (10.2) draws from proven-asset lines first.
    Done when: one rotation proposal cites real per-asset data and the planner demonstrably reuses a proven line.

11 Conversion now: A1 target: A3

What exists today CRO exists as a role; conversion basics ride in the audit and setup checklist (CTA above the fold, form sanity). The network runs a production cookie-less, self-hosted beacon on a sister property — the exact pattern conversion tracking will reuse.
  1. Conversion-path audit. now A CRO profile in the crawl: CTA presence/visibility per template, form friction, trust markers, tap-target sanity.the audit names the weakest step in the site's primary conversion path, with evidence.
    build prompt
    Context: crawl.py classifies page types; checks live in score_site.
    Task: add a CRO extraction pass per page: primary CTA candidates (links/buttons with action verbs), their DOM position (before/after first heading block as an above-fold proxy), form field counts + required counts, tel:/mailto: presence, trust markers (reviews, guarantees, certifications — text patterns), and tap-target density on nav. Define the site's primary path from intake goal (lead → contact form; sale → product→cart) and walk it in crawl data; the weakest step = the page whose CRO extraction scores lowest. New checks: "Primary CTA", "Form friction", "Path integrity" (every path step reachable in ≤1 click from the previous).
    Done when: for two real sites the named weakest step matches a human's judgment, with the evidence displayed.
  2. First-party events. next A cookie-less beacon (view → engage → convert), modeled on the production pattern already running in the network.a conversion rate appears measured by the engine's own beacon, no third-party analytics.
    build prompt
    Context: the network's existing production beacon: nginx sub_filter injects a /px fetch; location = /px logs to a dedicated access log; bots excluded by JS execution; dashboard generated by cron. Privacy-first alternatives for reference: plausible/analytics (27.8k★), umami (37.7k★) — we stay self-hosted-minimal like them, not adopt them.
    Task: extend the pattern to events: /px?e=view|engage|convert&p=<path> (engage = 15s timer or 50% scroll; convert = thank-you/path match from intake goal). Same log-based pipeline, no cookies, no fingerprinting, IPs truncated at log time. Per-workspace funnel numbers into the history store; conversion checks in the audit consume real rates.
    Done when: one network site shows a view→convert funnel from beacon data that sanity-matches its AWStats traffic.
  3. Speed as CRO. next Tie vitals (06.3) to conversion: the slowest money page vs. its funnel drop-off, stated together.the CRO report can say “this page is your slowest and your leakiest” from joined data.
    build prompt
    Prereqs: vitals (06.3) + beacon funnels (11.2).
    Task: join per-page vitals with per-page funnel drop-off in the workspace; flag pages in the worst quartile of BOTH as priority CRO targets (the compounding losers). Render as a quadrant view (fast/slow × converting/leaking) using the network's dataviz conventions. Feeds S3 prioritization with a CRO weight.
    Done when: the quadrant renders for a beacon-connected site and its worst-quadrant page becomes a top-3 roadmap item automatically.
  4. Proposal-grade experiments. later Staged A/B changes with expected lift and required sample size, honestly evaluated — no peeking.an experiment completes its sample and its verdict updates the site's CRO playbook.
    build prompt
    Prereqs: beacon (11.2), publish adapters (08.5), traffic ≥ the power calc.
    Task: experiment framework: proposal = {page, change (one variable), metric, minimum detectable effect, required n (standard two-proportion power calc in stdlib), max duration}. Refuse experiments the site's traffic can't power within 60 days — say so. Serve variants via a deterministic hash on the beacon's session-less bucket (path+day-grain, documented honestly as cluster-level, not per-user). Evaluation only at n-reached: verdict + CI; log to ws["cro_playbook"]. No sequential peeking; early-stop only on guardrail harm (conversion < 50% of control at half-sample).
    Done when: one full experiment runs to verdict on a network site and an underpowered proposal is correctly refused with the math shown.

12 Analytics now: A1 target: A4

What exists today Connection slots per workspace; real monthly traffic from the network's own analytics pipeline (unmeasured = “unmeasured,” never fabricated); the audit consumes reach honestly. Missing: platform APIs and history.
  1. Search Console API. now The single highest-leverage unbuilt integration: queries, impressions, positions, clicks, index coverage per workspace.a workspace shows its real top-20 queries with positions, refreshed on schedule.
    build prompt
    Prereq: OAuth (02.4). Context: 206 network sites are already GSC-verified — wire ours first, clients after.
    Task: gsc.py: per connected workspace, weekly Search Analytics pulls — dimensions [query], [page], [query,page] over trailing 28 days — into the history SQLite with pulled_at. Serve /gsc/top?domain= for the UI (top queries table with position + trend arrow vs. prior period). Handle quota politely (batch, backoff); partial pulls stored partial and labeled. This feeds 07.1 prioritization, 08.4 decay, 09.E1 rank history, 12.4 attribution — build it once, well.
    Done when: three network workspaces show top-query tables matching the GSC UI ±5%, refreshed twice on schedule without intervention.
  2. Metric history. now Append-only per-site store of every metric pull and audit score — the memory that turns snapshots into trends.any metric on any workspace charts over time from stored data.
    build prompt
    Task: /opt/autoengine/history.py: one SQLite DB (history.db), tables: audits(domain, date, scores_json, check_states_json), metrics(domain, date, source, name, value_json). Writers: analyze_and_store (post-save hook), gsc.py, beacon rollups, ads pulls. Readers: /history?domain=&name= series endpoint + sparkline components in app.js. Migration: backfill from existing workspace analyzed_at snapshots where present. WAL mode; single-writer discipline via a lock like the pacer's.
    Done when: overall score, one GSC metric, and traffic all chart over ≥4 points for one site, and a workspace JSON stays under its current size (history lives in SQLite, not the JSON).
  3. GA4 integration. next Sessions, sources, conversions joined with GSC into one view.organic sessions and their conversion rate appear alongside query data, no manual export.
    build prompt
    Prereq: OAuth (02.4) with GA4 Data API read scope.
    Task: ga4.py: weekly pull per connected workspace — sessions by default channel group, engaged sessions, key events (conversions), landing-page dimension for organic — into history.db. Join view: per landing page, GSC (impressions, position) + GA4 (sessions, conv rate) + beacon funnel where present, one table, provenance labels per column (the three sources measure differently — never sum across them).
    Done when: the joined table renders for a dual-connected site and each column's provenance tooltip is correct.
  4. Before/after attribution. next Every executed change stamped with its target metric and window; verdicts improved / neutral / regressed; regressions trigger rollback review.the workspace lists every change shipped last quarter with its measured outcome.
    build prompt
    Prereqs: history.db (12.2), execution logging (16.3).
    Task: attribution.py: every executed change records {change_id, page, target_metric (e.g. gsc position for its primary query; clicks; conv rate), baseline (trailing 28d), window (metric-appropriate: GSC 28-56d, beacon 14d)}. At window close: verdict = improved/neutral/regressed vs. baseline with a simple threshold + the caveat field auto-noting confounds (other changes to the same page inside the window → verdict "confounded", grouped). Regressed → rollback-review item in approvals. Verdicts feed 09.E3 class graduation and 05.4 cadence.
    Done when: ten changes on our own sites carry closed verdicts, spot-checked sane, and one seeded regression produced a rollback review.
  5. Anomaly watch. later Continuous monitoring: traffic cliffs, deindexing, vitals regressions — alerted within a day.a simulated ranking collapse alerts with the affected queries before a human would notice.
    build prompt
    Prereqs: history.db populated (12.1-12.3).
    Task: anomaly.py nightly: per site, robust z-score on daily clicks/impressions (GSC) and beacon views vs. trailing 28d (min history 14d — else skip, honestly); index-coverage drops; vitals p75 crossing a threshold band. Alert format: what moved, by how much, top affected queries/pages, first-seen date. Route via the 05.3 digest (severity: own-line for cliffs). Suppression: known-change windows (a big rollout yesterday) annotate rather than alarm.
    Done when: replaying a historical real dip through the detector fires correctly, and two quiet weeks produce zero false alarms on our sites.

13 Market Research now: A1 target: A3

What exists today Niche detection with domain-aware overrides; research as a staffed role. The felt gap: when a client was asked “who are your competitors,” the honest answer was “not sure” — the engine should answer that with evidence.
  1. Competitor discovery. now From the site's own money queries and category terms: who actually ranks against this business.every workspace lists its top organic competitors with the overlapping queries as evidence.
    build prompt
    Prereq: GSC (12.1) for the site's real queries; without it, fall back to category nouns from the crawl (labeled lower-confidence).
    Task: competitors.py: for the top ~15 queries, collect the recurring domains that outrank or co-rank (source: paced checks from the Windows node; volume tiny, monthly). Score competitors by overlap count × query value; store ws["competitors"] = [{domain, overlap_queries[], first_seen}] with provenance. Exclude marketplaces/wikis (configurable list) as "SERP features, not competitors" — listed separately.
    Done when: two workspaces show competitor lists a human familiar with each business rates ≥80% sensible, each with its evidence queries.
  2. Competitor tracking. next Paced, respectful periodic audits of named competitors through the same engine; comparison view.the workspace shows “you vs. the field” on the same ten checks, refreshed monthly.
    build prompt
    Prereq: 13.1 lists.
    Task: monthly, run run_engine (read-only, paced, homepage + ≤3 sampled pages) on each confirmed competitor; store under a separate competitors/ workspace dir (never mixed with client workspaces). Comparison view: same check labels, client column vs. field median vs. best-in-field; deltas highlighted. Respect robots.txt disallows on their sites absolutely; frequency cap monthly; UA honest.
    Done when: one client workspace renders the comparison with real competitor data and the crawl log shows full robots compliance.
  3. Demand mapping. next The niche's question map — what buyers ask, clustered by intent, sized by opportunity — feeding the content calendar directly.every content-calendar slot traces to a mapped, sized demand cluster.
    build prompt
    Sources: the site's own GSC queries (real demand it already touches), competitor overlap queries (13.1), question-formed headings found on competitor pages (13.2 crawl data), and the crawl's product vocabulary. No paid keyword APIs (subscription-only economics).
    Task: demand.py: normalize + cluster questions/queries by shared object-noun (stdlib; stemming-lite), size each cluster by summed GSC impressions where known (else "unsized — engine's own data only", honest), tag intent (informational/comparative/transactional by leading word). Store ws["demand_map"]; content_calendar (08.1) must cite a cluster id per slot.
    Done when: a generated calendar's slots each link to a real cluster, and unsized clusters display their honest label.
  4. Review & voice-of-customer mining. later Public reviews of the business and competitors mined for language, objections, feature gaps.the content role can quote real customer phrasing, sourced, when drafting.
    build prompt
    Task: voc.py: ingest public review text the owner has rights to see (their own Google Business profile via API with their auth; public review pages fetched politely). Extract recurring phrases (n-gram frequency vs. base rate), objection patterns ("too expensive", "slow shipping"-class), and praised features; store with source URLs; NEVER store reviewer names. Output: a voice bank the content grounding pack (08.2) may quote with source, and an objections list feeding FAQ generation.
    Done when: one business's voice bank contains ≥10 sourced phrases and a generated FAQ answers its top real objection.
  5. Pricing & positioning scan. later Where the business sits on price and claims vs. the tracked field — refreshed, sourced, never guessed.the strategist role can state the client's price position with citations.
    build prompt
    Prereq: 13.2 competitor crawls.
    Task: from competitor product pages already crawled, extract visible prices for comparable items (schema Offer data first — it's structured; text patterns second, flagged lower-confidence) and their headline claims (H1/hero text). Position map: client vs. field on price band and claim themes. Anything unextractable = "unknown", listed. Refresh with 13.2's monthly cycle.
    Done when: a position summary for one client cites the exact source pages for every number on it.

14 Outbound now: A1 target: A3 (draft-only by design)

What exists today Outreach, email, and social exist as roles; the engine generates social posts from real site signals. Standing network rule: outbound messages are always drafts — a human sends, the engine never does. Outbound's ceiling is deliberately A3.
  1. Social pipeline. now Calendar-driven post drafts per channel derived from published content — repurposing, not new claims — queued with preview.each published article automatically yields its social derivatives in the approval queue.
    build prompt
    Context: social_posts(brand, niche, sig) exists in server.py; published content will carry editorial memory (08.6).
    Task: on publish (or manually for existing pieces), derive per-channel drafts from the piece's own text only: 1 LinkedIn post (the piece's answer block + one insight + link), 3 short posts (one fact each, no invented stats), 1 carousel outline where the piece has ≥4 sections. Voice from intake guardrails. Land in approvals under the social role with a rendered preview; never post anywhere — export as copy-ready text.
    Done when: publishing one article yields its derivative set in the queue, every line traceable to the article.
  2. Email sequences, drafted. next Newsletter and lifecycle drafts grounded in the month's real activity, always landing as drafts in the owner's own mail tool.a monthly newsletter draft exists without anyone writing it; nothing was ever auto-sent.
    build prompt
    Constraints: ALWAYS-DRAFT is absolute. When drafting into Gmail via API, every URL must be an explicit <a href> anchor in htmlBody and the plaintext body URL-free (Gmail's linkifier wraps bare URLs at save time — known footgun).
    Task: monthly job per workspace: compose a newsletter from real events (published pieces, score improvements from the diff, one useful tip from the demand map) — no invented news, institutional voice, guardrails applied. Deliver as: (a) a draft file in the workspace + (b) where the owner has connected Gmail, create_draft with anchor-formatted links. Log draft-created only; sending is human, always.
    Done when: two consecutive monthly drafts are grounded (each claim traceable) and the audit trail shows zero send events, ever.
  3. Earned-links outreach. later Prospect lists for legitimate mentions with personalized draft pitches — quality-gated, volume-capped; the directory layer seeded from the 6.8k★ community canon.every outreach draft names a specific, verifiable reason this recipient would care.
    build prompt
    Seed: github.com/mmccaff/PlacesToPostYourStartup (6.8k★) for launch/listing venues, filtered per niche; plus 13.2's field data (who links to competitors' assets).
    Task: outreach.py: prospect record = {venue/site, contact path, why-them evidence (the exact page of theirs that makes this relevant), which asset of ours fits}. Draft pitch per prospect: ≤120 words, references their specific page, no templated flattery; gate rejects any draft whose why-them evidence is generic. Volume cap: 10 drafts/workspace/month. All drafts, never sends.
    Done when: a batch of 5 drafts for one site each cite a real page of the recipient's, human-judged non-spammy 5/5.
  4. Directory & profile hygiene. next The business's presence on the directories that matter for its niche — found, checked, and correction drafts prepared.the workspace lists its directory presence with NAP-mismatch corrections ready to submit.
    build prompt
    Prereq: entity record (09.C4) as the canonical NAP.
    Task: per niche, maintain a modest directory list (seeded from the PlacesToPostYourStartup canon + niche-specific additions; stored as data). Check presence: fetch each directory's listing search for the brand (paced, from the Windows node); found → diff NAP vs. canonical; absent → listing-creation draft with the canonical data prefilled. All submissions human-performed from prepared drafts.
    Done when: one business's directory report shows found/absent/mismatched across its list with a prepared correction for each mismatch.
Review

15 Board of Advisors now: A1 target: A2 (permanently human-adjacent)

What exists today The board exists as a review role, and its real-world prototype has already run: this month's audit findings were verified by an independent adversarial pass — a second, separate analysis re-derived every claim from raw page data and tried to refute each; 8 of 9 survived, and the one overstatement was caught and corrected before reaching the client. That process is the board's job description.
  1. Productize the adversarial pass. now Before any major deliverable reaches a client, independent reviewer passes attempt to refute its claims; only survivors ship, each carrying verification status.every client-facing claim is marked verified/unverified, and a planted false finding is demonstrably caught.
    build prompt
    Context: the manual prototype: an independent agent re-derived 9 audit claims from raw HTML and refuted one. Deliverable types: audit reports, changesets, campaign plans, briefs.
    Task: board.py: given a deliverable, extract its checkable claims (numbers, counts, quotes, "page contains X" assertions); for each, an independent verification pass re-derives the value from the SOURCE data (stored crawl HTML, GSC rows, history.db) using different code paths than the generator; verdict per claim {verified, refuted, unverifiable}. Refuted → deliverable blocked, item returned to its role with the evidence. Ship a fixture test: a report with one planted wrong count must come back refuted.
    Done when: the fixture is caught, and one real audit report ships with per-claim verification marks.
  2. Perspective diversity. next Board seats review through distinct lenses — correctness, brand risk, claims/legal risk, customer empathy — because different failure modes need different reviewers.each major deliverable shows per-lens verdicts, not one rubber stamp.
    build prompt
    Prereq: 15.1 pipeline.
    Task: define four lens reviewers as data (lens name, what it hunts, veto power): correctness (facts vs. source — from 15.1), brand (guardrail adjective/never-say compliance, tone), claims-risk (superlatives, guarantees, comparative claims without substantiation, regulated-category phrases), empathy (does it answer the actual question a customer has; readability grade ≤ 9th). Run all four on gated deliverables; store {lens, verdict, notes}. Any veto blocks; empathy warns-only. UI: four small chips per deliverable.
    Done when: a deliverable seeded with an unsubstantiated "best in the world" claim is vetoed by claims-risk specifically, not generically.
  3. The dissent log. later Rejections and their reasons kept and fed back as training signal to the generating roles.a rejected-claim class measurably stops recurring.
    build prompt
    Task: persist every board rejection {deliverable type, role, lens, claim class, reason} in history.db. Monthly: aggregate by claim class; classes recurring ≥3× generate a rule proposal — a concrete addition to the generating role's constraints (e.g. "never emit uncited percentages in briefs") — landing in approvals. Accepted rules append to the role's generation constraints (roles.json) and are cited in future rejections.
    Done when: one recurring rejection class produces an accepted rule and its recurrence drops to zero over the following month.

16 Approvals & Setup now: A1 target: A4 (the dial itself)

What exists today Per-workspace approvals state, a persisted setup checklist, connection management, deliverable-level approve/act controls. This section governs autonomy, so it matures ahead of every acting section.
  1. One queue. now Every pending item from every role in one approvals inbox — what will change, why, blast radius, and the undo.a site owner clears a week's approvals in ten minutes without leaving the queue.
    build prompt
    Context: pendingCount/deliverableAction exist in app.js; items now carry roles (03.1).
    Task: unify all pending item types (deliverables, changeset items, escalations, proposals) behind one queue schema {id, role, kind, title, why (finding/metric citation), blast_radius (pages touched, public-facing?), undo (description or "n/a — draft only"), created}. One inbox view, filterable by role/kind, keyboard-friendly (j/k/a/r), batch actions per class. Server: GET /queue?domain=, POST /queue/act. Every act writes the audit trail (16.3).
    Done when: all pending item types for a busy workspace appear in the one queue and a timed run-through of 20 items takes <10 min.
  2. Class-level rules. next Approvals graduate from per-item to per-class — the mechanism by which A3 becomes A4, one class at a time, per site.class rules exist, are visibly listed, and revoke in one click.
    build prompt
    Task: ws["class_rules"] = [{class (e.g. "seo.alt_set"), granted_at, granted_by, scope (site), conditions (max items/day, verification required), revoked_at?}]. Grant only from the approvals UI with a confirmation naming exactly what will start happening. The execution layer consults rules before acting; rule-covered actions still log individually and still verify. A "rules" panel lists active rules with one-click revoke; revocation takes effect before the next action (checked per-action, not per-batch).
    Done when: granting "seo.alt_set" automates exactly alt items, the panel shows it, and revoking mid-batch stops the batch.
  3. The full audit trail. next Every engine action — proposed, approved, executed, rolled back — append-only with actor, timestamp, artifact, outcome.any change on any site is traceable end-to-end months later.
    build prompt
    Task: trail.py: append-only table in history.db: actions(id, domain, ts, actor (human id | engine role), phase (proposed|approved|rejected|executed|verified|rolled_back), kind, artifact_ref, outcome, inverse_ref). Write hooks in the queue, executors, and rollback paths. Read API /trail?domain=&page= with filters. Integrity: no UPDATE/DELETE statements exist in trail.py — corrections are new rows referencing the corrected id.
    Done when: one executed change shows its complete lifecycle in order, and grepping trail.py for UPDATE|DELETE returns nothing.
  4. Kill switch & rollback registry. next One control halts all engine activity per site or globally; every executed change registers its inverse.the kill switch is tested live and stops everything within one cycle.
    build prompt
    Task: (1) Kill switch: a flag file per site (/opt/autoengine/halt/<domain>) + global (/opt/autoengine/halt/ALL); EVERY acting code path (executors, publishers, rollouts, paid mutations) checks it immediately before acting — not at batch start. UI: a visible switch on the workspace + network views. (2) Rollback registry: every executed change stores an executable inverse (file restore path, API inverse call, negative-keyword removal); /rollback/<action_id> runs it, verifies, and logs phase:rolled_back.
    Done when: flipping the site switch mid-batch stops the next action within one iteration (tested), and a rollback of a real published change restores the prior state byte-identically where applicable.
Library

17 Marketing Skills now: A1 target: A3

What exists today A curated library of 47 professional marketing skills across seven groups — Strategy & Planning, Research & Positioning, SEO & Site, Content & Creative, Ads & Outbound, Conversion & Lifecycle, and Growth/PR/Ops — searchable in the product, refreshed weekly, MIT-licensed from the 40.6k★ marketingskills repo (the most-starred marketing framework on GitHub, built specifically for AI agents)10. The full worksheet — all 47, sortable, with role and engine-hook mappings — is at /roadmap/skills/. Today the library teaches; the roadmap makes it operate.
  1. Skill→task binding. now Every deliverable names the skill(s) it applied — the library becomes the department's method manual, auditable against stated method.any deliverable answers “which skill produced this, applied how?”
    build prompt
    Context: skills-data.js carries 47 skills with slugs, categories, and per-skill guidance; deliverables are generated in server.py.
    Task: add a skill map (data): deliverable kind → skill slug(s) (calendar→content-strategy; drafts→copywriting+the format skill; changeset titles/meta→seo-audit+copy-editing; schema artifacts→schema; competitor views→competitor-profiling; briefs→marketing-plan; etc. — full mapping table in /roadmap/skills/). Stamp skills:[] on every generated deliverable; UI chip links to the skill's entry. Generators actually load the named skill's guidance into their constraints at generation time — binding is real, not a label.
    Done when: every deliverable type carries accurate skill chips and one generator demonstrably changes output when its skill's guidance changes.
  2. Gap analysis against the open canon. next Recurring judgment the library doesn't cover gets logged; the library grows deliberately, benchmarked against the canon — the 40.6k★ marketingskills set itself, the 13.2k★ Marketing-for-Engineers collection, and the maintained awesome-marketing lists.library additions trace to logged gaps or a named canon item it lacked.
    build prompt
    Task: gaps.py: (1) log skill-misses — generator moments where no mapped skill fit (from 17.1's map having no entry) and board rejections citing missing method (15.3); (2) quarterly, diff our 47 slugs against the upstream repo's current skill list (it evolves — fetch its index) and against topic headings in goabstract/Marketing-for-Engineers; (3) emit a gap report: {operational gaps, upstream additions we lack, canon topics uncovered} as proposals. Weekly regen (gen-ame-skills.py) already handles pulling approved additions.
    Done when: one quarterly report exists and at least one addition traces to it.
  3. Skill scoring by attribution. later Attribution data scores skills — which methods measurably work — and revises the library on evidence.at least one skill is revised because measurement showed its guidance underperforming.
    build prompt
    Prereqs: skill stamps (17.1) + attribution verdicts (12.4).
    Task: join: per skill slug, the verdict distribution of deliverables it produced (improved/neutral/regressed/confounded), minimum n=10 before display (below: "insufficient data", honest). Quarterly skill report ranks skills by verified lift; underperformers get a review proposal (revise our application of it, or flag upstream). Store per-skill verdicts in history.db.
    Done when: the report renders with honest n-gating and one revision proposal cites its verdict data.

18 Prompts now: A1 target: A2

What exists today A searchable library of 427 ready-to-paste marketing prompts — the manual-mode companion to the skills library.
  1. Workspace-aware prompts. now Prompts become templates pre-filled with the workspace's real data — copy out a prompt already grounded in your business.a copied prompt contains real workspace facts, not placeholders.
    build prompt
    Context: the prompts page + copyText() exist; workspaces carry brand, niche, checks, top queries (once 12.1 lands).
    Task: template variables in prompt bodies ({{brand}}, {{niche}}, {{top_query}}, {{weakest_check}}, {{primary_url}}); when a workspace is active, render prompts with real values (fallback: highlight unfilled vars + "connect X to fill"). Server endpoint returns the fill map; substitution client-side. Copy copies the filled version.
    Done when: with a workspace selected, ten spot-checked prompts copy out with correct real values.
  2. Usage-informed curation. next Track copies; prune and improve; dedupe against skills.ordering reflects measured usefulness, not alphabetical accident.
    build prompt
    Task: count copy events per prompt via the site's existing beacon pattern (no cookies; a /px?e=copy&id= ping). Monthly: rank by copies, flag never-copied prompts for review, and diff prompt topics against skill coverage — a prompt whose job a skill now does better links to the skill instead of duplicating it. Reorder default listing by usage within category.
    Done when: a month of data reorders the library and ≥10 duplicative prompts link to their skill.
  3. Prompt→department bridge. next Every prompt offers “let the department do this” — the manual tool becomes the on-ramp to the engine.a prompt's task can be handed to the matching role in one click.
    build prompt
    Prereq: role routing (03.1) + skill map (17.1).
    Task: map each prompt to its nearest deliverable kind + role; render a second button beside copy: "have the department do it" → creates a proposal in the approvals queue for that role, grounded in the active workspace, citing the prompt as origin. No workspace active → the button explains onboarding.
    Done when: clicking it on a content prompt lands a correct content-role proposal in the queue.
Foundation

19 “Engine online” — status & truth now: A1 target: A2

What exists today An engine-online indicator; the backend runs as a supervised service with health checks, and a one-shot verification pattern (scheduled probe → log → push notification) is proven in production.
  1. Component-level status. now “Online” decomposes into per-subsystem truth with last-verified timestamps.the status view answers “why did last night's run skip site X” without reading logs.
    build prompt
    Task: /status endpoint + page: per subsystem {fetcher (last successful external fetch), crawler, scheduler (last reaudit run), GSC/GA4/Ads connections per workspace (token age, last pull), publish adapters, alert channel (last ntfy success), history.db (size, last write)} each with {state: ok|degraded|down, last_verified, detail}. The nightly run writes per-site skip reasons here. Status truthfulness rule: a subsystem with no recent verification shows "unverified since …", never "ok".
    Done when: pulling a network cable class of failure (revoke a token) shows degraded within one self-check cycle, and skip reasons render per site.
  2. Self-verification. next The engine probes its own capabilities on schedule and degrades honestly.revoking a test credential produces a visible status change and an alert, with no fabricated zeros downstream.
    build prompt
    Context: the pm-verify pattern (one-shot probe → log → ntfy) generalizes.
    Task: selfcheck.py hourly: fetch a known-good external page (rotating through 3), one GSC test call per connected workspace daily, adapter dry-runs weekly, disk/DB writability. Failures set component status + one deduplicated ntfy alert (not hourly repeats — alert on state CHANGE only). Downstream consumers (overview, reports) read component status and render "data unavailable — connection down since X" instead of zeros; add a lint test grepping report templates for any path that could render a bare 0 from a down component.
    Done when: a revoked token alerts once, the workspace shows the honest unavailable state, and restoring it clears status on the next cycle.

20 Governance & safety rails standing non-negotiable at every level

The invariants — several already proven in production Never fabricate (unreadable pages refuse to score — shipped and regression-tested); quality gate before humans; staging + rollback for every executed change; hard spend caps enforced twice; outbound is draft-only, forever; disengagement over bravado; append-only audit trail; kill switch; flat-cost architecture (subscription economics, no per-action metering — what makes always-on viable for small businesses and removes the incentive to under-serve).
  1. The fabrication linter. now A test-time guard that hunts the entire codebase's output paths for places a missing measurement could render as a number.the linter runs in CI-fashion pre-restart and the codebase passes clean.
    build prompt
    Context: the worst historical bug: an empty 429 body scored 23/100 live in a demo. The class: absent data rendered as a plausible number.
    Task: lint_fabrication.py: (1) static pass — flag report/UI code paths that format a metric without a provenance envelope (04.1) or None-guard; (2) dynamic pass — run report generation against a synthetic workspace with EVERY optional field absent; assert output contains no bare numerals for absent fields (must be "unmeasured"/"unavailable"). Wire into the pre-restart hook beside the regression suite (09.E4).
    Done when: the synthetic-empty workspace renders with zero fabricated numbers and the hook blocks a seeded violation.
  2. Guardrail test harness. next The safety systems get the SpaceX treatment: deliberately test each to failure, on schedule.kill switch, spend caps, draft-only, and rollback each have a scheduled adversarial test with a logged pass.
    build prompt
    Task: harness.py, monthly, against a dedicated test workspace on our own network: (1) flip kill switch mid-batch — assert halt ≤1 action; (2) simulate spend-rate runaway (10.3 dry-run) — assert pause before cap; (3) attempt a programmatic send path for outbound — assert none exists (grep + runtime: no send-capable scopes/credentials in the outbound code paths); (4) publish + rollback a canary page — assert byte-level restoration; (5) submit a fabrication-linter violation — assert blocked. Log all to the trail; ntfy the monthly scorecard.
    Done when: the first monthly scorecard shows 5/5 with evidence links.
The order of operations is the strategy: perceive → measure → prioritize → generate → shadow → supervise → attribute → automate. Every section above is that same ladder, applied to its own domain.
The 90-day slice (all buildable now, none touches a live site unattended): crawl into the product (06.1) · Search Console + metric history (12.1–12.2) · fix artifacts + schema factory + pixel-width titles (09.A1–A3) · llms.txt + answer blocks (09.C1–C2) · the SEO regression suite (09.E4) · opportunity-weighted roadmap (07.1) · one approvals queue (16.1) · grounded content calendars (08.1) · competitor discovery (13.1) · component status (19.1) · the fabrication linter (20.1). Together: Stages S1–S4 complete — the entire perception-to-generation half of the machine.
Honesty ledger: Paid media has no platform integration yet. The content generator is template-grade until 08.1 ships. Heavily-defended sites can still slow live reads (adaptive pacing mitigates; scheduled probes cover demo-critical sites). AI-citation tracking is young everywhere — ours records evidence or says “unknown,” and the tooling is deliberately swappable. Attribution (S7) is the difference between “we did things” and “we caused things” — it is gated behind measurement rather than faked with take-credit-for-everything reporting, which is exactly why it comes after GSC wiring in the build order.

Sources & verified repositories

  1. Gartner press release, May 2026 — AI-driven automation of marketing work expected to grow from 16% to 36% by 2028. gartner.com
  2. Gartner marketing-AI maturity framing (Curious → Competent → Confident); Maturity Model for Marketing Analytics.
  3. Maturity models ending at “autonomous”: MarTech; Digital Applied 2026.
  4. Forrester-line projection: fewer than 15% of firms enabling genuinely agentic automation near-term — AMO analysis.
  5. Engineering method (Musk's algorithm, Tesla shadow mode, the data engine, test-to-failure) — fully sourced on Self-Driving SEO.
  6. Structured data: schema.org (vocabulary repo: schemaorg/schemaorg, 6.2k★).
  7. Google Search Central — Search Essentials & people-first content: developers.google.com/search.
  8. Core Web Vitals: web.dev/vitals. Lab tooling: GoogleChrome/lighthouse (30.5k★); site-wide: harlan-zw/unlighthouse (4.7k★); continuous: sitespeedio/sitespeed.io (5.0k★).
  9. Google Search Console: search.google.com/search-console.
  10. The GitHub marketing & SEO canon (all star counts verified live via the GitHub API, July 2026): coreyhaines31/marketingskills (40.6k★ — marketing skills for Claude Code and AI agents; the source of the engine's 47-skill library, MIT); goabstract/Marketing-for-Engineers (13.2k★); mmccaff/PlacesToPostYourStartup (6.8k★); marcobiedermann/search-engine-optimization (2.7k★); AnswerDotAI/llms-txt (2.5k★, the llms.txt spec); awesome-marketing lists (~400★).
  11. Self-hosted measurement & automation references (privacy-first precedents the engine's beacon and pipelines align with): umami (37.7k★), plausible/analytics (27.8k★), matomo (21.7k★), posthog (36.6k★); workflow orchestration: n8n (197k★); marketing automation reference architecture: mautic (10.2k★); crawling at scale: scrapy (63.2k★), crawlee (24.8k★); rank tracking: serpbear (2.0k★); SEO analysis: python-seo-analyzer (1.5k★), advertools (1.4k★).