Personal research agent
This page explains how a personal research agent runs multi-source search and cross-checking on the public web, and persists user-confirmed preferences and conclusions as cross-session memory. After reading it, you will understand why Web Agent and GUMem are the core modules here, when interactive delegation is actually needed, and what belongs in long-term Memory.
Use case
Users repeatedly research similar topics — hardware choices, competitor moves, papers, investment information — and personal deep dives like purchase decisions, school selection, or gathering medical information. A single search rarely makes this kind of research solid: sources need cross-checking, conclusions need citations, and the next round should continue from previously confirmed conclusions instead of starting over.
Typical triggers:
- Before a major purchase, specs and real-world feedback must be cross-checked across review sites, forums, and official pages.
- Before choosing a school or program, information from many parties must be consolidated, separating official statements from third-party opinions.
- Around a medical visit, public medical information must be organized into a sourced reading list for the user and their doctor.
Engineering challenges
- Uneven source reliability: review sites may have commercial bias, forum takes mix truth with noise, and official pages only state the upside. Without multi-source cross-checking, a single-source claim easily ships as a conclusion.
- Freshness is hard to judge: prices, models, and policies change constantly, and last year's conclusion may already be stale. Without a source URL and collection time on every finding, the user cannot decide what to trust.
- Unclear long-term retention scope: budget, decision criteria, and confirmed conclusions deserve cross-session memory; full page dumps and unconfirmed claims belong only to the current task. A blurry boundary turns Memory into a store of stale web pages.
- Interactive consent only appears in a minority of cases: every product call needs a GenAuth delegate token, but public-web research runs on a silent grant; only when the research must open subscription databases, private forums, or other signed-in sources does the user need to interactively confirm "who is accessing on my behalf".
Module composition
| Module | Role | Notes |
|---|---|---|
| GenAuth | Core | The runtime issues short-lived credentials via silent delegation (required for every product call); interactive consent is not needed by default here — upgrade to interactive only for signed-in sources or write actions (posting, ordering). |
| Web Agent | Core | Runs multi-source search and cross-checking via WebSearch, keeping a source URL and collection time per finding and flagging single-source facts. |
| GUMem | Core | Stores personal preferences (budget, region, key metrics), user-confirmed conclusions, and research topic history — the memory that genuinely spans sessions. |
When interactive consent is needed
Every Web Agent and GUMem product call requires a GenAuth delegate token; for public, read-only research a silently issued grant is enough — no enterprise-style consent ceremony. The silent runtime credential already provides the constraints this scenario needs: it is short-lived, the user can revoke it at any time, and every call carries a grantId and auditId for traceability.
Only two situations require upgrading to mode: 'interactive' delegation, with explicit user confirmation in the Qoni Console:
- Signed-in sources: the research needs pages visible only after the user signs in — subscription databases, paid reviews, private forums.
- Write actions: the task would post, comment, or order — actions that change external state, which this scenario excludes by default.
Note: the example on this page requests product-level delegation (products: ['webSearch']) plus GUMem memory scopes. Fine-grained boundaries such as domain lists 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
The user submits a research question, such as "camera options within a $3,000 budget".
Your app obtains a silent runtime credential scoped to web search and GUMem memory read/write.
GUMem recalls relevant preferences and confirmed conclusions on the same topic — budget, region, key metrics, and the outcome of the last round.
Web Agent runs multiple queries across official pages, reviews, and community threads, keeping a source URL and collection time per finding.
Checkpoint: Key facts backed by a single source are flagged "not cross-checked"; conflicting sources are presented as-is, never guessed away.
The Agent assembles the report, attaching a citation and confidence label to every conclusion; for medical or legal topics, the output states it is an information digest, not professional advice.
The user reviews the report and confirms which conclusions and preferences are worth keeping long-term.
Checkpoint: Only user-confirmed preferences and conclusions are written back to long-term Memory; unconfirmed web content stays within this task.
Example code
The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): silent runtime credential → recall preferences and confirmed conclusions → one webSearch.run() for multi-source research → app-side validation against the JSON contract → write back to Memory after user confirmation.
import { Qoni, QoniScopes } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
interface ResearchFinding {
finding: string
sourceUrls: string[]
capturedAt: string
confidence: 'high' | 'medium' | 'low'
caveats: string[]
}
// App-side validation: parse the JSON contract set by the prompt and
// drop entries missing a source URL or collection time
function parseFindings(output: string): { kept: ResearchFinding[]; dropped: number } {
const items = JSON.parse(output) as ResearchFinding[]
const kept = items.filter(
(item) =>
item.finding &&
Array.isArray(item.sourceUrls) &&
item.sourceUrls.length > 0 &&
item.capturedAt,
)
return { kept, dropped: items.length - kept.length }
}
export async function runResearch(userId: string, question: string) {
// 1. Silent delegation issues the runtime credential — required for
// every product call; public-web research needs no interactive consent
const { data: grant } = await qoni.delegateToken({
user: { id: userId },
agent: 'personal-research',
products: ['webSearch'],
scopes: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_MEMORY_WRITE],
})
// 2. Before the run: recall preferences and confirmed conclusions
// on this topic. On first use, create the sessionId with
// qoni.gumem.createSession
const { data: prior } = await qoni.gumem.recall({
token: grant.token,
sessionId: `user-${userId}`,
query: 'budget, region, key metrics, confirmed conclusions on this topic',
})
// 3. One WebSearch run covers the multi-source sweep: the prompt
// pins down an explicit JSON output contract
const search = await qoni.webSearch.run({
token: grant.token,
prompt: `
Research: ${question}.
Cross-check key facts across multiple sources. Return ONLY a JSON
array of findings, each shaped as
{ "finding": string, "sourceUrls": string[], "capturedAt": string,
"confidence": "high" | "medium" | "low", "caveats": string[] }.
Flag single-source facts with a "not cross-checked" caveat and
present conflicting sources as-is. For medical or legal topics,
add a caveat that the output is an information digest, not
professional advice.
Prior confirmed context: ${JSON.stringify(prior)}
`,
maxResultsPerQuery: 8,
})
const result = await search.wait()
// 4. App-side validation: entries without a source are dropped, and
// the report notes how many were dropped
const { kept: findings, dropped } = parseFindings(result.output)
return {
findings,
droppedCount: dropped,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}
// 5. Called after the user reviews and confirms in your UI: only
// whitelisted fields (confirmed preference and conclusion text) are
// written back — never the raw search output
export async function confirmAndRemember(
grantToken: string,
sessionId: string,
confirmed: { preferences: string[]; conclusions: string[] },
) {
await qoni.gumem.addMessages({
token: grantToken,
sessionId,
messages: [
{
role: 'user',
content: [
...confirmed.preferences.map((p) => `Confirmed preference: ${p}`),
...confirmed.conclusions.map((c) => `Confirmed conclusion: ${c}`),
].join('\n'),
},
],
})
}The report structure is a JSON contract set by the task prompt, and parseFindings enforces it on the app side: entries missing a source URL or collection time are dropped, with the drop count surfaced in the return value. Writing back to Memory happens in the separate confirmAndRemember, called only after user confirmation and accepting only whitelisted fields. The SDK itself returns the generic RunResult (runId, status, output, artifacts, and so on).
Memory strategy
- Into Memory: user-confirmed preferences (budget, region, key metrics), confirmed conclusions (with source pointers, confidence, and time), and research topic history; preference memories decay quarterly so old tastes do not dominate new decisions.
- Not into Memory: full page dumps, unconfirmed web claims, and one-off comparison data — discarded when the task ends, re-collected when needed.
- Corrections: when new evidence overturns a conclusion (for example, a model hit by a quality scandal), mark the old conclusion invalidated and point it to the new memory instead of physically deleting it.
Failure handling
| Situation | Recommended handling |
|---|---|
| A source page requires sign-in or shows a CAPTCHA | Skip the source and record it; if signed-in access is genuinely needed, start a separate interactive delegation — never silently bypass. |
| A key fact rests on a single source | Flag it "not cross-checked"; never treat an uncorroborated claim as verified. |
| A result entry lacks a source or collection time | App-side validation drops the entry and the report notes how many were dropped. |
| A recalled conclusion conflicts with new evidence | New evidence wins; mark the old conclusion invalidated and write the correction back to GUMem. |
Production notes
Never write full web pages into long-term Memory: only user-confirmed preferences, conclusions, or long-term constraints are written back; everything else is discarded when the task ends. Research output on medical or legal topics must state it is an information digest, not professional advice — final decisions belong to the user and licensed professionals. Time-sensitive data in the report (prices, policies) should keep its collection time and be re-collected rather than reused once past a reasonable window.
Next steps
- Read the Quickstart to run the shortest path for Agent identity and delegation.
- Read WebSearch for how multi-source search works.
- Continue with the Travel planning agent for an adjacent scenario.