Travel planning agent
This page explains how a travel planning agent combines cross-session travel preferences with live public information to produce an itinerary draft where every item carries a source. After reading it, you will understand why Web Agent and GUMem are the core modules here, why public price comparison needs no interactive delegation, and why booking and payment must be authorized separately.
Use case
Users want an Agent to plan trips, conferences, or business travel based on budget, dates, preferences, and live web information. Flight, hotel, and event data is scattered across many sites and changes constantly, so manual comparison is slow and goes stale quickly — and if dietary restrictions and hotel-tier preferences have to be re-asked every time, users stop using the assistant.
Typical triggers:
- Vacation dates are set, and flight-plus-hotel combinations across several destinations must be compared within budget.
- Business travel dates are fixed, and an executable itinerary must be generated quickly around schedule habits and dietary restrictions.
- The destination is locked, and several date combinations need comparing to find acceptable prices.
Engineering challenges
- Fragmented, minute-level-stale information: fares and availability can change between two queries. Every quote in the draft must carry a source URL and query time, or the user cannot tell whether it still holds.
- Preferences are hard to carry across trips: dietary restrictions, hotel tier, and schedule habits should not be re-asked each time — but treating a three-year-old preference as today's taste is equally wrong. Preference memory needs decay and correction, and satisfaction feedback from past trips should feed the correction.
- Comparison and booking are not the same class of action: reading public quotes is risk-free; signing in for member prices, touching the account, or submitting an order is a different tier entirely. Mixing them under one credential means the comparison task holds booking power.
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 for member prices, account actions, or writes such as booking and payment. |
| Web Agent | Core | Queries public transport, hotel, and event information via WebSearch, keeping a source URL and query time per option. |
| GUMem | Core | Travel preferences, dietary restrictions, and past-trip satisfaction — genuine preference memory with quarterly decay, so drafts fit today's taste rather than a three-year-old one. |
When interactive consent is needed
Every Web Agent and GUMem product call requires a GenAuth delegate token; for read-only public price comparison a silently issued grant is enough — no per-task user consent. 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.
Situations that require upgrading to mode: 'interactive' delegation, with explicit user confirmation in the Qoni Console:
- Signed-in queries: member prices, mileage awards, or account coupons — pages visible only after the user signs in.
- Account actions and booking: submitting orders, paying, canceling, or changing account settings — write actions outside the scope of this page's example, requiring a separately initiated booking task with explicit confirmation.
Note: the example on this page requests product-level delegation (products: ['webSearch']) plus GUMem memory scopes. Fine-grained boundaries such as site 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 enters the destination, dates, and budget ceiling.
Your app obtains a silent runtime credential scoped to public web queries and GUMem memory read/write.
GUMem recalls long-term preferences (hotel tier, dietary restrictions, schedule habits) and satisfaction feedback from past trips.
Web Agent queries public transport, hotel, and event information, extracting price, schedule, and availability per option, with a source URL and query time for each.
Checkpoint: Every option carries a source and query time; quotes without a source or past their window never enter the draft.
The Agent assembles the candidates into an itinerary draft, annotating fit against budget and preferences.
After the user approves the draft, booking — if wanted — runs as a separately initiated interactive-delegation task with per-action confirmation.
Checkpoint: Booking and payment never auto-execute just because the draft was approved; each confirmation maps to a replayable audit record.
After the trip, user-confirmed satisfaction feedback on stays and plans is written back to GUMem to correct future preferences.
Example code
The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): silent runtime credential → recall travel preferences → one webSearch.run() for the comparison → app-side validation against the JSON contract → write back confirmed preferences after user review.
import { Qoni, QoniScopes } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
interface TripOption {
item: string
kind: 'transport' | 'stay' | 'event'
price: string
sourceUrl: string
capturedAt: string
}
// App-side validation: parse the JSON contract set by the prompt and
// drop options missing a source URL or query time
function parseOptions(output: string): TripOption[] {
const items = JSON.parse(output) as TripOption[]
return items.filter((option) => option.item && option.kind)
}
export async function planTrip(
userId: string,
destination: string,
dates: string,
budget: string,
) {
// 1. Silent delegation issues the runtime credential — required for
// every product call; public comparison needs no interactive consent
const { data: grant } = await qoni.delegateToken({
user: { id: userId },
agent: 'travel-planning',
products: ['webSearch'],
scopes: [QoniScopes.GUMEM_MEMORY_READ, QoniScopes.GUMEM_MEMORY_WRITE],
})
// 2. Before the run: recall travel preferences and past-trip feedback.
// On first use, create the sessionId with qoni.gumem.createSession
const { data: preferences } = await qoni.gumem.recall({
token: grant.token,
sessionId: `user-${userId}`,
query: 'budget range, hotel tier, dietary restrictions, schedule habits, past trip feedback',
})
// 3. One WebSearch run covers the public comparison: the prompt pins
// down an explicit JSON output contract
const search = await qoni.webSearch.run({
token: grant.token,
prompt: `
Plan a trip to ${destination} between ${dates} within budget ${budget}.
Compare public transport, hotel and event options. Return ONLY a
JSON array of options, each shaped as
{ "item": string, "kind": "transport" | "stay" | "event",
"price": string, "sourceUrl": string, "capturedAt": string }.
Drop options without a source URL.
Comparison only: do not book, pay, or sign in to any account.
Confirmed preferences from Memory: ${JSON.stringify(preferences)}
`,
maxResultsPerQuery: 8,
})
const result = await search.wait()
// 4. App-side validation: quotes without a source or query time never
// enter the draft, and the drop count is surfaced
const options = parseOptions(result.output)
const sourced = options.filter(
(option) => option.sourceUrl && option.capturedAt,
)
// Booking and payment are outside this credential: after the user
// approves the draft, start a separate mode: 'interactive' delegation
// with per-action confirmation
return {
draft: sourced,
droppedCount: options.length - sourced.length,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}
// 5. Called after the user confirms in your UI: only whitelisted fields
// (confirmed preference and trip feedback text) are written back —
// never the raw comparison output
export async function confirmAndRemember(
grantToken: string,
sessionId: string,
confirmed: { preferences: string[]; tripFeedback: string[] },
) {
await qoni.gumem.addMessages({
token: grantToken,
sessionId,
messages: [
{
role: 'user',
content: [
...confirmed.preferences.map((p) => `Confirmed preference: ${p}`),
...confirmed.tripFeedback.map((f) => `Trip feedback: ${f}`),
].join('\n'),
},
],
})
}The draft structure is a JSON contract set by the task prompt, and parseOptions enforces it on the app side: options missing a source URL or query 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 travel preferences (budget range, hotel tier, dietary restrictions, schedule habits) and past-trip satisfaction feedback, all timestamped; preference memories decay quarterly so old tastes do not dominate new trips.
- Not into Memory: quotes and availability — they go stale in minutes and belong to this task's collected data; trip-specific constraints (such as "near the venue this time") are discarded at task end or kept for roughly two weeks.
- Corrections: when a preference changes (for example, economy to business class), mark the old preference invalidated and point it to the new memory instead of physically deleting it, keeping the trail traceable.
Failure handling
| Situation | Recommended handling |
|---|---|
| A fare page requires sign-in or shows a CAPTCHA | Skip the source and record it; if member prices are genuinely needed, start a separate interactive delegation — never silently bypass. |
| Page structure changes break price extraction | Treat it as a failure; never emit prices without evidence. |
| An option lacks a source or its query time is too old | App-side validation drops the entry; re-query when needed. |
| Recalled preferences conflict with this task's input | The user's explicit input wins; write the correction back to GUMem. |
Production notes
Booking, payment, or cancellation requires a separately initiated interactive delegation with per-action confirmation and should never auto-execute: even after the user approves the draft, submitting an order still takes one independent confirmation, and each confirmation maps to a replayable audit record. Quote data is highly time-sensitive — annotate the query time when delivering the draft, and re-query prices the next day instead of reusing them.
Next steps
- Read the Quickstart to run the shortest path for Agent identity and delegation.
- Read Track for the product capability of watching fares and availability over time.
- Continue with the Personal research agent for an adjacent scenario.