Skip to content

Quickstart

This page uses an Email Assistant Agent example to show how to call the Qoni SDK and combine GenAuth, GUMem, and Web Agent into a minimal Agent service.

Toward the agentic web

The goal of Qoni is to make Agents first-class citizens of the Web. Web Agent is not only a background click simulator, and not only a renderer for humans; it places Human, Agent, and Web in the same auditable collaboration layer, so an Agent can read, act, produce results, and render state back to people within an explicit permission boundary.

Toward the agentic webA layered model where Agents become first-class Web citizens through human authorization, local browser use, and sandboxed rendering.HTMLWebAgentHumanInteract withAgent OnlyRender to humanBrowser use inlocal backgroundCollect data & renderto human (MCPWeb)

Task

The Agent opens the user's mailbox with delegated authority, reads emails that need attention, uses the user's reply preferences to draft responses, and uses Web Agent to research public context when needed. It does not send email automatically.

What you will build

You will build a backend endpoint such as POST /agent/email-drafts. When called, it does the following:

  1. Identifies the current user and Email Assistant Agent.
  2. Lets the user delegate limited authority to the Agent.
  3. Calls doAnything.run() once with the complete task description and recalled user preferences.
  4. Streams the Agent loop as typed events and step screenshots.
  5. Lets the user complete mailbox sign-in in a controlled browser session when needed.
  6. Reads task email, researches public context, and generates drafts; preferences are read from and written to GUMem by your app around the task, and durable preferences are written only after app confirmation.
  7. Returns sources, drafts, step screenshots, permission boundary, and audit data.

Usage

Start with one SDK interface. Qoni currently ships an official Node.js / TypeScript SDK (@qoniai/qoni); other languages call the HTTP API. Both paths follow the same conceptual flow.

1. Choose an SDK interface

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

const qoni = new Qoni({
  accessKey: process.env.QONI_ACCESS_KEY!,
  secretKey: process.env.QONI_SECRET_KEY!,
})
bash
export QONI_ACCESS_KEY="<access-key>"
export QONI_SECRET_KEY="<secret-key>"
export QONI_HOST="https://<your-qoni-console-or-sdk-gateway>"
# Direct HTTP calls must construct the AK/SK Authorization signature header.

The official unified SDK is Node/TypeScript (@qoniai/qoni); other languages call the HTTP API directly. For private deployments, see Qoni SDK for constructor options such as host. Keep access keys on the server side only.

2. Identify the user and Agent

Your app signs in the user first, then sends the user identity, Agent key, and task input to your backend service.

ts
const task = {
  id: 'task-001',
  userId: 'user_123',
  agentKey: 'email-assistant',
  instruction: 'Review customer emails from today and create reply drafts'
}

The userId comes from your login system or GenAuth session. The agentKey is a stable identifier you define in your app code, such as email-assistant.

The agentKey is required because delegated authority is not granted to arbitrary backend code. It is granted to a specific Agent. Use the same agentKey every time this Agent runs, so delegation and audit records can show which Agent acted for the user.

In GenAuth, an Agent Profile is a managed configuration that describes an Agent identity. It can include the Agent name, purpose, allowed scopes, denied scopes, and audit ownership. In production, you can map agentKey to a GenAuth Agent Profile or permission template.

This Quickstart focuses on Agent identity and Agent delegation. If you need to authenticate human users with the GenAuth SDK first, read Use API & SDK to complete authentication.

3. Delegate limited authority

The agent needs to act on the user's behalf, so it first obtains a scoped, time-boxed delegation for this task only.

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

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

// User-level consent: create an authorization request first.
const { data: authorization } = await qoni.delegateToken({
  mode: "interactive",
  agent: "email-assistant",
  scopes: [
    QoniScopes.DO_ANYTHING_READ,
    QoniScopes.DO_ANYTHING_MANAGE,
    QoniScopes.GUMEM_MEMORY_READ,
    QoniScopes.GUMEM_MEMORY_WRITE,
  ],
  redirectUri: "http://localhost:3000/qoni/callback",
  state: "task-001",
  user: { id: task.userId },
  expiresIn: 900, // seconds (60-86400); size it to the task
});
redirectUserTo(authorization.authorizationUrl);

// This code runs later in the server-side GET /qoni/callback route.
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")!,
  });
  // Keep grant.token on the server for subsequent product calls.
  return grant;
}
bash
curl -X POST "$QONI_HOST/api/v3/eak/delegations" \
  -H "Authorization: <AK/SK signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "interactive",
    "agent": "email-assistant",
    "scopes": [
      "webagent.do_anything:read",
      "webagent.do_anything:manage",
      "gumem.memory:read",
      "gumem.memory:write"
    ],
    "redirectUri": "http://localhost:3000/qoni/callback",
    "state": "task-001",
    "userId": "user_123",
    "expiresIn": 900
  }'
# The first response contains authorizationUrl and grantId, not a token.
# Send the user to authorizationUrl, then complete the exchange in the server callback:
curl -X POST "$QONI_HOST/api/v3/eak/delegations/complete" \
  -H "Authorization: <AK/SK signature>" \
  -H "Content-Type: application/json" \
  -d '{
    "grantId": "<callback-grant-id>",
    "code": "<callback-code>",
    "state": "task-001"
  }'
# The completion response names the delegation token field delegationToken.

Full flow and scope reference

This is the shortest path. For the complete consent flow, the real scope constants, token exchange and revocation timing, see GenAuth's First delegation in 30 minutes. The official unified SDK is Node/TypeScript (@qoniai/qoni); other languages call the HTTP API directly.

