Skip to content

Competitive intelligence agent

This page explains how a competitive intelligence agent uses Web Agent's WebSearch to periodically collect competitor launches, pricing, docs updates, hiring signals, and news, validates sources and confidence on the app side, writes the results into a structured intel store, and assembles a traceable intelligence brief. After reading it, you will understand why collecting public information only needs a silently issued runtime credential, how long-running tasks reconnect from run.id, and why historical judgments belong in a versioned intel store rather than Memory.

Use case

Product, marketing, or strategy teams need to track competitor launches, pricing changes, docs updates, and news. These signals are scattered across websites, changelogs, hiring pages, and press coverage; manual aggregation is slow and tends to treat second-hand retellings as first-hand facts, and a brief without uniform source labels cannot be verified afterwards.

Typical triggers:

  • The weekly or biweekly intelligence brief is due and must cover a fixed competitor list.
  • After a competitor launch event or major release, its impact on the team's roadmap must be assessed quickly.
  • A competitor's hiring page shows roles signaling a new direction that should feed strategic judgment.

Engineering challenges

  • Second-hand retellings vs. first-hand facts: the same "competitor shipped X" carries very different credibility from an official changelog versus a press retelling. A brief that does not tier its sources spreads speculation as conclusion, with no way to correct it later.
  • Increment detection: what the team actually wants is "what's new this cycle." Without a versioned store of historical items, every brief re-reports known facts or misses quiet changes.
  • Interrupt recovery for long runs: a collection cycle covering a dozen competitors can run for tens of minutes, and process restarts, deploys, or timeouts will cut it off. A collection task that cannot reconnect has to be re-run from scratch.

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 — see "When interactive consent is needed" below.
Web AgentCoreWebSearch collects public signals across sources; run() returns a reconnectable handle, so long tasks save run.id and attach() at any time.
GUMemNot usedIntel items, sources, and confidence are shared team business data — they go to a structured intel store; historical judgments live there too, versioned, invalidated when disproven rather than deleted. Memory plays no part in this scenario.

Competitive intelligence agent architecture

Every product call requires a GenAuth delegate token; public read-only scenarios are covered by silent delegation. This scenario reads publicly visible pages only and needs no interactive confirmation in the Console:

  • Public reads: issue a runtime credential silently with delegateToken (products: ['webSearch']). The credential is short-lived and revocable at any time, re-issued per cycle on schedule; every issuance and search enters the audit chain.
  • Signed-in targets or write actions: this scenario explicitly never signs in to competitor products, registers trial accounts, or bypasses access controls. If you genuinely need signed-in competitor research, that is a different scenario and should use mode: 'interactive' delegation — see the Authenticated competitive messaging agent.

Note: the SDK example requests product-level authorization. Fine-grained boundaries — competitor domain lists and fetch frequency — 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

Competitive intelligence agent workflow

  1. The team defines the competitor list, topics of interest, and brief cadence, maintained in a versioned collection playbook.

  2. Your app silently issues a runtime credential and loads the current playbook version and already-reported facts from the intel store.

  3. Your app starts the WebSearch collection and saves the run.id for reconnection.

    Checkpoint: After a process restart or a wait() timeout, reconnect to the same task with qoni.webSearch.attach(run.id, { token }) instead of re-running the whole cycle.

  4. Web Agent searches across sources for launch, pricing, docs, hiring, and news signals, keeping a source URL per item.

  5. Your app parses and validates the results: unsourced items are dropped; key conclusions with corroboration < 2 are marked single_source: true and routed to a needs-review list instead of the brief body; contradictory items are kept side by side, never merged silently.

    Checkpoint: Key conclusions backed by a single source go to the needs-review list and only graduate to brief facts after human verification; when sources contradict each other, keep the contradiction on record instead of picking a side.

  6. Validated items are written to the structured intel store together with the playbook version; comparison against historical items highlights the true increment.

  7. Your app assembles the brief (facts, inferences, and suggestions layered separately) with sources, confidence labels, and the audit id for the analyst.

    Checkpoint: Every conclusion in the brief should trace back to a concrete item and source in the intel store; conclusions without evidence should not enter the deliverable.

Example code

