Skip to content

客户 Onboarding Agent

本页说明客户 Onboarding Agent 如何在只读委托下实测客户环境的配置状态,结合客户目标与沟通历史生成个性化入驻清单,并把写操作交还终端客户执行。读完本页,你能理解这个场景为什么三个模块都是核心、环境状态为什么必须以源系统为准而不是记忆,以及写操作的交还机制如何工作。

适用场景

客户成功团队希望 Agent 根据客户行业、购买产品和历史沟通,生成入驻计划和下一步清单,并按客户环境的实际配置状态(授权可见页面)逐步引导新客户完成配置。人工逐个客户核对环境状态成本高;直接给脚本一个能读写客户环境的账号,则任何一次误操作都可能落到客户的生产配置上。

典型触发时机:

  • 新客户签约后,需要按其行业和购买产品生成个性化 onboarding 计划。
  • 客户配置停滞在某一步,需要读取环境状态定位卡点并给出下一步。
  • 试用转正式前,需要核对必备配置项是否全部完成。

工程挑战

  • 环境状态随时漂移:onboarding 清单依赖客户环境的实际配置,而客户随时可能自己改动。凭上次沟通记录或历史清单推断当前状态必然出错——每次都要以源系统实测为准。
  • 读与写的责任边界:核对配置只需要读,但"引导客户开启某功能"天然诱惑 Agent 代劳写操作。一旦写进客户生产环境,任何误操作都是供应商侧事故,责任无从切割。
  • 上下文分散在两类系统:客户目标和沟通历史属于跨会话的客户记忆,配置实况属于客户环境的当前事实。两者一旦混淆——把记忆当环境真相——清单就会引导客户执行已完成或已失效的步骤。

模块组合

模块角色说明
GenAuth核心交互式只读委托、撤销与审计链;写操作排除在委托范围外。
Web Agent核心受控会话读取授权可见的环境状态页,逐项留证据;需要写操作时签发接管链接交还终端客户执行。
GUMem核心客户目标、沟通历史要点和确认过的偏好——跨会话延续的客户上下文;环境配置状态不在其中,每次实测。

客户 Onboarding Agent 场景架构

权限与委托边界

Agent 本身不持有任何固有权限。每次任务的实际权限是三个集合的交集:客户成功人员真实权限 ∩ 本次显式委托范围 ∩ 企业批准边界。落到这个场景:

  • 委托范围只覆盖"读取该客户的资料、历史沟通和授权可见的环境状态页面",不包含修改客户环境配置、代签承诺或变更合同条款。
  • 委托凭证短时效,单次入驻检查建议分钟级有效期,过期后需重新委托。
  • 客户成功负责人或管理员可随时撤销授权;撤销后新的环境读取请求立即失败。
  • 越权尝试(例如提交配置变更表单)会被拒绝并留痕——审计链覆盖全部尝试,不只是成功行为。

注意:SDK 示例申请的是产品级 scope(如 webagent.do_anything:read)。客户环境的域名清单、页面范围和动作白名单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担;本页示例未展示该配置。完整语义见 Delegate Token 与缩权

工作流程

客户 Onboarding Agent 工作流程

  1. 客户成功人员打开客户工作区并触发入驻任务。

  2. GenAuth 通过交互式委托让负责人确认授权,签发限定为只读的委托凭证。

  3. GUMem 召回客户目标、沟通历史要点和确认过的偏好——不含配置状态。

  4. Web Agent 读取授权可见的客户环境状态页面,逐项核对已完成和缺失的配置项。

    检查点:环境状态的每条判断都来自本次实测并对应具体页面证据;读不到的配置项标注"未知",不用记忆或上次报告补位。

  5. Agent 生成个性化 onboarding 清单:已完成项、缺失项、推荐顺序和对应文档链接。

  6. 遇到需要写操作的步骤(例如修改配置、开启功能),Agent 发出接管请求,应用把接管链接转发给终端客户,由客户在自己的会话中执行。

    检查点:Agent 不代替客户修改生产配置;客户完成接管操作交还后,Web Agent 强制重新观察环境状态再更新清单。

  7. 客户成功人员复核清单与进度,确认后同步给客户;进度节点和确认过的沟通要点写回 GUMem。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:交互式只读委托(完整回调)→ 召回客户上下文 → 一次 doAnything.run() 实测环境状态 → 应用侧校验清单 → 负责人复核后写回进度。

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

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

interface ChecklistItem {
  step: string
  status: 'done' | 'missing' | 'unknown' | 'blocked'
  evidenceUrl: string
}

// 应用侧校验:解析 prompt 约定的 JSON 契约;
// 缺页面证据的条目降级为 blocked,不当作有效判断
function parseChecklist(output: string): ChecklistItem[] {
  const items = JSON.parse(output) as ChecklistItem[]
  return items
    .filter((item) => item.step && item.status)
    .map((item) =>
      item.evidenceUrl ? item : { ...item, status: 'blocked' as const },
    )
}

