Blog Local Pro Solutions
May 2026 ~22 min read
A building-in-public playbook

The autonomous SEO loop.

A complete, copy-able playbook for building a self-learning SEO system on a local service business website — from data layer to skill files to safety gates. Written so anyone with the prerequisite accounts can execute it end to end.

Author Joey Farbstein
Published May 2026
Reading time ~22 minutes
Reference site localprosolutions.com

There is a category of business that is enormously valuable, deeply local, and almost entirely invisible to the current wave of AI-native company-building: the local service business. Tree services, landscapers, HVAC contractors, plumbers, roofers, dog daycares, trailer dealers. They are the businesses that keep neighborhoods functioning, and they are sitting on more first-party customer signal per dollar of revenue than any tech company on earth.

This post is the second in a series about building self-learning operating models for those businesses. The first introduced the customer intelligence loop — the foundational layer of a self-learning local business. This one is more practical: I'm going to give you the complete playbook for building the first loop I'm running on my own site, localprosolutions.com, end to end.

This is a fully autonomous SEO system. It does its own keyword research, builds new pages, tracks rankings, manages the Google Business Profile, monitors competitors, and decides what to do next — without anyone prompting it. It is built once and runs continuously. The architecture is the same one I'll be cloning to client sites once the lighthouse is stable.

If you are a developer, an agency owner, or a local business operator with technical depth: you can build this. The code isn't the hard part. The hard part is the discipline of letting the system run, reading its action log every week, and tuning its planner prompts based on real outcomes.

What follows is the complete specification. Copy it, adapt it, ship it.

§ 01What this is

A fully autonomous SEO system that runs continuously on a single local service business website. It does its own keyword research, builds new pages, tracks rankings, manages the Google Business Profile, monitors competitors, and decides what to do next — without anyone prompting it.

It is built once for localprosolutions.com as the lighthouse implementation. Once it works there, the same architecture clones to client sites.

What "fully autonomous" means here

What it is not

§ 02The core mental model

The system is one agent loop that runs forever, with four phases:

Perceive
Decide
Act
Learn

Everything else in this document is plumbing around that loop.

§ 03Prerequisites & accounts

Before any code is written, these accounts must exist. None of them are optional.

Service Purpose Why it's required
GitHubCode & content versioningEvery change is committed; full audit trail
VercelDeploymentAuto-deploys on every push; runs scheduled cron jobs
SupabaseDatabaseSource of truth — keywords, rankings, pages, actions
Claude APIAgent brainPlanning + execution calls
DataForSEOKeyword + SERP dataCheap, predictable, MCP available
Local FalconLocal rank trackingGeo-gridded local rankings — critical
Search ConsoleQuery dataThe source of truth for what's actually working
GBP APIProfile managementPosts, Q&A, photos, services
GA4Traffic dataOutcome measurement
CallRailCall trackingOutcome measurement — calls > rankings

§ 04The tool stack

The principle: the AI is only as useful as the tools it can reach. Every tool here either has a native MCP server or a documented API that gets wrapped in one. The tools change. The protocol doesn't.

Build & operate environment

Data & state

Reach surfaces (via MCP)

The framing that matters

Wrong question: "Which tool should I use?"

Right question: "Can the AI reach it?"

Tools change quarterly. Anchoring the architecture on a specific vendor locks it to that vendor's lifespan. Anchoring on the protocol future-proofs it.

Phase 00 · ~Week 1
Foundation setup

Get everything connected.

Every account is reachable. Every MCP is wired into Claude Code. Every credential lives in one secure place.

0.1 — Initialize the project repo

mkdir lps-seo-agent
cd lps-seo-agent
git init
gh repo create lps-seo-agent --private --source=. --remote=origin

0.2 — Folder structure

lps-seo-agent/
├── site/                    # The Astro site
│   ├── src/
│   │   ├── content/         # Markdown — pages, blog, cities
│   │   ├── layouts/         # Astro layouts (schema baked in)
│   │   ├── components/
│   │   └── pages/
│   ├── astro.config.mjs
│   └── package.json
├── agent/                   # The agent
│   ├── skills/              # Individual skill files
│   ├── lib/                 # Shared utilities, MCP clients
│   ├── prompts/             # System prompts
│   └── CLAUDE.md            # Master instruction file
├── infra/
│   ├── supabase/            # Schema migrations
│   └── vercel/              # Cron config
├── .env.example
└── README.md