The example below wires this scenario in with the official Qoni SDK (@qoniai/qoni): silent delegation → load the versioned playbook from the intel store → webSearch.run() with run.id saved → app-side source and confidence validation → write to the intel 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 collectIntel(projectId: string, competitors: string[]) {
  // 1. Silent delegation: public pages only, no site sign-in;
  //    the credential is revocable at any time
  const { data: grant } = await qoni.delegateToken({
    user: { id: process.env.QONI_USER_ID! },
    agent: 'competitive-intelligence',
    products: ['webSearch'],
  })

  // 2. Before collection: load the current playbook and already-reported
  //    facts from YOUR intel store (not Memory)
  const playbook = await loadIntelPlaybook(projectId) // e.g. { version: '2026-W34', topics: [...], knownFacts: [...] }

  // 3. Start the collection: save run.id first so the long task can reconnect
  const search = await qoni.webSearch.run({
    token: grant.token,
    prompt: `
      New launches, pricing changes, docs updates, hiring signals and news
      for these competitors: ${competitors.join(', ')}.
      Return items as a JSON array of
      { competitor, claim, sourceUrl, publishedAt, confidence, corroboration }
      objects. confidence grades how well the claim is supported; corroboration
      is the number of independent sources backing it. Keep contradictory
      sources as separate items instead of merging them. Public pages only.
      Skip facts already listed in the playbook below.

      Collection playbook (version ${playbook.version}):
      ${JSON.stringify(playbook)}
    `,
    maxResultsPerQuery: 5,
  })
  // Persist the run id AND this round's playbook version so a resumed run
  // is still archived under the version it was started with
  await saveRunRef(projectId, { runId: search.id, playbookVersion: playbook.version })

  const result = await search.wait()
  return finalizeIntel(projectId, result, playbook.version, grant)
}

// After a process restart or timeout: reconnect to the same long task by run.id
export async function resumeIntelRun(projectId: string, token: string) {
  const ref = await loadRunRef(projectId) // { runId, playbookVersion }
  const search = await qoni.webSearch.attach(ref.runId, { token })
  const result = await search.wait()
  // Archive under the version pinned at start time — a playbook update during
  // the interruption must not change this run's attribution
  return persistIntelItems(projectId, result, ref.playbookVersion)
}

async function finalizeIntel(projectId: string, result: { output: unknown; artifacts: unknown[] }, playbookVersion: string, grant: { token: string; auditId: string; grantedScopes: string[] }) {
  const { verified, needsReview } = await persistIntelItems(projectId, result, playbookVersion)
  return {
    items: verified,
    needsReview, // needs-review list: single-source conclusions graduate to brief facts only after human verification
    artifacts: result.artifacts,
    playbookVersion,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

async function persistIntelItems(projectId: string, result: { output: unknown; artifacts: unknown[] }, playbookVersion: string) {
  // 4. Parse and validate the output contract on the app side:
  //    items without a source are dropped
  const sourced = parseIntelItems(result.output).filter((i) => i.sourceUrl)

  // 5. Triage: key conclusions with corroboration < 2 are marked
  //    single_source and routed to the needs-review list, not the brief body
  const verified = sourced.filter((i) => i.corroboration >= 2)
  const needsReview = sourced
    .filter((i) => i.corroboration < 2)
    .map((i) => ({ ...i, single_source: true }))

  // 6. Items, sources, and confidence go to the structured intel store
  //    (versioned) — never to Memory; needs-review items get their own queue
  await intelStore.append(projectId, verified, { playbookVersion })
  await intelStore.appendReviewQueue(projectId, needsReview, { playbookVersion })
  return { verified, needsReview }
}

The output structure is a contract set by the task prompt: here it is an array of { competitor, claim, sourceUrl, publishedAt, confidence, corroboration }, parsed and validated by parseIntelItems on the app side — any item missing sourceUrl is dropped, and items with corroboration < 2 are marked single_source: true and routed to the needs-review list, so only multi-source items reach the brief body. 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:

  • Versioned rules: the competitor list, topics of interest, and confidence-tiering standards — managed by version in the collection playbook, with every brief referencing the playbook version.
  • Business state: intel items, source pointers, confidence labels, and historical judgments — kept in the structured intel store; disproven judgments are invalidated and linked to their replacements, so evolution stays traceable.
  • Audit records: the behavior chain of credential issuance and every search — maintained by GenAuth.
  • User Memory: this scenario does not use GUMem. Intelligence is shared team business data, not one user's preferences; putting it in Memory loses version reconciliation and team sharing.

Failure handling

SituationRecommended handling
The collection run is interrupted (restart, deploy, timeout)Reconnect with attach() using the saved run.id; never re-run the whole cycle.
A source page is unreachable or taken downKeep the failure record and lower the confidence of related items; never present cached content as current fact.
Sources contradict each other on the same factWrite the sources and differences side by side, mark them pending human judgment, and do not merge into a single conclusion.
A key conclusion has only one supporting source (corroboration < 2)Mark it single_source: true and route it to the needs-review list; it graduates to a brief fact only after human verification.
An output item lacks a sourceApp-side validation drops the item and the brief notes how many were dropped.

Production notes

Market analysis should separate facts, inferences, and suggestions, and never present an inference as a settled conclusion; the layering happens when your app assembles the brief, based on the per-item sources and confidence in the intel store. Collection covers only publicly visible pages — no account registration, no bypassing access controls — and fetch frequency against target sites should be capped. For fixed targets such as pricing pages that need a long-lived watch, see the Track shape in the Vendor monitor agent.

Next steps