Skip to content

Partner portal monitoring agent

This page explains how a partner portal monitoring agent signs in to a partner portal under the owner's interactive delegation, uses Track to keep a long-lived watch on policy, price list, rebate, and announcement pages, and notifies the owner with evidence when a change hits. After reading it, you will understand why signed-in monitoring must use interactive delegation, how Profiles lets later rounds reuse the login state, and why policy baselines belong in a monitoring store rather than Memory.

Use case

Channel, brand, or marketing teams need to monitor product titles, images, pricing, inventory, promotions, and descriptions in partner portals, along with policy documents, price lists, rebate rules, and announcements that are only visible after sign-in. These pages have no public API, and signing in to each portal manually is slow and misses updates.

Typical triggers:

  • A partner portal publishes a new rebate rule or price list, and the channel owner must be notified before it takes effect.
  • Before a promotion window opens, product presentation across several portals must be checked against brand rules.
  • Before renewing a channel agreement, the team must confirm how portal policy documents changed since the last cycle.

Engineering challenges

  • Login-state maintenance: portal content sits behind a login wall, and session expiry, MFA, and risk-control pages are the norm. Unattended monitoring stands or falls on whether the login state can be reused safely and escalates to a human on failure instead of dying silently.
  • Change grading: portals have routine updates every day; what actually needs escalation is substantive change — "the rebate rule changed," "the policy start date moved up." Without a versioned policy baseline, grading falls back to a human re-reading everything.
  • Portal heterogeneity: every portal differs in page structure, terminology, and update style, so baseline conventions are hard to unify; one structural redesign can turn the whole monitoring chain into noise.

Module composition

ModuleRoleNotes
GenAuthCorePortal login state is high-risk delegation: interactive authorization, short-lived credentials, revocation, and audit trails for out-of-scope attempts are all carried by GenAuth.
Web AgentCoreProfiles stores and reuses the authorized login state; Track maintains the long-lived monitor that fetches, compares, and calls back on schedule.
GUMemOptionalOnly stores owner-confirmed long-term risk preferences (for example, "always escalate rebate changes"). Policy and price baselines are business state — they go to the baseline store, managed by version, not to Memory.

Partner portal monitoring agent architecture

Permission and delegation boundaries

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

  • The delegated scope covers only "read policy, pricing, rebate, announcement, and product display pages on the specified partner portals" — no ordering, price changes, listing edits, or contacting partners.
  • Portal sign-in is a high-risk operation and must use mode: 'interactive': the owner approves in Qoni Console, and the credential is exchanged in a server-side callback — it never reaches the browser.
  • Delegation credentials are short-lived; long-running monitoring relies on scheduled re-issuance, not one long-lived credential.
  • Out-of-scope attempts (for example, an order management page 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 — portal domains, 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

Partner portal monitoring agent workflow

  1. The owner selects partners, portals, page scope, and the notification channel.

  2. Your app starts interactive delegation; the owner approves in Qoni Console, and the server-side callback exchanges the credential.

  3. Your app creates the Track monitor; the first run opens the portal, the owner completes sign-in in the controlled session, and Profiles stores the login state for later ticks.

    Checkpoint: When a login wall, MFA, or risk-control page appears, escalate to a human instead of silently bypassing it.

  4. The first run baselines the policy, price list, rebate, and announcement pages; the baseline goes to the baseline store together with its version.

  5. Every scheduled run afterwards fetches the current pages and compares against the baseline; whether something changed is decided by rules, not by model wording.

  6. On a hit, Track calls back to your app with a change record: changed fields, screenshot evidence, and source URLs.

  7. Your app grades the change against the policy baseline (optionally combined with owner-confirmed risk preferences) and pushes the anomaly, evidence, suggested actions, and audit id to the owner.

    Checkpoint: Every anomaly should trace back to a concrete page screenshot and source URL; change verdicts without evidence should not enter the notification.

Example code

The example below wires this scenario in with the official Qoni SDK (@qoniai/qoni): interactive delegation (full callback) → load the versioned policy baseline from your baseline store → create the Track monitor for the long-lived watch.

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

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

// 1. Entry point: start interactive delegation — portal login state is
//    involved, so the owner approves in Qoni Console
export async function startPortalMonitoring(ownerId: string, portalPages: string[]) {
  // Task params go to your app storage; state carries only a task ID,
  // and the callback loads the params back by that ID
  const taskId = await taskStore.save({ portalPages })
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'partner-portal-monitoring',
    scopes: [QoniScopes.DO_ANYTHING_READ], // product-level read-only scope for the signed-in watch
    redirectUri: 'https://app.example.com/qoni/callback',
    state: taskId,
    user: { id: ownerId },
    expiresIn: 900, // seconds; long-running monitoring re-issues on schedule instead of extending validity
  })
  redirectUserTo(authorization.authorizationUrl)
}