0.3 — Connect MCPs to Claude Code

Create .claude/mcp.json with entries for every MCP server you need. Pattern looks like:

{
  "mcpServers": {
    "dataforseo": {
      "command": "npx",
      "args": ["-y", "dataforseo-mcp-server"],
      "env": {
        "DATAFORSEO_USERNAME": "${DATAFORSEO_USERNAME}",
        "DATAFORSEO_PASSWORD": "${DATAFORSEO_PASSWORD}"
      }
    },
    "supabase": {
      "command": "npx",
      "args": ["-y", "@supabase/mcp-server-supabase"],
      "env": { /* ... */ }
    },
    // ... github, vercel, localfalcon, gsc, gbp, etc.
  }
}

Verify each MCP connects by asking Claude Code to make a trivial call to each one before moving on.

Phase 01 · ~Week 1
The data layer

Build the Supabase schema.

Every piece of state the agent needs lives in Supabase. Multi-tenant by client_id from day one, even for a single site — so the same schema works when you expand to clients.

The core tables

TablePurpose
clientsEvery site the agent operates on. Business info, service area, GBP ID, repo URL.
client_factsVerifiable signals — pricing, team, photos, real customer questions. Injected into every published page.
keywordsEvery term tracked. Volume, difficulty, intent, business fit score, priority score.
rankingsDaily rank snapshots. Position, URL, SERP features, device, geo.
pagesEvery URL published. Target keyword, status, word count, performance.
actionsAppend-only log of every decision. Reasoning, inputs, outputs, gate results, outcome.
experimentsA/B tests the agent is running. Hypothesis, variants, winner.
competitorsDomains the agent watches.
backlinksLink profile + outreach pipeline.

The actions table is the most important one. It's append-only by design. Every action has reasoning attached. That's how you debug, how you learn, and how you defend the work to anyone who asks "why did you publish this?"

create table actions (
  id uuid primary key default uuid_generate_v4(),
  client_id uuid references clients(id) on delete cascade,
  action_type text not null,           -- 'publish_page'|'gbp_post'|'refresh'|...
  target_table text,
  target_id uuid,
  reasoning text not null,             -- WHY the agent did this
  inputs jsonb,                        -- what state it saw
  outputs jsonb,                       -- what it produced
  gate_results jsonb,                  -- which safety gates fired
  outcome jsonb,                       -- measured after the fact
  status text default 'completed',
  created_at timestamptz default now()
);
Phase 02 · ~Week 1-2
The site layer

An Astro site, deliberately static.

The site is intentionally a static Astro project. Four reasons:

  1. Astro outputs plain HTML — perfect for crawling, no SPA rendering blockers
  2. Markdown content with frontmatter is trivial for the agent to generate and edit
  3. Schema markup is baked into layouts, not bolted on per page
  4. Vercel auto-deploys on every commit — "publish" = "git commit"

Each piece of content is a markdown file in a typed collection. Services, cities, blog posts. The frontmatter is enforced by Astro's content collections, which means the agent can't publish a malformed page — it would fail the build.

Content collections (typed schemas)

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const services = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    description: z.string(),
    targetKeyword: z.string(),
    serviceAreas: z.array(z.string()).optional(),
    pricing: z.object({ from: z.number() }).optional(),
    faqs: z.array(z.object({ q: z.string(), a: z.string() })).optional(),
    publishedAt: z.date()
  })
});

// ... cities, blog collections follow same pattern

export const collections = { services, cities, blog };

Schema markup baked in

Every layout — ServiceLayout.astro, CityLayout.astro, BlogLayout.astro — emits structured data automatically based on the frontmatter. The agent doesn't need to remember to add schema. It can't not have schema.

Phase 03 · ~Week 2-5
The skills

Each skill is a self-contained capability.

This is the heart of the system. Each skill is a single SKILL.md file telling Claude Code exactly how to do one job. Skills live in agent/skills/. The agent never freelances. It runs skills.

The master instruction file — agent/CLAUDE.md — lists every available skill and the operating principles that apply to all of them.

Operating principles (from CLAUDE.md)

1. Every action gets logged to the actions table with reasoning.

2. Never publish without running all safety gates.

3. Local trust signals are non-negotiable — every page needs verifiable facts.

4. Rank tracking is a means, not an end. Lead generation is the goal.