export async function startOnboardingCheck(csUserId: string, customerId: string) {
  // 1. 交互式委托:涉及客户环境登录,让负责人在 Qoni Console 确认授权
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'customer-onboarding',
    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: `onboarding-${customerId}`,
    user: { id: csUserId },
    expiresIn: 900, // 单次入驻检查给分钟级有效期
  })
  redirectUserTo(authorization.authorizationUrl)
}

// 负责人同意后,Qoni 回调你的服务端路由,在这里兑换 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 customerId = parseCustomerId(query.get('state')!)
  return runOnboardingCheck(grant, customerId)
}

async function runOnboardingCheck(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  customerId: string,
) {
  // 2. 任务前:召回客户目标与沟通要点——配置状态不来自 Memory
  const { data: customerContext } = await qoni.gumem.recall({
    token: grant.token,
    sessionId: `customer-${customerId}`,
    query: 'customer goals, communication highlights, confirmed preferences',
  })

  // 3. 一次调用完成入驻检查:只读实测环境状态,写操作一律升级;
  //    prompt 明确 JSON 输出契约
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Check onboarding progress for customer ${customerId}.
      Read the authorized environment state pages and return ONLY a JSON
      array of checklist items, each shaped as
      { "step": string, "status": "done" | "missing" | "unknown",
        "evidenceUrl": string },
      where evidenceUrl points to concrete page evidence from THIS run;
      mark unreadable items as "unknown" — never fill gaps from memory
      or previous reports.
      Read-only: never change the customer's environment configuration.
      For any step that requires a write, raise a takeover interaction.
      Customer context from Memory: ${JSON.stringify(customerContext)}
    `,
    capture: { screenshots: true },
  })

  // 4. 事件流:进度与截图给负责人前端,接管请求转发给终端客户;
  //    done 事件的输出经应用侧校验后才成为清单
  let checklist: ChecklistItem[] = []
  for await (const event of run.events()) {
    if (event.type === 'progress') appendTrace(customerId, event.data)
    if (event.type === 'screenshot') renderScreenshot(customerId, event.image)
    if (event.type === 'interaction') forwardTakeoverToCustomer(customerId, event.data)
    if (event.type === 'done') checklist = parseChecklist(event.data.output)
  }

  return {
    checklist,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

// 5. 由客户成功人员复核清单后调用:只写白名单字段
//    (目标进展、确认过的偏好、沟通要点)。
//    清单里的 step/status/evidenceUrl 等配置状态字段被显式过滤在外——
//    配置状态每次实测,不写入 Memory
export async function confirmOnboardingProgress(
  grantToken: string,
  customerId: string,
  confirmed: {
    goalProgress: string[]
    preferences: string[]
    communicationHighlights: string[]
  },
) {
  await qoni.gumem.addMessages({
    token: grantToken,
    sessionId: `customer-${customerId}`,
    messages: [
      {
        role: 'user',
        content: [
          ...confirmed.goalProgress.map((g) => `Goal progress: ${g}`),
          ...confirmed.preferences.map((p) => `Confirmed preference: ${p}`),
          ...confirmed.communicationHighlights.map(
            (h) => `Communication highlight: ${h}`,
          ),
        ].join('\n'),
      },
    ],
  })
}

onboarding 清单的结构由 prompt 中的 JSON 契约约定,parseChecklist 在应用侧强制执行:每项必须含 step、status 和页面证据,缺证据的条目降级为 blocked。写回 Memory 由独立的 confirmOnboardingProgress 在负责人复核后调用,只接受白名单字段,配置状态字段不经过它。SDK 顶层只返回通用的 RunResultrunIdstatusoutputartifacts 等)。事件流断线时 SDK 会用 Last-Event-ID 自动重连续传。

记忆策略

  • 进 Memory:客户目标、沟通历史要点、确认过的偏好和入驻进度节点,均带时间;这些是跨会话延续的客户上下文。
  • 不进 Memory:客户环境的配置状态。它随时会被客户自己改动,GUMem 不做配置真相——"已完成/缺失"的判断只来自 Web Agent 本次实测,不来自记忆或上次报告。
  • 更正:客户目标或承诺变化时,把旧记录标记为已失效并指向新记忆,而不是物理删除,保证历史引导决策可追溯。

失败处理

情况推荐处理
客户环境页面登录态失效任务挂起,通知客户或负责人重新登录,从断点继续。
环境状态页面读不到或结构变化对应配置项标注"未知"并回放会话记录,不输出无证据的状态。
委托范围外的配置写请求直接拒绝并记录,事后可在审计链中查到未遂动作。
召回的历史承诺与本次沟通冲突以负责人本次确认为准,并把更正写回 GUMem。

生产注意点

客户环境状态必须以源系统为准:清单中的每条"已完成/缺失"判断只来自本次实测页面证据,不来自 GUMem 或历史报告。客户承诺、合同和 SLA 不应由 Agent 自动生成或修改,必须经过负责人确认。客户生产环境的任何写操作都不属于 Agent 的委托范围:需要变更时通过接管链接交还终端客户执行,每次接管与交还都留有审计记录,交还后 Agent 强制重新观察再更新清单。

下一步