// 2. After the owner approves, exchange the delegation token in the
//    server-side callback and continue to create the monitor
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')!,
  })
  // Load the page list startPortalMonitoring stored, keyed by the task ID in state
  const { portalPages } = await taskStore.load(query.get('state')!)
  return createPortalMonitor(grant, portalPages) // grant.token stays server-side only
}

With grant in hand, the callback calls createPortalMonitor to set up the monitor. The login state is stored by Profiles after the first sign-in and reused by later ticks — no re-login per round.

Draft interface

Draft interface — the unified SDK's Track signatures are not final; the example expresses integration intent. See Track.

ts
// The scope Track requires follows the released signature (Draft interface);
// this example carries over the product-level read-only scope for illustration
export async function createPortalMonitor(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  portalPages: string[],
) {
  // 3. Before creation: load the current policy baseline from YOUR baseline store (not Memory)
  const baseline = await loadPortalPolicyBaseline() // e.g. { version: '2026-Q3', watchedPages: [...], escalation: ... }

  // 4. Create the long-lived monitor: the first run signs in and builds the baseline,
  //    later runs compare on the schedule
  //    (the login-state reuse field follows the released Track signature; not shown here)
  const { data: monitor } = await qoni.track.create({
    token: grant.token,
    intent: `
      Watch these partner portal pages for policy, price list, rebate and
      announcement changes: ${portalPages.join(', ')}.
      Report only substantive changes with the changed fields, a screenshot
      and the source URL. Read only: never place orders, change prices,
      edit listings or contact partners.

      Policy baseline (version ${baseline.version}):
      ${JSON.stringify(baseline)}
    `,
    schedule: { kind: 'interval', intervalSeconds: 3600 },
    notify: { kind: 'callback_url', url: 'https://app.example.com/hooks/portal-track' },
  })

  await saveMonitorRef(monitor.id, { baselineVersion: baseline.version })

  // Pause scheduling during a promotion freeze or portal maintenance, resume afterwards
  await qoni.track.pause(monitor.id, { token: grant.token })
  await qoni.track.resume(monitor.id, { token: grant.token })
}

On a hit, Track calls back to your app: parse the change record, drop entries without a screenshot or source URL, write the new baseline with its version to the baseline store, and then notify the owner per your grading rules. If the team keeps owner-confirmed risk preferences in GUMem, your app reads and injects them at grading time; writing a preference always requires the owner's confirmation first.

Data and memory boundaries

This scenario touches four kinds of data; only the last optionally belongs in GUMem:

  • Versioned rules: policy baselines, monitored-page lists, grading and escalation rules — managed by version in the baseline store, with every notification referencing the baseline version.
  • Business state: page snapshots, change records, screenshot evidence — carried by Track's run records and your monitoring store, replayable.
  • Audit records: the behavior chain of interactive authorization, every fetch, and rejected out-of-scope attempts — maintained by GenAuth.
  • User Memory (optional): owner-confirmed long-term risk preferences (for example, "always escalate rebate changes") — this is where GUMem fits; policy and price baselines never enter Memory.

Failure handling

SituationRecommended handling
Login state expiresThe monitor signals that a human is needed (intervene); the owner signs in again and the watch continues — the key to closing the loop in unattended monitoring.
A portal redesign breaks the comparisonTreat it as a failure and replay that run; rebuild the baseline after human confirmation, and never emit change conclusions without evidence.
Request outside the delegated scopeReject and record it; the attempted access remains visible in the audit chain.
A callback entry lacks a screenshot or sourceApp-side validation drops the entry and records how many were dropped; no evidence, no notification.

Production notes

The Agent should not place orders, change prices, edit listings, or contact partners. Treat the output as a review report; the owner decides follow-up actions. Monitoring is read-only and notify-only — it never performs changes on a human's behalf. Set a floor on the schedule interval based on how often the portal actually updates, to avoid load on target sites; after the grant is revoked, the monitor's next fetch fails immediately — that is expected behavior, not a fault.

Next steps

  • Read Track for the monitor's full lifecycle and event stream.
  • Read the Quickstart to run the shortest path for Agent identity and delegation.
  • Continue with the Vendor monitor agent for the adjacent public-page monitoring scenario.