Support knowledge agent
This page explains how a support knowledge agent queries public documentation and the authorized ticket system under a customer-scoped grant, and combines that with this customer's historical context to draft sourced answers. After reading it, you will understand why all three modules are core here, why canonical answers live in the knowledge base rather than Memory, and where the customer-data trimming boundary sits.
Use case
Support teams need an Agent that answers product questions, troubleshoots common failures, or produces better-fitting suggestions from a customer's history. Answer material is scattered across product docs, status pages, community posts, and the ticket system — digging through them manually slows response times, and handing a support account to a script means the reachable customer data in the ticket system is completely unbounded. The Agent's output is an answer draft for the support team to review; it never replies to customers directly.
Typical triggers:
- High-frequency questions surge (for example, after a release), and sourced standard-answer drafts are needed fast.
- A hard ticket needs similar historical tickets, docs, and community threads consolidated before replying.
- A product announcement or known-issue update lands, and existing answers must be checked for staleness.
Engineering challenges
- Answer material mixes fresh and stale: product docs, status pages, and community posts update on different rhythms, and one announcement instantly expires old answers scattered everywhere. Without a source link and timestamp per conclusion, support cannot judge what is still trustworthy.
- Two kinds of knowledge are easy to conflate: standard answers and product knowledge are canonical, team-maintained, versioned knowledge-base content; this customer's environment facts and handling history are customer context. Copying knowledge-base content into customer memory turns it into stale private copies the moment the product changes — with no central way to fix them.
- The customer-data trimming boundary: a support account can see far more tickets than this question needs. Context passed to the web task must be trimmed to what this question requires; unrelated customer data cannot be recalled once it enters the task.
Module composition
| Module | Role | Notes |
|---|---|---|
| GenAuth | Core | Interactive delegation scoped to the current customer context; revocation and an audit chain covering every access attempt. |
| Web Agent | Core | Queries public docs, status pages, and community posts (WebSearch locates sources) and retrieves similar tickets within the authorized scope. |
| GUMem | Core | Carries only this customer's historical issues, handling notes, and confirmed environment facts; standard answers and product knowledge belong to the knowledge base, not Memory. |
Permission and delegation boundaries
The Agent holds no inherent permissions. The effective authority for each task is the intersection of three sets: what the support user actually holds ∩ what was explicitly delegated for this task ∩ what the enterprise has approved. Applied here:
- The delegated scope covers only "read tickets, history, and public knowledge sources related to the current customer" — no replying to customers, closing tickets, or modifying customer data.
- Delegation credentials are short-lived; minute-level validity is recommended for a single answering task, with re-delegation after expiry.
- The support user or an administrator can revoke the grant at any time; new ticket reads fail immediately after revocation.
- Out-of-scope attempts (for example, reading another customer's tickets unrelated to this question) 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 — the ticket system's domain lists and customer ranges — 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
The support user opens the customer conversation and triggers the answering task.
GenAuth runs interactive delegation for the support user's explicit consent and issues a credential scoped to this customer's context.
GUMem recalls this customer's historical issues, handling notes, and confirmed environment facts — knowledge-base content is not recalled.
Web Agent queries the latest product docs, status pages, and community threads, and retrieves similar tickets within the authorized scope.
Checkpoint: Context passed to the Web Agent must be trimmed to what this question needs — no unrelated customer data travels with the task.
The Agent consolidates the sources into an answer draft, attaching a source link and timestamp to every key conclusion.
The support user reviews and edits the draft, then replies to the customer; the Agent never contacts the customer directly.
Checkpoint: Every conclusion in the draft traces back to a concrete source; when a cited doc conflicts with a product announcement, it is flagged "to verify" rather than silently resolved.
Reviewed standard answers are updated in the knowledge base (maintained by the team); this customer's environment facts and handling notes are written back to GUMem.
Example code
The example below wires this scenario into your backend with the official Qoni SDK (@qoniai/qoni): interactive delegation (full callback) → recall customer history → one doAnything.run() to consolidate doc and ticket evidence → app-side draft validation → write back customer facts after the support team's review.
import { Qoni, QoniScopes } from '@qoniai/qoni'
const qoni = new Qoni({
accessKey: process.env.QONI_ACCESS_KEY!,
secretKey: process.env.QONI_SECRET_KEY!,
})
interface AnswerDraftItem {
conclusion: string
sourceRef: string
capturedAt: string
}
// App-side validation: parse the JSON contract set by the prompt and
// drop conclusions missing a source reference or capture time
function parseAnswerDraft(output: string): AnswerDraftItem[] {
const items = JSON.parse(output) as AnswerDraftItem[]
return items.filter((item) => item.conclusion && item.sourceRef && item.capturedAt)
}
export async function startAnswerDraft(supportUserId: string, ticketId: string) {
// 1. Interactive delegation: ticket-system sign-in is involved,
// so the support user confirms the grant in the Qoni Console
const { data: authorization } = await qoni.delegateToken({
mode: 'interactive',
agent: 'support-knowledge',
scopes: [
QoniScopes.DO_ANYTHING_READ,
QoniScopes.DO_ANYTHING_MANAGE,
QoniScopes.GUMEM_MEMORY_READ,
QoniScopes.GUMEM_MEMORY_WRITE,
],
redirectUri: 'https://app.example.com/qoni/callback',
state: `ticket-${ticketId}`,
user: { id: supportUserId },
expiresIn: 900, // minute-level validity for one answering task
})
redirectUserTo(authorization.authorizationUrl)
}
// After the support user consents, Qoni calls your server route
// to redeem the grant
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')!,
})
const ticket = await loadTicket(parseTicketId(query.get('state')!))
return draftAnswer(grant, ticket)
}
async function draftAnswer(
grant: { token: string; auditId: string; grantedScopes: string[] },
ticket: { id: string; customerId: string; question: string },
) {
// 2. Before the run: recall this customer's history and confirmed
// environment facts — not knowledge-base content
const { data: customerContext } = await qoni.gumem.recall({
token: grant.token,
sessionId: `customer-${ticket.customerId}`,
query: 'historical issues, confirmed environment facts, handling notes',
})
// 3. One call consolidates the evidence: public docs plus similar
// tickets within the authorized scope; the prompt pins down a
// JSON output contract
const run = await qoni.doAnything.run({
token: grant.token,
prompt: `
Draft an answer for this support question: ${ticket.question}.
Search the latest product docs, status pages and community posts,
and retrieve similar tickets within the authorized scope only.
Return ONLY a JSON array of draft items, each shaped as
{ "conclusion": string, "sourceRef": string, "capturedAt": string };
if a doc conflicts with a product announcement, prefix the
conclusion with "[to verify]" instead of picking a side.
Draft only: do not reply to the customer, close tickets,
or modify any customer data.
Customer context from Memory: ${JSON.stringify(customerContext)}
`,
capture: { screenshots: true },
})
const result = await run.wait({
// Interactions such as an expired ticket-system session:
// forward to the support user
onInteraction: (interaction) => notifySupportUserActionRequired(interaction),
})
// 4. App-side validation: every conclusion must carry a source
// reference and capture time — items missing sourceRef or
// capturedAt are dropped. Nothing is
// written to Memory here; that happens in afterReview once the
// support team has reviewed the ticket
const draft = parseAnswerDraft(result.output)
return {
draft,
artifacts: result.artifacts,
audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
}
}
// 5. Called after the support team reviews the ticket: writes only this
// customer's environment facts and preferences — never knowledge-base
// content, since reviewed standard answers go to the team-maintained
// knowledge base
export async function afterReview(
grantToken: string,
sessionId: string,
reviewedFacts: string[],
) {
await qoni.gumem.addMessages({
token: grantToken,
sessionId,
messages: [{ role: 'user', content: reviewedFacts.join('\n') }],
})
}The draft structure is a JSON contract set by the task prompt, and parseAnswerDraft enforces it on the app side: conclusions missing a source reference (sourceRef) or capture time (capturedAt) are dropped. Writing back to Memory happens in the separate afterReview, called only after the support team reviews the ticket and accepting only reviewed customer environment facts. The SDK itself returns the generic RunResult (runId, status, output, artifacts, and so on).
Memory strategy
- Into Memory: this customer's historical issues, handling notes, and confirmed environment facts (with sources and timestamps) — context that belongs to this customer alone.
- Not into Memory: standard answers, product knowledge, and troubleshooting guides. They are canonical knowledge-base content, maintained by the team per version and injected at task time; copying them into Memory creates stale private copies that cannot be centrally corrected after a product update.
- Corrections: when an environment change invalidates an old fact (for example, the customer upgraded), mark the old fact invalidated and point it to the new memory instead of physically deleting it, keeping past replies traceable.
Failure handling
| Situation | Recommended handling |
|---|---|
| Ticket-system sign-in state expires | Suspend the task, notify the support user to sign in again, and resume from the checkpoint. |
| Sources contradict each other | Present the disagreement as-is with confidence labels; the support user decides — never guess. |
| A customer-data request outside the grant | Reject and record it; the attempted access remains visible in the audit chain. |
| A recalled customer fact conflicts with the current ticket | The fact confirmed in this ticket wins; mark the old fact invalidated and write it back to GUMem. |
Production notes
Standard answers and product knowledge are canonical in the knowledge base: reviewed answers are updated there for the whole team, not written into an individual customer's Memory. Never pass customer data to web tasks that do not need it — Web Agent should only receive trimmed task context. The Agent's output is always a draft for the support team; it must not be configured to reply to customers directly, and final responsibility for outbound replies stays with the reviewing support user.
Next steps
- Read Authorization and browser sandbox for the security boundaries of controlled sessions.
- Read the Quickstart to run the shortest path for Agent identity and delegation.
- Continue with the Customer onboarding agent for an adjacent scenario.