客户 Onboarding Agent
本页说明客户 Onboarding Agent 如何在只读委托下实测客户环境的配置状态,结合客户目标与沟通历史生成个性化入驻清单,并把写操作交还终端客户执行。读完本页,你能理解这个场景为什么三个模块都是核心、环境状态为什么必须以源系统为准而不是记忆,以及写操作的交还机制如何工作。
适用场景
客户成功团队希望 Agent 根据客户行业、购买产品和历史沟通,生成入驻计划和下一步清单,并按客户环境的实际配置状态(授权可见页面)逐步引导新客户完成配置。人工逐个客户核对环境状态成本高;直接给脚本一个能读写客户环境的账号,则任何一次误操作都可能落到客户的生产配置上。
典型触发时机:
- 新客户签约后,需要按其行业和购买产品生成个性化 onboarding 计划。
- 客户配置停滞在某一步,需要读取环境状态定位卡点并给出下一步。
- 试用转正式前,需要核对必备配置项是否全部完成。
工程挑战
- 环境状态随时漂移:onboarding 清单依赖客户环境的实际配置,而客户随时可能自己改动。凭上次沟通记录或历史清单推断当前状态必然出错——每次都要以源系统实测为准。
- 读与写的责任边界:核对配置只需要读,但"引导客户开启某功能"天然诱惑 Agent 代劳写操作。一旦写进客户生产环境,任何误操作都是供应商侧事故,责任无从切割。
- 上下文分散在两类系统:客户目标和沟通历史属于跨会话的客户记忆,配置实况属于客户环境的当前事实。两者一旦混淆——把记忆当环境真相——清单就会引导客户执行已完成或已失效的步骤。
模块组合
| 模块 | 角色 | 说明 |
|---|---|---|
| GenAuth | 核心 | 交互式只读委托、撤销与审计链;写操作排除在委托范围外。 |
| Web Agent | 核心 | 受控会话读取授权可见的环境状态页,逐项留证据;需要写操作时签发接管链接交还终端客户执行。 |
| GUMem | 核心 | 客户目标、沟通历史要点和确认过的偏好——跨会话延续的客户上下文;环境配置状态不在其中,每次实测。 |
权限与委托边界
Agent 本身不持有任何固有权限。每次任务的实际权限是三个集合的交集:客户成功人员真实权限 ∩ 本次显式委托范围 ∩ 企业批准边界。落到这个场景:
- 委托范围只覆盖"读取该客户的资料、历史沟通和授权可见的环境状态页面",不包含修改客户环境配置、代签承诺或变更合同条款。
- 委托凭证短时效,单次入驻检查建议分钟级有效期,过期后需重新委托。
- 客户成功负责人或管理员可随时撤销授权;撤销后新的环境读取请求立即失败。
- 越权尝试(例如提交配置变更表单)会被拒绝并留痕——审计链覆盖全部尝试,不只是成功行为。
注意:SDK 示例申请的是产品级 scope(如 webagent.do_anything:read)。客户环境的域名清单、页面范围和动作白名单这类细粒度边界由 GenAuth 的 Agent Profile 或策略层配置强制执行,不由任务 prompt 承担;本页示例未展示该配置。完整语义见 Delegate Token 与缩权。
工作流程
客户成功人员打开客户工作区并触发入驻任务。
GenAuth 通过交互式委托让负责人确认授权,签发限定为只读的委托凭证。
GUMem 召回客户目标、沟通历史要点和确认过的偏好——不含配置状态。
Web Agent 读取授权可见的客户环境状态页面,逐项核对已完成和缺失的配置项。
检查点:环境状态的每条判断都来自本次实测并对应具体页面证据;读不到的配置项标注"未知",不用记忆或上次报告补位。
Agent 生成个性化 onboarding 清单:已完成项、缺失项、推荐顺序和对应文档链接。
遇到需要写操作的步骤(例如修改配置、开启功能),Agent 发出接管请求,应用把接管链接转发给终端客户,由客户在自己的会话中执行。
检查点:Agent 不代替客户修改生产配置;客户完成接管操作交还后,Web Agent 强制重新观察环境状态再更新清单。
客户成功人员复核清单与进度,确认后同步给客户;进度节点和确认过的沟通要点写回 GUMem。
示例代码
下面的示例使用官方 Qoni SDK(@qoniai/qoni)把这个场景接到你的服务端:交互式只读委托(完整回调)→ 召回客户上下文 → 一次 doAnything.run() 实测环境状态 → 应用侧校验清单 → 负责人复核后写回进度。
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 顶层只返回通用的 RunResult(runId、status、output、artifacts 等)。事件流断线时 SDK 会用 Last-Event-ID 自动重连续传。
记忆策略
- 进 Memory:客户目标、沟通历史要点、确认过的偏好和入驻进度节点,均带时间;这些是跨会话延续的客户上下文。
- 不进 Memory:客户环境的配置状态。它随时会被客户自己改动,GUMem 不做配置真相——"已完成/缺失"的判断只来自 Web Agent 本次实测,不来自记忆或上次报告。
- 更正:客户目标或承诺变化时,把旧记录标记为已失效并指向新记忆,而不是物理删除,保证历史引导决策可追溯。
失败处理
| 情况 | 推荐处理 |
|---|---|
| 客户环境页面登录态失效 | 任务挂起,通知客户或负责人重新登录,从断点继续。 |
| 环境状态页面读不到或结构变化 | 对应配置项标注"未知"并回放会话记录,不输出无证据的状态。 |
| 委托范围外的配置写请求 | 直接拒绝并记录,事后可在审计链中查到未遂动作。 |
| 召回的历史承诺与本次沟通冲突 | 以负责人本次确认为准,并把更正写回 GUMem。 |
生产注意点
客户环境状态必须以源系统为准:清单中的每条"已完成/缺失"判断只来自本次实测页面证据,不来自 GUMem 或历史报告。客户承诺、合同和 SLA 不应由 Agent 自动生成或修改,必须经过负责人确认。客户生产环境的任何写操作都不属于 Agent 的委托范围:需要变更时通过接管链接交还终端客户执行,每次接管与交还都留有审计记录,交还后 Agent 强制重新观察再更新清单。
下一步
- 阅读 授权与浏览器沙盒 了解受控会话与人机接管的安全边界。
- 阅读 快速开始 跑通 Agent 身份与委托的最短路径。
- 继续查看 客服知识 Agent 了解相邻场景。