Affinity Design
Agency Guide

SEO Agent

Always-on SEO optimization worker with autonomous sub-agents, auto-blogger, and campaign lifecycle

Overview

The SEO Agent is an always-on optimization worker for a connected website. It reads SEO reporting data, site content, keyword opportunities, prior action history, and operator-submitted ideas — then chooses the next executable SEO action within budget and safety limits.

The agent can:

  • Audit technical and content health
  • Research keywords, SERPs, and competitors
  • Build topical maps and content clusters
  • Draft SEO blog posts through the auto-blogger
  • Optimize money pages
  • Apply or recommend technical SEO fixes
  • Monitor reports for ranking, traffic, and striking-distance changes
  • Record every action into campaign and action history for review

Think of the SEO agent as a loop, not a one-shot generator. It continuously watches reporting data, queues campaigns, analyzes situations, runs actions, and measures outcomes.

A Campaign Is The Execution Of A Plan

Older campaigns picked one next action at a time with no bigger picture — analyze, do one thing, repeat. A campaign now builds a short ordered plan first (for example: refresh the audit, then run research, then write two blog posts) and works through it item by item, instead of deciding fresh after every single action. When the plan runs out, the agent goes back to research and builds a new one.

This makes campaigns more predictable to read in the campaign history — you can see the intended sequence, not just a string of individual decisions — and it avoids the old failure mode where an agent would commit to writing a post, discover mid-run that it had nothing good to write about, and burn most of the campaign recovering from that.

Runtime Loop

The runtime processes work asynchronously through a worker heartbeat:

  1. Queue — A campaign is queued by an admin trigger, manual request, or SEO reporting alert
  2. Lease & start — The worker heartbeat claims the campaign and sets runtime to running
  3. Situation analysis — Loads current domain state, reporting trends, previous actions, site context, and memory
  4. Action context — Builds the executable action catalog, verifying each action has real inputs (due keyword, target URL, audit findings)
  5. Build the plan — On the first pass, lays out the ordered list of work items for this campaign
  6. Strategy — Walks the plan one item at a time, or returns none when no safe action is ready
  7. Sub-agent dispatch — The selected action runs with resolved inputs
  8. Website handoff — If the action changes site content, it hands off to website execution: a WordPress draft job, a GitHub pull request, or — for GHL-hosted sites — a native GHL blog post
  9. Evaluation — Records results, updates keyword timestamps, appends action history, checks budget/duration/cancellation
  10. Loop or complete — The graph moves to the next plan item, builds a fresh plan once the current one is exhausted, or completes the campaign with a report
  11. Outcome measurement — Later reporting data is compared against baseline so the agent learns
  12. Alert scan — Continues watching for position drops, traffic cliffs, and striking-distance surges

Runtime States

StateMeaning
idleNo active campaign
queuedCampaign accepted, waiting for worker
runningWorker owns the lease, actively executing
cancellingCancellation requested, stops at next safe boundary

Only one active campaign per SEO agent at a time.

New-Agent Setup Now Actually Runs On Its Own

When a new SEO agent is created, it used to only run the initial audit automatically — research and the topical map needed a manual trigger, even though the setup screen told operators those steps would "start automatically after activation." That was wrong, and it's fixed: activating a new agent now runs audit, research, and topical map as one setup campaign, in that order, without anyone having to trigger the later steps by hand. Progress shows on the same setup screen as before. If a client asks why their new agent is "still setting up," the setup screen's progress line is the accurate answer now — check it before assuming something is stuck.

Sub-Agents

ActionWorkerPurpose
auditAuditorRefresh technical/content health and identify findings
researchResearcherGather keyword, SERP, and competitor context
topographyTopographyBuild or refine topical clusters and site structure
blog-writeBlog WriterProduce or update informational content tied to executable keyword context
money-pageMoney PageImprove high-intent pages tied to executable URL context
technical-seoTechnical SEOApply or recommend technical fixes (requires completed audit with actionable findings)
noneStrategy onlyExplicitly take no action when no safe high-value action is executable

Aliases like auto-blogger, blogger, auditor, and topographer normalize to the supported runtime actions.

Strategy never chooses blog-write or money-page without executable inputs. If inputs are missing, the runtime returns none instead of attempting a partial action.

Action Outputs

ActionIntended Output
auditAudit record with score, findings, and summary of site health
researchKeyword, SERP, and competitor research payload used for planning
topographyUpdated topical map or cluster structure
blog-writeContent generation or update result tied to a specific keyword opportunity
money-pagePage optimization result tied to a specific URL and commercial intent
technical-seoTechnical remediation result or structured set of fixes

Auto-Blogger

The auto-blogger is the blog-write sub-agent. It does not blindly invent posts — it uses executable keyword context from DataForSEO, Google Search Console, tracked keywords, and operator-submitted ideas.

