Skip to the content.

Autonomy policy

seo-suite is built to run SEO end to end without a human in the loop. The user supplies credentials once and sets an autonomy level once. After that, every skill decides and acts on its own within those limits.

What is never a question

Decide these from evidence, write the choice into the report’s Assumptions section and into SEO-WORKLOG.md, and carry on:

Decision How to decide it
Scope and crawl size Sitemap URL count. Up to 2,000 URLs: crawl all. Above that: section crawls (--path-prefix) in parallel subagents
Audit profile and depth audit_profile.py (references/profiles.md): request words, then audit_profile, then paid budgets, then the measured size
Templates and money pages URL patterns in the crawl CSV; navigation; pages with forms, cart, checkout, booking or pricing; Product/Service/LocalBusiness schema
Market and language Language: <html lang> and hreflang. Country, per language: where that language’s searchers are, not where the business is (“The market of each language” below)
Competitors Domains that rank in the top 10 for the site’s head terms (WebSearch)
Keyword seeds Titles, H1s and nav labels of money pages, plus GSC queries when available
Whether to research again The research store (seo-research/, references/research-store.md): research_store.py status lists the stale and missing datasets per locale. Refresh only those, reuse the rest, and write new results back with ingest
What to check and fix first The skill’s workflow and triage.py priorities
Whether an AI-written post needs a human sign-off before it goes live The project’s publishing mode: research_store.py status → publishing (references/research-store.md §2). unset runs as review and adds one question at the end (below)
Paid data calls Allowed when inside the configured budget, and only for data free sources can’t give (tools.md)
Beginner vs expert output Infer from the user’s wording. If unsure, give a plain summary with an expert appendix

The market of each language

The market is the country the searchers are in. ccTLD, currency and addresses show where the business is, which is the same country only when its customers are local.

  1. The store, once a run has decided it. site.json → markets[locale] with both gl and gl_basis (local, gsc, sells-abroad or owner). A gl with no gl_basis is not a decision: ingest keyword-ideas copies it from the first file it sees (keyword_ideas.py defaults to --gl us), and older stores took it from the business address. research_store.py status lists those locales as undecided_markets: decide them with steps 2–3, then write gl and gl_basis into site.json.
  2. Customers are local (a shop, a clinic, a local service): the business’s country, for every language (gl_basis: local). Search Console isn’t asked: clicks from abroad on a translated page don’t move a local business’s market.
  3. Customers come from abroad or buy from abroad (inbound travel, export, remote services):
    • With Search Console: the country with the most clicks on that language version’s pages (Performance, Pages filtered to the language’s path, then Countries) [measured] (gl_basis: gsc).
    • Without it [inference] (gl_basis: sells-abroad): a language that is a main language of the business’s own country keeps that country (Georgian for an operator in Georgia). Every other language gets the largest country it is written for (English for the same operator is not Georgia’s market).
  4. One country per locale. A second country that matters for the same language is a locale of its own with its region (en-gb beside en-us); the store keeps them apart (research-store.md §3, ingest keyword-ideas). With Search Console it matters at about a fifth of that version’s clicks or more [heuristic]. Without it, add the second-largest country only for a language with several large ones (English, Spanish, Arabic).
  5. Use it everywhere: keyword_ideas.py --gl, located SERP samples, competitor checks and rank sets. hreflang codes are a separate decision (seo-international).

To change a market the store already holds, edit markets[locale].gl and gl_basis in site.json and research that locale again. The store never drops data (research-store.md §5), so two things stay from the old country:

Record the choice and its evidence under Assumptions.

What to ask (batched at the end of a run, each with a recommendation)

  1. Credentials and access the run could not get: API keys, OAuth sign-ins, Search Console/GA4/Bing access or exports, repo access, or a CDN allowlist when a bot challenge blocks the crawl. Finish everything possible first. Then list exactly what each missing access would add.
  2. Business decisions: markets or languages to enter, positioning, deleting or merging pages that earn traffic or revenue, spend above the budget, and the publishing mode while it is unset (below).
  3. Actions above the configured level (below). The run finishes everything up to that level, stops at the boundary and says which level or credential would finish it (for example, level 2: the pull request is open; level 3 merges it).
  4. The approval batch: each always-gated action the run prepared (below), one item each, with exactly what will happen and through which account. Claude carries out only the items the user approves in chat.

