Skip to content

Ad creative review agent

This page explains how an ad creative review agent reads ad platform or creative library drafts under a read-only grant and reviews them against versioned brand and compliance rules, producing review opinions for human confirmation. After reading it, you will understand which modules this scenario needs, how the permission boundary narrows, and why brand and compliance rules belong in a policy store rather than Memory.

Use case

Marketing teams need to review images, headlines, descriptions, landing pages, and claims before ads are submitted. Creative volume multiplies with channels and A/B tests, so manually checking every item against brand rules and platform policy misses things — and giving an automation script an ad platform account also hands it the power to change budgets and submit campaigns.

Typical triggers:

  • A new campaign's creative batch enters pre-launch review and must be finished before the launch window.
  • Brand rules or forbidden compliance terms change, and existing library and queued creative must be re-checked.
  • A creative is rejected by the platform, and the rest of the batch must be swept for the same issue.

Engineering challenges

  • Review volume multiplies with channels and A/B tests while the launch window stays fixed: a batch must be fully reviewed before the window, and one out-of-bounds claim reaching production means a platform rejection or a compliance incident.
  • Opinions must be verifiable and consistent: the same sentence gets different verdicts under different platform policies and landing page contexts, so every opinion must cite its creative source and the rule version it was judged against — otherwise reviews cannot be re-checked and verdicts drift between batches.
  • Borrowed ad-account access is too broad: an account that can read drafts can usually also change budgets and submit campaigns, while the review only needs to read the drafts in scope — any accidental write is real financial damage.

Module composition

ModuleRoleNotes
GenAuthCoreRead-only delegation, revocation, and the audit chain for ad platforms and creative libraries; minute-level credentials for reviews inside a launch window.
Web AgentCoreControlled sessions sign in to the ad platform (Profiles can reuse login state) and extract assets, copy, landing page links, and claims per creative, keeping a screenshot and source each.
GUMemOptionalOnly stores reviewer-confirmed long-term preferences (for example, preferred opinion tone). Brand and compliance rules are versioned policy — keep them in your policy store and inject them per version; past rejection records belong in your review system. Neither is Memory.

Ad creative review agent architecture

Permission and delegation boundaries

The Agent holds no inherent permissions. The effective authority for each review task is the intersection of three sets: what the user actually holds ∩ what was explicitly delegated for this task ∩ what the enterprise has approved. Applied to this scenario:

  • The delegated scope covers only "read creative drafts, the creative library, and campaign rules in the specified ad accounts" — no campaign submission, budget changes, creative editing, or ad takedowns.
  • Delegation credentials are short-lived; minute-level validity is recommended for a single review run, with re-delegation after expiry.
  • The user or an administrator can revoke the grant at any time; new draft-read requests fail immediately after revocation.
  • Out-of-scope attempts (for example, an ad account outside the delegation) are rejected and recorded — the audit chain covers all attempts, not just successful actions.

Note: the SDK example requests product-level scopes (such as webagent.do_anything:read). Fine-grained boundaries — ad account lists, page ranges, action whitelists — are enforced by the GenAuth Agent Profile or your policy layer, not by the task prompt; that configuration is not shown on this page. See Delegate token and attenuation for the full semantics.

Workflow

Ad creative review agent workflow

  1. The user selects the campaigns or creative drafts to review and confirms the interactive authorization in Qoni Console.

  2. GenAuth issues a least-privilege, read-only delegation credential for this task.

  3. Your app loads the current version of brand and compliance rules from the policy store and injects them into the task, along with platform policy highlights.

  4. Web Agent signs in to the ad platform or creative library and extracts each creative's assets, copy, landing page link, and claims, keeping a screenshot and source per item.

    Checkpoint: When the ad platform shows a login wall, CAPTCHA, or risk-control page, the Web Agent should escalate to a human instead of silently bypassing it.

  5. The Agent reviews each creative against the rules, flagging risks, suspect claims, and copy that conflicts with the landing page, each with a creative ID and the triggered rule ID.

  6. Your app validates the output contract: opinions missing a creative ID or rule ID are dropped.

  7. The Agent returns review opinions — risk tiers, edit suggestions, and claims that need confirmation — with the policy version and an audit id, for human confirmation; confirmed outcomes are archived to your review system.

    Checkpoint: Every risk item should trace back to a concrete creative source and the rule version it triggered; conclusions without evidence should not enter the deliverable.