Auto-Blogger Loop

  1. Select a due keyword candidate (weighted by priority, ranking opportunity, intent, freshness)
  2. Skip keywords recently drafted to avoid repeated posts
  3. Build a content brief from cluster context, internal link opportunities, and SERP/research data
  4. Generate the blog draft, title, slug, meta title, meta description, FAQ/article schema, and review notes
  5. Publish through the site's connected platform — a WordPress WebsiteExecutionJob (operation: "write_entity", postType: "post"), a GitHub pull request, or a direct GHL blog post
  6. Attach a client asset as the featured image when featuredAssetRef or assetRefs are provided
  7. Save keyword timestamps (lastSelectedAt, lastDraftedAt)
  8. The reporting loop measures rankings and traffic later before strategy repeats or changes direction

The SEO agent's own Publish Mode setting (draft or publish) decides whether a finished post goes live right away or waits for operator review — draft is the default for both WordPress and GHL sites. GitHub-backed sites always land as a pull request, never an auto-merge.

GHL blog posts need a little setup the first time: a GHL blog site, a blog author, and a featured image are all required. If more than one GHL blog is connected, set website.config.ghlBlogId so the agent doesn't guess which one to post to. If any of these are missing, the agent skips the post and records why instead of failing silently.

Content Plan (Proactive Posting Quota)

Each SEO agent has a Content Plan on its Config tab. Turning it on lets the agent write new blog posts on its own schedule, instead of waiting for an operator to ask for each one. The Content Plan sets how many posts to write per month and how many can go out in a single run.

Turning the per-agent switch on is not enough by itself. There is also one global switch that covers the whole deployment: the SEO_CONTENT_ENGINE environment variable. It must be set to on, or the content engine will not draft any posts — even if every agent's Content Plan says "enabled."

If an agent's Content Plan is on but no posts are showing up, check whether SEO_CONTENT_ENGINE=on is set for the deployment. This is a server-wide setting, not a per-agent one — no individual agent can turn it on by itself. Maintenance work and manually triggered campaigns are not affected by this switch; it only blocks the automatic, on-schedule blog quota.

If the per-agent switch is on but the deployment switch is off, the Config tab now shows an amber warning naming the exact environment variable to check, so this mismatch is visible without digging through logs. The Config tab also shows a plain-language cadence line, like "about one post every 7 days," so operators can check the numbers match the posting schedule they had in mind.

Keyword Ideas & Scheduling

Operators can influence the agent without editing database records directly.

Submit Keyword Ideas

POST /admin/seo/agents/:id/keyword-ideas

Fields: keyword, proposedAction, intent, priority, scheduledFor, url, clusterSlug, and notes.

  • Future-dated ideas are held until due
  • Higher priority due ideas receive a stronger score in action selection
  • Submitted ideas are stored as manual SeoKeyword records with operator tags and scheduling metadata

Inspect Action Context

GET /admin/seo/agents/:id/action-context

See what the runtime currently considers executable or blocked, including candidates for blog writing, money-page work, and technical SEO.

The admin SEO detail page exposes this in the Keywords tab.

Keyword Hit List & Maintenance

The Hit List (SEO ▸ Keywords ▸ Hit List) is the single committed master list for a client's keyword work — every keyword the agent or an operator has research has been through the same pipeline lands here, alongside the Saved Keywords research library it's promoted from.

Before a keyword can claim a hit-list candidate slot, an LLM relevance check screens out GSC queries that share a word with the site but have no commercial tie to the business (e.g. a school or an unrelated app name that happens to match a query). Screened-out keywords stay in the agent's working set — they just don't clutter the curated hit list.

  • Stages: candidate → proposed → approved → scheduled → drafting → published → live, with content type (blog post / service page / local page), schedule, published URL, and per-channel distribution chips.
  • One master list. Agent research, operator-submitted keyword ideas, and the Hit List page all write into the same pipeline — a submitted idea shows up on the Hit List immediately as a candidate, not just once the agent acts on it.
  • Promote to hit list — a row action on the Saved Keywords tab (or from the Hit List tab directly) that moves a keyword into the build pipeline and queues it into the agent's working set.
  • Remove from hit list — a row action on the Hit List tab that resets a keyword's stage back to none and pulls it out of the agent's working set, without deleting the underlying research row. Use the Saved Keywords delete action instead to archive a keyword's research history entirely.
  • Manual pipeline correction — if a build genuinely published but the Hit List never picked it up, open the keyword in the Saved Keywords editor and use the Stage select and Published URL field to correct it by hand instead of waiting on support. Marking a keyword newly published/live this way stamps its publish date automatically, and the fix shows up on both the Saved Keywords and Hit List tabs right away.
  • Distribute fans a published keyword across channels (blog, GMB, Facebook/Instagram/LinkedIn/Pinterest, newsletter) using the client's connected accounts; unconnected channels show as skipped, not failed.
  • Autopilot (per agent, off by default) auto-promotes the strongest fresh candidates into the build pipeline within configured volume/difficulty/pace thresholds, instead of waiting for operator review.