Never ask “should I continue?”, never ask to confirm a read-only step, and never stop a run to ask something that an assumption plus a note can cover.

The publishing mode, while it is unset

Each project chooses how its AI-written posts and their translations go live: automatic, once every automated gate passes, or review, after a named human sign-off (references/research-store.md §2). The mode and the autonomy level are separate. The level decides whether a run may merge or publish at all; the mode decides whether a person signs off first.

While status shows publishing.mode as unset, a run behaves as review and never stops to ask. It adds one question to the end-of-run batch, however many posts it held, with a recommendation:

For example: “Publishing mode is unset, so this run held 4 posts for a human sign-off and shipped 8 translations noindex. Recommendation: automatic, since nobody signs posts off today and the automated gates still hold back any post that fails one. Answer automatic or review.”

When the user answers in chat, record it: python3 "${CLAUDE_PLUGIN_ROOT}/scripts/research_store.py" set-publishing --mode automatic. The next run follows it, and the next step 1 of seo-blog-builder ships what was held back once its gates pass. A scheduled run writes the question to seo-work/automation/inbox.md and never sets the mode: a scheduled prompt is not the user.

Autonomy levels (set once by the user)

Level Runs without asking Still needs the user
1. Analyse (default) All reads: fetches, crawls, scripts, free APIs, paid calls within budget, reports, work log, research store updates (uncommitted) Any change to the site or any external account
2. Fix in code Level 1 + edits to the site’s repo on a branch, commits, pushing that branch, opening a PR Merging, deploying
3. Ship Level 2 + merging PRs that pass CI, submitting sitemaps / IndexNow for changed URLs CMS publishing, account settings
4. Operate Level 3 + publishing to a CMS and Search Console sitemap submission through an authorised connector (claude_seo.py submit-sitemap, after the user’s one-time google-signin --sitemap-write; never the Indexing API) See “always gated”

Never, at any level and not even with approval: buying reviews, buying links that pass ranking value (a paid link is fine only with rel="sponsored" or nofollow, and the user buys it), and anything a vendor’s terms forbid (skills/seo-automation §D). The run reports these as risks and prepares no action for them.

Always gated, at every level: messages to real people (outreach emails, DMs, review requests), disavow files, deleting content that earns traffic, Change of Address, CDN/WAF and DNS changes, Google Business Profile and Merchant Center edits, and spend above budget.

Credentials

Where the level and budgets live

The user sets them once with /plugin configure seo-suite@seo-suite:

Key Default Meaning
autonomy_level 1 The level above (1–4)
audit_profile auto The audit profile when the request names none: auto (the free stack, or the paid list for the site’s size when its budget is above 0), quick, full-free or paid (references/profiles.md)
semrush_monthly_units, ahrefs_monthly_units 0 Monthly budget for paid calls, in API units. 0 denies paid calls
dataforseo_monthly_usd, other_monthly_usd 0 Monthly budget for paid calls, in US dollars. 0 denies paid calls
per_run_cap_pct 25 The most one run may spend, as a percentage of each monthly budget
ask_above_pct 5 A single call estimated above this percentage of its monthly budget is deferred
max_rows 50 Row limit on every paid call
cache_ttl_days 0 (per data type) How long cached paid results are reused
psi_api_key unset Optional PageSpeed Insights key

The credit guard (hooks/hooks.json) enforces the budget and limit keys on every paid call; audit_profile is read by scripts/audit_profile.py, not the guard. A call above ask_above_pct is deferred: the guard denies it and records it as a batched request in its requests.jsonl, and Claude raises it with the end-of-run questions. So “paid calls within budget” means within the monthly budget, the per-run cap and the per-call threshold. If nothing is configured, that is level 1 with budgets of 0: free tools only, which still covers a full audit. Setup and the guard’s details: skills/seo-automation and references/paid-tools-automation.md §8.