Skip to content

SEO content planner agent

This page explains how an SEO content planner agent researches public SERPs and competing content, then generates a topic list and content briefs against the covered topics in your planning store. After reading it, you will understand why Web Agent is the core module here, why planning state lives in a planning store or CMS rather than Memory, and when interactive consent is actually needed.

Use case

Growth or content teams plan the next batch of content from public SERPs, competitor content, and the site's own planning state. SERPs and competitor content change constantly; manual consolidation misses already-covered topics and cannot keep up with competitor shifts — and every topic decision needs a data or page source behind it, or the content team cannot judge priority.

Typical triggers:

  • A new quarter's content calendar kicks off, and a topic list must be produced from keyword gaps.
  • Traffic drops for a keyword group, and competitor content must be researched to weigh update priority.
  • The site enters a new topic area, and keyword gaps versus covered topics must be mapped.

Engineering challenges

  • SERP data is highly time-sensitive: rankings and content formats shift weekly. Without a collection time, the plan is built on stale samples; and comparisons across keyword groups only hold when the samples come from similar collection windows.
  • Planning state belongs to a system, not to memory: covered topics, the content calendar, and keyword strategy are team-shared structured state, updated by the publishing workflow. Putting them into personal memory decouples them from what the CMS actually published — which is exactly where duplicate and conflicting topics come from.
  • The evidence chain: every priority call must trace back to a concrete SERP or data source, or the content team cannot judge whether to act and planning reviews cannot verify anything.

Module composition

ModuleRoleNotes
GenAuthCoreSilent delegation issues the short-lived runtime credential every product call requires; interactive consent is only needed when login state or write actions enter the picture.
Web AgentCoreQueries public SERPs and competing content via WebSearch, keeping a source URL and collection time per item.
GUMemNot usedKeyword strategy, covered topics, and the content calendar are team-shared planning state, versioned in a planning store or CMS and updated by the publishing workflow; there is no personal memory worth persisting across sessions.

SEO content planner agent architecture

Every product call requires a GenAuth delegate token; public read-only scenarios are covered by silent delegation. Public SERP research does not need per-task user consent — a silently issued runtime credential already provides the constraints this scenario needs: it is short-lived, revocable at any time, and every call carries a grantId and auditId for traceability.

Situations that require upgrading to mode: 'interactive' interactive consent, with explicit user confirmation in the Qoni Console:

  • Signed-in access: the task needs data behind Search Console, analytics, or the CMS backend — not covered by this page's example; page-performance data is exported by your app and injected into the task instead.
  • Write actions: publishing, overwriting, or deleting CMS content — excluded by default here; the topic list ships through the content team's review and publishing workflow.

Note: the example on this page requests product-level delegation (products: ['webSearch']). Fine-grained boundaries such as queryable ranges are enforced by the GenAuth Agent Profile or your policy layer, not by the task prompt. See Delegate token and attenuation for the full semantics.

Workflow

SEO content planner agent workflow

  1. The user picks the site, topic, or keyword scope.

  2. Your app loads the keyword strategy, covered topics, and content calendar (versioned) from the planning store or CMS, and obtains a silent runtime credential.

  3. Web Agent queries public SERPs group by group via WebSearch, recording top-ranking pages, content angles, and a source URL and collection time per result.

    Checkpoint: Every SERP item carries a source URL and collection time; cross-group comparisons only run on similar collection windows.

  4. The Agent dedupes against covered topics, maps the gaps, and generates the topic list, content briefs, and priority suggestions.

    Checkpoint: Every priority call traces back to concrete data or a page source; unsupported judgments never enter the deliverable.

  5. The content team reviews the list; confirmed topics and calendar decisions are written back to the planning store or CMS as input for the next planning round.

Example code

The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): silent runtime credential → load strategy and covered topics from the planning store → one webSearch.run() for the SERP research → write confirmed topics back to the planning store.

ts
import { Qoni } from '@qoniai/qoni'

const qoni = new Qoni({
  accessKey: process.env.QONI_ACCESS_KEY!,
  secretKey: process.env.QONI_SECRET_KEY!,
})

export async function planContent(
  userId: string,
  siteId: string,
  keywordGroups: string[],
) {
  // 1. Silent runtime credential: public SERP research needs no
  //    site sign-in
  const { data: grant } = await qoni.delegateToken({
    user: { id: userId },
    agent: 'seo-content-planner',
    products: ['webSearch'],
  })

  // 2. Before the run: load strategy and covered topics from the
  //    planning store / CMS (this is versioned state, not Memory)
  const plan = await loadPlanningState(siteId)
  // e.g. { version: '2026-Q3', strategy: ..., coveredTopics: [...], calendar: [...] }

  // 3. One WebSearch run covers the SERP research, group by group
  const search = await qoni.webSearch.run({
    token: grant.token,
    prompt: `
      Research public SERPs for these keyword groups:
      ${keywordGroups.join(', ')}.
      For each group, list top-ranking pages, content angles, and gaps
      versus the covered topics below. Record the source URL and
      collection time for every result; keep collection windows
      comparable across groups. Do not publish, overwrite, or edit any
      CMS content, and never promise ranking outcomes.

      Planning state (version ${plan.version}):
      ${JSON.stringify(plan)}
    `,
    maxResultsPerQuery: 8,
  })
  const result = await search.wait()

  // 4. Validate on the app side: entries without a source or
  //    collection time are dropped
  const topics = parseTopicPlan(result.output).filter(
    (t) => t.sourceUrl && t.collectedAt,
  )

  // 5. After the content team confirms: topics and calendar decisions
  //    go back to the planning store (not Memory)
  //    await savePlanningDecisions(siteId, confirmedTopics)

  return {
    topicPlan: topics,
    planVersion: plan.version,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

The topic-plan structure is a contract set by the task prompt: entries carry a sourceUrl and collectedAt, parsed and validated by parseTopicPlan on the app side, with unsourced or untimed entries dropped. The SDK itself returns the generic RunResult (runId, status, output, artifacts, and so on).

Data and memory boundaries

This scenario touches four kinds of data; none of them belongs in GUMem:

  • Planning state: keyword strategy, covered topics, and the content calendar — team-shared structured state, versioned in a planning store or CMS, injected at task time, with confirmed topic decisions written back there.
  • Business state: topic lists, content briefs, and SERP collection samples — archived for planning review and verification.
  • Audit records: the delegation and behavior chain formed by grantId and auditId — maintained by GenAuth.
  • User Memory: not used in this scenario. If an individual editor's preferences (say, brief-writing tone) ever need persisting, that is where GUMem fits; team-shared planning state is not personal memory.

Failure handling

SituationRecommended handling
SERP results are unstable or collection failsTreat it as a failure and record the collection time; never draw trend conclusions from incomplete samples.
Collection windows diverge too much across keyword groupsRe-collect to align the windows before comparing; never mix old and new samples.
An output entry lacks a source or collection timeApp-side validation drops the entry and the list notes how many were dropped.
Analytics data behind a sign-in is neededThis task does not access it; export and inject it from your app, or start a separate interactive delegation.

Production notes

Never auto-publish or overwrite CMS content: the topic list ships through the content team's review and publishing workflow. Keep the source and collection time on ranking and competitor data; SERP data is time-sensitive, and samples past a reasonable window should be re-collected rather than reused. Topics and priorities are suggestions based on current data — never promise ranking outcomes.

Next steps