Publishing or promoting a keyword to published/live auto-enrolls it in rank tracking by default. An hourly scheduled check keeps position history up to date automatically after that, so the maintenance watchers below always have fresh data to work from.

Maintenance Watchers

Once a keyword is published/live, the agent keeps watching it instead of moving on:

WatcherWhat It Catches
Hit-list decayPublished keywords whose ranking position dropped week-over-week, or that have sat 30+ days with no clicks and no progress
Index coveragePublished pages Google can't or won't index — Soft 404s, robots.txt blocks, crawled-but-not-indexed

Both watchers feed the same alert scan that already watches for position drops and traffic cliffs, so a maintenance campaign can be queued automatically when a published page needs attention — and that campaign is built to fix or refresh the specific flagged page rather than propose generic new content. Once a decay or index-coverage alert queues a campaign, it won't queue another one for the same condition for 7 days, since these are slow-moving issues that don't need a fresh campaign every scan cycle.

The index-coverage watcher's result also shows up directly on the Hit List row as a badge next to the published link (Indexed / Not indexed yet / the specific issue, e.g. Soft 404), so you don't have to wait for an alert to see it.

Triggering a rank check manually on a config tracking more than 15 keywords now starts it in the background instead of blocking the request — the page shows it as started and keeps polling until results are in, rather than risking a timeout on a long serial SERP run.

The Delete domain action on a tracked domain's rank-tracking page now actually removes it, after a confirm prompt. It frees that client/domain/location/language/device slot for re-adding, and keeps the keyword and ranking history that was already collected.

Budget & Safety

Launch safety boundaries protect against unbounded execution:

  • API responses redact WordPress, Rank Math, and DataForSEO secrets
  • Generic agent updates cannot overwrite runtime state, campaign history, budget config, alert config, or initial audit state
  • Budget and alert values are validated and bounded before they can affect a campaign
  • Unknown sub-agent names fail clearly instead of pretending an action ran
  • Full campaigns stop at cancellation-safe boundaries
  • Blog posts default to draft status on both WordPress and GHL sites unless the agent's Publish Mode is set to publish
  • WordPress edits go through bounded website execution jobs with target constraints and preview metadata
  • Elementor smart edits remain widget-scoped and backup-aware

The SEO agent's detail header shows a running-cost stat (spend against budget) alongside the same in-place rename affordance available on other agents — see Creating & Configuring Agents.

Budget Controls

PUT /admin/seo/agents/:id/budget

Change action, token, cost, duration, and cooldown caps.

Alert Controls

PUT /admin/seo/agents/:id/alerts

Change alert thresholds only. This does not function as a scheduling endpoint.

Campaign Lifecycle

StepDescription
QueueAPI accepts the request, stores runtime state as queued
Lease & startWorker claims ownership, sets running, records heartbeat data
DecideSituation and strategy nodes choose the highest-value executable action
DispatchRuntime invokes the matching worker with resolved inputs
EvaluateResults recorded in action history and used for future strategy context
Continue or stopWorker loops for another action or completes the campaign
CancelQueued campaigns cancel immediately; running campaigns transition to cancelling and stop at next safe boundary

Operator API Reference

EndpointPurpose
POST /admin/seo/agents/:id/triggerRun a specific sub-agent or queue a full campaign
POST /admin/seo/agents/:id/campaignQueue a manual full campaign
POST /admin/seo/agents/:id/campaign/cancelRequest cancellation
GET /admin/seo/agents/:id/runtimeInspect current runtime state, current work, recent actions, audit trail
GET /admin/seo/agents/:id/campaignsInspect campaign history
GET /admin/seo/agents/:id/actionsInspect action history with pagination

WordPress & MCP Integration

  • WordPress REST writes and reads through the SEO WordPress connector
  • Rank Math and Yoast-compatible meta fields are sent with blog draft jobs
  • Rank Math redirects are supported when the site exposes the namespace
  • Elementor smart edits available through the WordPress connector with constrained edit behavior
  • Website execution previews and runs SEO-originated WordPress draft jobs
  • Client asset library references resolve through website asset consumers and upload into WordPress media
  • MCP WordPress and image-generation features can create or reference assets the SEO flow later uses for posts

Agent Model Provider

The SEO agent defaults to Gemini but can be switched per agency to OpenAI or Claude (Anthropic) at Admin → Config → LLMS. GLM (z.ai) is not offered for this role — a full campaign fires many calls in a time-boxed run, and GLM's reasoning latency risks a timeout. Grounded research calls (Google Search retrieval) stay pinned to Gemini regardless of the selected provider, since grounding is Gemini-native. Leave it on the default unless you have a specific reason to change it.

On this page