5. When uncertain, log a blocked action with reasoning and alert via Slack.

The full skill inventory

Fourteen skills, each ~100-300 lines of natural-language instruction. Together they cover the entire loop.

SKILLperceive-state.md
runs every scheduled cycle
What it does

Pulls current SEO state from every source: Supabase, GSC, GA4, GBP, Local Falcon. Outputs a structured JSON state object that downstream skills consume.

Why it's first

You can't decide what to do until you know what's happening. This skill is the agent's eyes.

SKILLplan-next-action.md
runs after perceive-state
What it does

Takes the state JSON. Calls Claude Opus (the planner model) with a strict constraint: return ONE action. Outputs JSON: action type, target, reasoning, estimated impact, confidence score.

Decision priority order
  1. Critical issues (de-indexed, manual penalties) — fix immediately
  2. Decaying winners (>20% drop in 30 days) — refresh
  3. Easy wins (ranking 4-15, commercial intent) — expand + internal link
  4. New opportunities (high-priority keywords, no targeting page) — publish
  5. GBP work — minimum 2 per week
  6. Maintenance (linking, schema, tech health)
SKILLkeyword-research.md
weekly
What it does

Calls DataForSEO MCP with seed terms. Pulls related terms, volume, difficulty, CPC, current SERP. Scores each candidate by business_fit, local_match, and priority_score. New keywords inserted with status='discovered'; top 10 by priority get auto-promoted to 'targeted'.

Guardrail

Hard cap of $5 in API spend per run. Skip terms where business fit < 0.4. Skip volume < 10 unless local_match is true.

SKILLtrack-rankings.md
daily at 6am
What it does

For every targeted/ranking keyword, calls DataForSEO SERP. For local-match keywords, also calls Local Falcon for geo-gridded data. Writes to rankings table. Flags movers >5 positions.

SKILLpublish-service-page.md
on planner request
What it does
  1. Reads target keyword, supporting keywords, action input
  2. Pulls verifiable facts from client_facts
  3. Pulls top 3 ranking competitor pages, summarizes structure
  4. Generates a brief: H1, H2 outline, internal link targets, FAQ source material
  5. Generates full page content as markdown with frontmatter
  6. Runs safety-gates.md against the draft
  7. If gates pass: commits to GitHub, Vercel auto-deploys
  8. Inserts row in pages, logs the action
  9. Schedules outcome measurement at +7d and +30d
SKILLpublish-city-page.md
on planner request
What it does

Same flow as service page, with city-specific requirements: title pattern "{Service} in {City}, {State}"; 600-800 words; embedded GBP map; named neighborhoods; links to all sibling city pages (hub-and-spoke). Schema is Service + LocalBusiness + BreadcrumbList.

Guardrail

Never publish a city page outside the client's service area. Never duplicate primary keyword targeting.

SKILLpublish-blog-post.md
on planner request
What it does

Informational-intent content only. Pulls People Also Ask data from DataForSEO. Generates 1200-2000 words built around real questions. Always includes a related-services callout box, internal links to 2-3 service pages, and an FAQ section.

SKILLrefresh-decaying-page.md
on planner request
What it does

Pulls current page content via GitHub MCP. Pulls top 3 current rankers for the page's target keyword. Diffs the structure — what do they cover that we don't? Pulls recent GSC query data — what queries is the page already attracting but not optimizing for? Generates a refresh plan, applies it as an update, runs gates, commits.

SKILLinternal-linking-pass.md
monthly + on new publish
What it does

Builds a graph of current internal links by parsing every markdown file. Identifies orphaned pages and hub candidates. For each orphaned page, finds 3 contextually relevant existing pages and adds links. Commits all changes as one PR, one action log entry.

SKILLgbp-post.md
2x weekly minimum
What it does

Pulls a recent winning page or featured service. Generates a 100-300 word GBP post with a local hook (named city, season, recent project), a clear CTA, and an image reference pulled from client_facts. Posts via GBP MCP. Never repeats the same content within 60 days.

SKILLcompetitor-delta.md
weekly
What it does

For each tracked competitor domain, pulls their current top-ranking pages for our targeted keywords. Diffs against last week's snapshot. Identifies new pages, gained rankings, lost rankings. Summarizes new competitor pages — what angles, what keywords? Feeds findings to the planner.

