Skip to content

社媒草稿 Agent

本页说明社媒草稿 Agent 如何在交互式委托下登录社媒后台,读取 campaign 上下文和历史帖子,按平台生成待确认的内容草稿。读完本页,你能理解这个场景需要哪些模块、发布动作为什么必须留在委托范围之外,以及品牌规则这类数据为什么应该放在规则库而不是 Memory。

适用场景

Marketing 团队需要把一次 campaign 拆成 X、LinkedIn、公众号、邮件或社区渠道的草稿,但不希望 Agent 自动发布。各平台的长度、语气和格式差异大,人工逐平台改写既慢又容易偏离品牌规则;直接把社媒账号交给脚本,则会把发布、评论和私信权限一并交出去。

典型触发时机:

  • 新 campaign 上线,需要在同一个工作日内产出多平台首发草稿。
  • 一条产品更新公告需要按平台改写成不同长度和语气的版本。
  • 品牌规则或平台偏好调整后,需要按新版本规则重写下一批草稿。

工程挑战

  • 平台约束多且互相冲突:X 的长度限制、LinkedIn 的行业语气、公众号的图文结构各不相同,同一条 campaign 信息在逐平台改写中很容易漂离品牌规则,人工逐条核对成本高。
  • 草稿与发布只隔一个按钮:多数社媒后台"保存草稿"和"发布"入口相邻,一次误触就是线上事故;"只写草稿"的意图需要机制保障,不能只靠 prompt 叮嘱。
  • 借用运营账号权限过宽:运营登录态天然带发布、评论和私信能力,而建稿任务只需要"读取上下文、写入草稿箱"这一件事。

模块组合

模块角色说明
GenAuth核心社媒后台登录的交互式委托、撤销与审计链;发布与互动动作留在委托范围之外。
Web Agent核心受控会话登录社媒后台或 CMS(Profiles 可复用登录态),读取 campaign 上下文与历史帖子,把草稿写入草稿箱。
GUMem可选仅保存用户明确确认的长期语气偏好(例如惯用的开场方式)。品牌 voice、平台规则、禁用表达属于版本化规则,放在你的规则库(policy store)里按版本注入;历史表现数据属于业务状态,放在你的 analytics 存储——都不属于 Memory。

社媒草稿 Agent 场景架构

权限与委托边界

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

  • 委托范围只覆盖"读取历史帖子与 campaign 资料、创建并更新草稿",不包含发布、评论、私信或批量互动。
  • 委托凭证短时效,单次草稿任务建议分钟级有效期,过期后需重新委托。
  • 用户或管理员可随时撤销授权;撤销后新的读取或建稿请求立即失败。
  • 越权尝试(例如触发发布动作)会被拒绝并留痕——审计链覆盖全部尝试,不只是成功行为。

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

工作流程

社媒草稿 Agent 工作流程

  1. 用户选择 campaign 和目标渠道(X、LinkedIn、公众号、邮件等)。

  2. GenAuth 发起交互式委托;用户在 Qoni Console 确认后,服务端回调兑换出最小权限凭证。

  3. 应用从规则库读取当前版本的品牌 voice、平台规则和禁用表达,注入任务描述。

  4. Web Agent 打开社媒后台或 CMS;首次访问时用户在受控会话中完成登录,后续任务可通过 Profiles 复用登录态。

    检查点:登录墙、验证码或风控页出现时,Web Agent 应升级给人处理,而不是静默绕过。

  5. Web Agent 读取 campaign 资料和历史帖子,按平台生成草稿,标注各平台在长度、语气和格式上的差异。

  6. Web Agent 把草稿写入平台草稿箱,或返回给应用界面等待确认;输出附 audit id。

    检查点:任何草稿都不应进入发布状态;后台界面上"保存草稿"与"发布"入口相邻时,该步骤应按风险动作过类型化关卡。

  7. 应用侧解析草稿输出并校验渠道标注;用户的采纳、修改和否决结果归档到你的内容库。

示例代码

下面的示例使用官方 Qoni SDK@qoniai/qoni)把这个场景接到你的服务端:交互式委托(完整 callback)→ 从规则库读取品牌规范 → 一次 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!,
})