Checkpoint:

  • The delegated authority has a clear expiration time.
  • The Agent cannot send email, delete email, or change mailbox settings.
  • The backend records userId, agentKey, scopes, task id, and audit id.

4. Run the Agent loop with doAnything

Call doAnything.run() once with the complete task. Inside this call, Web Agent runs an Agent loop: it opens the mailbox, asks the user to complete sign-in when needed, reads task emails, researches public context, and produces reply drafts. User preferences are recalled from GUMem by your app before the task and injected into the prompt — the Agent cannot read or write Memory bypassing your app. Do not pass raw mailbox credentials to the Agent, and do not store the user's mailbox password in your app service.

Progress is streamed back through the run handle: run.events() provides typed events (progress, messages, screenshots, interactions, completion), and the onScreenshot / onInteraction callbacks of wait() forward step screenshots and user-action requests to your frontend.

ts
// Before the task: recall the user's confirmed reply preferences.
// Your app decides which context to inject; create the sessionId first
// with qoni.gumem.createSession on first use.
const { data: recalled } = await qoni.gumem.recall({
  token: grant.token,
  sessionId: `user-${task.userId}`,
  query: 'email reply tone, signature and standing preferences',
})

// Start the Agent loop: open the mailbox, read task emails,
// research public web context, and generate drafts
const run = await qoni.doAnything.run({
  token: grant.token,
  prompt: `
    Open the user's mailbox.
    Find customer emails from today that need a reply.
    Apply the user's confirmed reply preferences listed below.
    If an email mentions an unfamiliar company or link, research public web context.
    Generate reply drafts only. Do not send email.

    Confirmed reply preferences from Memory:
    ${JSON.stringify(recalled)}
  `,
  capture: { screenshots: true },
})

const draftTask = await run.wait({
  // Step screenshots: stream them to the frontend to show progress
  onScreenshot: (image, step) => broadcastStep(task.id, step, image.bytes),
  // Steps that need the user: site login, MFA, confirmations, etc.
  onInteraction: (interaction) => notifyUserActionRequired(task.id, interaction),
})

// After the task: durable preferences are written back to GUMem
// only after your app confirms them
await qoni.gumem.addMessages({
  token: grant.token,
  sessionId: `user-${task.userId}`,
  messages: [
    { role: 'user', content: 'Use a concise, warm tone for refund replies.' },
  ],
})

If the mailbox requires MFA, OAuth consent, or enterprise SSO, the task emits an interaction such as site_login. Forward it to the user in onInteraction; only after explicit user approval should the application call an available action such as confirm(), openLogin(), or confirmSignedIn(). Do not approve an interaction just because can('confirm') is true: a confirmation may approve a plan or a destructive operation. After the user completes the step, Web Agent can only continue within the delegation-token boundary.

When you do not use wait(), iterate the event stream directly. The SDK reconnects and resumes the stream automatically after a disconnect; event type constants live in QoniEventTypes, and raw wire events are available through event.raw:

ts
for await (const event of run.events()) {
  if (event.type === 'progress') appendTrace(event.data)
  if (event.type === 'message') appendTrace(event.data.text)
  if (event.type === 'screenshot') renderScreenshot(event.image)
  if (event.type === 'interaction') handleInteraction(event.data)
  if (event.type === 'done') return event.data.output
}

User-facing traces should contain explainable summaries, actions, and observations, not hidden chain-of-thought. Your app controls GUMem access: the Agent only consumes the injected context, and durable preferences are written only after your app confirms them.

Checkpoint:

  • Read only user-visible emails needed for the task.
  • Do not read historical archives, settings pages, or unrelated folders.
  • Recall only task-relevant Memory such as reply tone, signature rules, and commitments the Agent must not make automatically.
  • Write Memory back only when your app confirms a durable preference.
  • Use public web context only for unfamiliar companies or links.
  • Stream explainable trace events and step screenshots so the user can inspect progress.

5. Return result and audit data

doAnything.run() returns a RunHandle; when await run.wait() completes, it returns a generic RunResult (runId, status, output, artifacts, terminalReason, and so on). The SDK does not invent business-specific fields such as drafts at the top level; the output structure is agreed in the task description and parsed and validated by your app.

ts
return {
  id: draftTask.runId,
  status: draftTask.status,
  output: draftTask.output,
  artifacts: draftTask.artifacts,
  audit: {
    auditId: grant.auditId,
    permissionBoundary: grant.grantedScopes
  }
}

Do not write full email bodies, temporary web content, or sensitive credentials into long-term Memory. If a user confirms a durable preference while reviewing the draft, your app decides whether to call qoni.gumem.addMessages.

Checkpoint

After completing the Quickstart, the user should see:

  • Summaries of emails that need replies.
  • Reply drafts for each email.
  • Public web sources used for context.
  • Trace events and step screenshots for task inspection.
  • The permission boundary and audit id for the task.
  • The next action that requires user confirmation.

The Agent should not send email, delete email, change mailbox settings, or store the user's mailbox password.

Use modules separately

The Quickstart shows GenAuth, Web Agent, and GUMem as a combined loop. You do not need to adopt every module at once.

  • Use GenAuth by itself when you need sign-in, delegated authority, or permission boundaries.
  • Use GUMem by itself when you need durable preferences, user context, or task memory.
  • Use Web Agent by itself when you need web search, extraction, or controlled browser actions.

Combine the three modules when your Agent needs identity, Memory, and web action together.

Next steps

Choose a similar scenario from User cases in the sidebar, or continue with the GenAuth, Web Agent, and GUMem module docs.