Example code

The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): interactive delegation (with the full callback) → load brand and compliance rules from your policy store → one doAnything.run() for the creative-by-creative review → parse and validate the opinions.

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

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

// 1. Interactive delegation: ad platform sign-in is involved —
//    the user confirms in Qoni Console
export async function startCreativeReview(userId: string, taskId: string) {
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'ad-creative-review',
    scopes: [QoniScopes.DO_ANYTHING_READ, QoniScopes.DO_ANYTHING_MANAGE],
    redirectUri: 'https://app.example.com/qoni/callback',
    state: taskId,
    user: { id: userId },
    expiresIn: 900, // minute-level validity for a single review run
  })
  redirectUserTo(authorization.authorizationUrl)
}

// GET /qoni/callback — after the user approves, exchange for the grant
// server-side and continue the task
export async function handleQoniCallback(request: Request) {
  const query = new URL(request.url).searchParams
  const { data: grant } = await qoni.completeDelegateToken({
    grantId: query.get('grantId')!,
    code: query.get('code')!,
    state: query.get('state')!,
  })
  return runReviewTask(grant, await loadReviewTask(query.get('state')!))
}

async function runReviewTask(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  task: { campaigns: string[] },
) {
  // 2. Before the run: load the current brand and compliance policy
  //    from YOUR policy store (versioned policy, not Memory)
  const policy = await loadCreativeReviewPolicy() // e.g. { version: '2026-08', forbiddenClaims: [...], brandVoice: ... }

  // 3. One call runs the review: sign in, extract each draft, opinions only
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Review the creative drafts in these campaigns: ${task.campaigns.join(', ')}.
      Extract each creative's image, headline, description, landing page link
      and claims, then flag risks against the policy below. Return findings as
      a JSON array of { creativeId, risk, quote, ruleId, sourceUrl } objects.
      Review only — never submit, change budgets, edit or take ads down.

      Creative review policy (version ${policy.version}):
      ${JSON.stringify(policy)}
    `,
    capture: { screenshots: true },
  })

  const result = await run.wait({
    // Login walls / MFA / risk-control pages: escalate to a human
    onInteraction: (interaction) => notifyUserActionRequired(interaction),
  })

  // 4. Validate the output contract on the app side: opinions missing
  //    a creative ID or rule ID are dropped
  const opinions = parseOpinions(result.output).filter(
    (o) => o.creativeId && o.ruleId,
  )

  return {
    opinions, // pending human confirmation; outcomes archived to your review system
    artifacts: result.artifacts,
    policyVersion: policy.version,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

The output structure is a contract set by the task prompt: here it is an array of { creativeId, risk, quote, ruleId, sourceUrl }, parsed and validated by parseOpinions on the app side, and any entry missing creativeId or ruleId is 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; only the last belongs in GUMem:

  • Versioned rules: brand guidelines, forbidden compliance terms, platform policy highlights — managed by version in your policy store, with every opinion citing a rule version.
  • Business state: review opinions, human confirmation outcomes, past rejection records — archived to your review system for cross-batch comparison and traceability.
  • Audit records: the delegation and behavior chain formed by grantId and auditId — maintained by GenAuth.
  • User Memory (optional): reviewer-confirmed long-term preferences, such as opinion tone — this is where GUMem fits; a one-off review neither recalls nor writes back by default.

Failure handling

SituationRecommended handling
Ad platform login state expiresSuspend the task, notify the user to sign in again, and resume from the checkpoint.
Creative library structure changes break extractionTreat it as a failure and replay the session recording; never emit risk items without evidence.
Request for an ad account outside the delegated scopeReject and record it; the attempted access remains visible in the audit chain.
An opinion lacks a creative ID or rule IDApp-side validation drops the entry and the summary notes how many were dropped.

Production notes

The Agent should not submit campaigns, change budgets, or publish ads automatically. High-risk claims need human confirmation. Review opinions are input to launch decisions, never a direct trigger for taking creative down; every opinion should carry a rule version, and old opinions are not retroactively re-judged after rules change. Extraction frequency against the ad platform should be capped to avoid triggering its risk controls.

Next steps