export async function startDraftTask(userId: string, taskId: string) {
  // 1. 交互式委托:涉及社媒后台登录,让用户在 Qoni Console 确认授权
  const { data: authorization } = await qoni.delegateToken({
    mode: 'interactive',
    agent: 'social-media-draft',
    scopes: [QoniScopes.DO_ANYTHING_READ, QoniScopes.DO_ANYTHING_MANAGE],
    redirectUri: 'https://app.example.com/qoni/callback',
    state: taskId,
    user: { id: userId },
    expiresIn: 900, // 单次草稿任务给分钟级有效期
  })
  redirectUserTo(authorization.authorizationUrl)
}

// GET /qoni/callback —— 用户同意后在服务端兑换 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 task = await loadDraftTask(query.get('state')!) // 你的任务存储
  return runDraftTask(grant, task)
}

async function runDraftTask(
  grant: { token: string; auditId: string; grantedScopes: string[] },
  task: { campaignName: string; channels: string[] },
) {
  // 2. 任务前:从你的规则库读取当前版本的品牌规范(不是 Memory)
  const policy = await loadBrandPolicy() // 例如 { version: '2026-08', voice: ..., platformRules: [...], forbiddenPhrases: [...] }

  // 3. 一次调用完成建稿:登录后台、读取上下文、按平台写入草稿箱
  const run = await qoni.doAnything.run({
    token: grant.token,
    prompt: `
      Open our social media tool and read the campaign "${task.campaignName}"
      plus recent posts for these channels: ${task.channels.join(', ')}.
      Draft one post per channel, adapting length, tone and format.
      Return drafts as a JSON array of { channel, draft, notes } objects.
      Save every draft to the draft box only. Do not publish, comment,
      send direct messages, or perform any bulk engagement.

      Brand policy (version ${policy.version}):
      ${JSON.stringify(policy)}
    `,
    capture: { screenshots: true },
  })

  const result = await run.wait({
    // 登录墙 / 验证码 / 发布确认弹窗:转发给用户,由人处理
    onInteraction: (interaction) => notifyUserActionRequired(interaction),
  })

  // 4. 应用侧解析并校验输出契约:缺少渠道标注的条目不进交付物
  const drafts = parseDrafts(result.output).filter((d) => d.channel && d.draft)

  return {
    drafts,
    artifacts: result.artifacts, // 步骤截图,随草稿归档到你的内容库
    policyVersion: policy.version,
    audit: { auditId: grant.auditId, permissionBoundary: grant.grantedScopes },
  }
}

输出结构由任务描述约定:这里约定返回 { channel, draft, notes } 数组,应用侧 parseDrafts 负责解析与校验,缺少 channeldraft 的条目被直接丢弃。SDK 顶层只返回通用的 RunResultrunIdstatusoutputartifacts 等)。

数据与记忆边界

这个场景涉及四类数据,只有最后一类属于 GUMem:

  • 版本化规则:品牌 voice、平台规则、禁用表达——放在规则库(policy store)里按版本管理,草稿输出引用规则版本号。
  • 业务状态:草稿、采纳与否决结果、历史帖子表现数据——归档到你的内容库和 analytics 存储,供复盘与追溯。
  • 审计记录:grantIdauditId 构成的委托与行为链——由 GenAuth 维护。
  • 用户 Memory(可选):仅保存用户明确确认的采纳、修改和否决结果沉淀出的长期语气偏好——这才是 GUMem 的位置;单次建稿默认不召回也不写回。

失败处理

情况推荐处理
登录态失效任务挂起,通知用户重新登录,从断点继续。
后台改版导致建稿失败按失败处理并回放会话记录,没有证据证明写入成功就不算成功。
委托范围外的发布或互动请求直接拒绝并记录,事后可在审计链中查到未遂动作。
输出草稿缺少渠道标注或含禁用表达应用侧校验直接剔除该条草稿,并在交付物中说明剔除原因和依据的规则版本。

生产注意点

默认禁止自动发布、自动评论、自动私信或批量互动。草稿必须经过用户确认。对同一平台的建稿和读取频率应设置上限,避免触发平台风控;遇到风控或异常验证时升级给人处理,不尝试绕过。品牌规则更新后应在规则库中发布新版本,避免旧规则继续约束新草稿。

下一步