Agent-NativeCI/CDAutomationWorkflows·8 min read

Building Agentic SEO Workflows: Idempotency, Budgets, and CI

Five patterns for running yeet unattended — retries that don't duplicate, budgets that don't blow out, and a worked weekly automation end to end.

Everything in the previous four guides — MCP tools, the check primitive, auto-merge gates, outcome priors — is designed to be driven by an unattended process, not just a human clicking through a dashboard. This guide is the assembly manual: five patterns for wiring yeet into CI, cron, or a scheduled agent, closing with a full worked example.

The common thread across all five patterns is that none of them require yeet-specific infrastructure on your side. Idempotency is a header. Budgets are a lookup against a fixed cost table. Events are signed HTTP POSTs. If your CI system, your agent runtime, or your alerting stack already speaks HTTP and can verify an HMAC, it can drive yeet-seo unattended today.

a) Retry-safe automation with idempotency keys

Every mutating tool and endpoint — submit_urls, run_agent_job, run_check/POST /api/v1/check, card actions, hypothesis actions, content generation — accepts an idempotency_key (MCP) or Idempotency-Key header (REST). The server caches the response for 24 hours against that key. In any loop that might retry — a CI step that times out and re-runs, a webhook handler that redelivers, a scheduled agent that crashes mid-poll and restarts — always pass a deterministic key derived from the logical operation, not a fresh UUID per attempt:

{
  "tool": "submit_urls",
  "arguments": {
    "urls": ["/playbook/new-post"],
    "idempotency_key": "submit-2026-07-11-new-post"
  }
}

Get this wrong and a flaky network turns one intended submission into five, or one intended check into five separate credit-charging runs.

b) Nightly CI check: fail the build or open an issue

The simplest CI integration is a scheduled job that calls the same primitive covered in the check API guide: POST /api/v1/check, poll GET /api/v1/check/{id} to complete/partial, then branch on the report. A reasonable policy: fail the build (or open a tracking issue via your own GitHub integration) if found.candidates_open spikes past a threshold relative to the last run, or if verified_since_last contains a regressed entry — a regression is the one outcome that should always interrupt someone, regardless of autonomy mode.

c) Deploy hooks

After a deploy, notify yeet so the verifier isn't working from a stale snapshot of the site: POST /api/v1/deploy-notify with the deploy's commit/URL context. The next check's verifier pass then re-scrapes against the deployed state rather than waiting for its normal cadence to catch up — useful if you ship SEO-relevant changes (metadata, routing, content) outside of yeet's own PR pipeline and want outcome tracking to reflect them promptly.

d) Budget control

Every credit-consuming action has a fixed cost:

ActionCredits
Check1
Deep investigation3
PR opened2
Verified hands-off fix5

Plan quotas (Pro: 60 credits/month; Agent: 300 credits/month — see pricing) are hard daily/monthly caps, not soft throttles. When a call would exceed the remaining budget, the API returns 402 { "error": "insufficient_credits" } as covered in the check API guide. Design your automation to treat that 402 as the stop signal it is — don't retry it, don't backoff-and-retry it, just halt and surface it. Combine this with the expected-value math from the outcome priors guide to decide, before you spend, whether a low-verified_rate subcategory is worth the credits an aggressive automation would burn on it.

A practical way to combine (b) and (d): before a scheduled check runs, have the automation reason about remaining monthly credits divided by remaining days in the billing cycle. If a nightly job is burning more than that daily allowance — say, because deep investigations keep triggering at 3 credits a pop — throttle to every other night rather than running headfirst into a 402 mid-month and losing coverage entirely for the remaining days.

e) Events over polling

Register a webhook once instead of polling everywhere:

POST /api/v1/webhooks
Authorization: Bearer yseo_your_key_here
Content-Type: application/json

{
  "url": "https://your-service.example.com/hooks/yeet",
  "events": ["card.*", "check.completed"]
}

Wildcard subscriptions like card.* or even * are supported. Every delivery carries X-YeetSeo-Event, X-YeetSeo-Webhook-Id, and X-YeetSeo-Signature: sha256=<hmac> computed against your endpoint's whsec_ secret — verify it before trusting the body, and respond within the 10-second delivery timeout (queue heavy processing async). The event catalog includes card.created/applied/dismissed/verified/regressed, agent_run.started/completed/failed, check.started/completed, rank.changed, hypothesis.created, property.connected/disconnected, and citation.detected.

Worked example: a weekly Claude Code automation

Put together, here's a full weekly loop, scheduled via a cron trigger driving a Claude Code session with the yeet-seo MCP server connected on a write-scoped key:

  1. Run the check. Call run_check with idempotency_key: "weekly-check-2026-W28".
  2. Poll to terminal. Call get_check on an interval until status is complete or partial.
  3. Approve nothing automatically. This automation is read-and-report only — it never calls approve_hypothesis or any card-mutating tool. Autonomy mode and auto-merge gates (see the auto-apply deep dive) already govern what yeet does unattended; this loop's job is visibility, not additional authority.
  4. Post the report to Slack. Format found, did, and verified_since_last into a summary and POST it to a Slack incoming webhook URL — plain HTTP, no yeet-specific machinery involved.
  5. Escalate awaiting_approval specifically. Anything in awaiting_approval.hypotheses or awaiting_approval.prs_awaiting_merge gets its own tagged message (or a separate high-priority channel) — that's the queue that structurally requires a human decision, and it's the one thing this automation should make impossible to miss.

This pattern generalizes: the automation is deliberately read-heavy and escalation-only. Any workspace that wants more autonomy than that grants it explicitly through set_autonomy/PUT /api/v1/autonomy (see auto-apply internals) — the reporting loop itself never needs to ask for more authority than "look and tell someone."

Quick FAQ

Q: Should idempotency keys be reused across different weekly runs?
A: No — derive a new key per logical run (e.g. include the ISO week), so retries within a run dedupe but distinct weekly runs don't collide with each other's cache.

Q: What should happen if the weekly check returns partial?
A: Report what's there and note it's partial in the Slack message; don't block the whole automation waiting past the 20-minute deadline — the next scheduled check fills in the rest.

Q: Is there a way to test the webhook signature locally before going live?
A: Register a webhook pointing at a local tunnel endpoint, capture a real delivery, and verify your HMAC implementation against the actual X-YeetSeo-Signature header before wiring it into production alerting.