SKILLaudit-site.md
monthly
What it does

Runs DataForSEO OnPage against the site and Lighthouse on the 10 most important pages. Identifies broken links, duplicate metadata, slow pages, missing schema, mobile issues. Auto-fixes tier-1 issues (broken internal links, missing alt text, schema additions). Queues tier-2/3 for planner review.

SKILLsafety-gates.md
before ANY publish
Seven gates, all must pass
  • Fact-check — Claude verifies draft against client_facts. Fail if score < 0.85
  • YMYL check — fail if medical, legal, financial, safety topics
  • Duplicate / cannibalization — fail if existing page targets same keyword
  • Local signal — fail if missing 2+ cities, real pricing, or image
  • Word count — fail if under minimum for page type
  • Internal links — fail if under minimum count
  • Brand voice — Claude scores against tone-of-voice JSON. Fail if < 0.75
If any gate fails

Do not publish. Log a 'blocked' action with the gate results. Alert Slack. Move on to the next planned action.

SKILLweekly-digest.md
Sundays at 6pm
What it does

Pulls the week's actions, rank deltas, traffic deltas, GBP deltas, lead deltas. Generates a markdown digest covering: what I did this week, what moved, what I learned, what I'll prioritize next week. Emails via Gmail MCP. Posts summary to Slack.

Phase 04 · ~Week 5-6
Scheduling & orchestration

The agent runs on triggers, not continuously.

Vercel Cron is the nervous system. A simple vercel.json defines four scheduled endpoints:

{
  "crons": [
    { "path": "/api/agent/daily",   "schedule": "0 13 * * *" },
    { "path": "/api/agent/weekly",  "schedule": "0 14 * * 1" },
    { "path": "/api/agent/monthly", "schedule": "0 15 1 * *" },
    { "path": "/api/agent/digest",  "schedule": "0 1 * * 1" }
  ]
}
Phase 05 · ~Week 6
Guardrails

Beyond per-publish gates: global guardrails.

Even with safety gates on every publish, the system needs standing constraints. These are the protections that keep "fully autonomous" from becoming "fully unsupervised."

Cost caps

Frequency caps

Rollback triggers

A note on bursting

Google notices when a small site goes from publishing once a month to publishing every day. Frequency caps aren't an arbitrary restriction — they're a defensive posture. The whole point of "self-learning" is that the site gets better every week, not just bigger.

Phase 06 · ~Week 7-8
Reporting & feedback

Outcomes feed back into the loop.

The system has to prove it's working — to you, to clients, and to itself. The feedback loop is the difference between automation and learning.

After every completed action, three outcome measurements get scheduled:

Those outcomes write back to actions.outcome. The planner reads recent outcomes when deciding the next action. Actions that worked get amplified. Actions that didn't get parked. That is the learning mechanism, made concrete.

Rank tracking is a means, not an end. The system optimizes for what shows up in the bank account.

§ 12Build sequence, week by week

WeekMilestone
1Foundation. Accounts, MCPs, Supabase schema, Astro init, one test page live with schema verified.
2Perception. perceive-state, track-rankings, keyword-research. Run daily for 7 days against empty queue.
3First publish. publish-service-page + safety-gates. Manually trigger one publish. Verify indexed in <72h.
4The planner. plan-next-action. Run daily. Watch the action log accumulate. Spot-check reasoning.
5Full skill set. City pages, blog, refresh, internal linking, GBP, competitor delta, audit.
6Guardrails. All global caps. Trigger a deliberate failure to verify the YMYL gate fires correctly.
7Reporting. Dashboard or Notion mirror. Weekly digest. Outcome measurement back-references.
8Watch and tune. Two weeks unattended (with Slack monitoring). Read every action. Tune planner prompts.

After week 8, the lighthouse is live. Every subsequent week, the agent gets smarter because the actions table is growing.

§ 13Appendix — what to expect

Where things will break

I am building this in public, which means I expect it to fail in ways I can't predict yet. The known risks:

The honest read

If you handed this playbook to a competent full-stack developer with the prerequisite accounts, they could build the lighthouse implementation in 6 to 8 weeks. The code isn't the hard part. The hard part is the discipline of watching the action log every week and tuning the planner prompts based on real outcomes.

The system gets better because you get better at noticing what it's doing well and what it's doing poorly. That's the part that can't be outsourced. That's the part that compounds.