给桌面端 AI 任务加队列时,我会先写清幂等键和确认点
最近给一个桌面端 AI 助手补自动化能力时,我发现最容易出事故的地方不在模型回答,也不在单个工具函数,而在“用户点了一次按钮之后,到底产生了几次真实动作”。桌面端用户习惯随手关闭窗口、切换账号、重复点击,AI 任务又经常跨文件、网络请求和本地索引。只要一次任务被重复投递,轻则多跑一遍向量化,重则重复写文件或发出外部请求。于是我把这类长任务从按钮回调里挪出来,先做成一条可恢复的任务队列。

原创示意图:从用户意图、幂等键、任务队列、worker 执行、人工确认到重启恢复的完整链路。 来源:Codex image generation
问题背景
Electron 官方文档把 IPC 视为构建桌面应用的关键部分,因为主进程和渲染进程职责不同,常见做法是通过开发者定义的 channel 在 ipcMain 和 ipcRenderer 之间传递消息。这个边界提醒我,渲染进程适合表达用户意图,主进程才应该拥有文件系统、队列、网络和后台 worker 的控制权。
早期实现里,渲染进程调用 runAgentTask() 后直接等待结果。任务短时还能接受,一旦要读多个文件、检索知识库、调用模型再写回工作区,窗口刷新或应用退出都会留下半截状态。更麻烦的是用户看不到后台是否已经接单,第二次点击会生成一条几乎相同的任务。
关键难点
第一个难点是幂等。BullMQ 的 Idempotent jobs 文档强调,任务应按失败和重试来设计,首次成功和失败后重试成功应落到相同最终状态,并且任务最好保持原子和简单。放到 AI 助手里,幂等键不能只用时间戳,要能表达业务意图。我通常用 workspace_id、actor_id、intent_type、normalized_target 和 payload_hash 拼出 key,再把同 key 的请求合并到已有任务状态。
第二个难点是确认点。LangGraph 持久化文档提到,检查点能保存每一步状态,支持 human in the loop、回放调试和故障恢复。桌面端也需要类似能力,尤其是任务即将写文件、发送消息或改配置前,必须把“等待确认”落盘。只有确认记录存在,worker 才能继续执行副作用步骤。
第三个难点是失败分类。Temporal 的 TypeScript 文档里有 ApplicationFailure 和 nonRetryable 的处理方式,这让我把错误分成两类:网络抖动、限流和临时锁可以重试,输入无效、权限不足和用户取消应直接进入待处理状态,等待用户修改后再恢复。
解决思路
我给每次桌面端任务套了一层请求信封,字段包括 task_id、idempotency_key、source_window_id、intent_type、payload_hash、created_at 和 approval_required。渲染进程只通过 preload 暴露的窄 API 投递信封,主进程负责校验、去重和落盘。如果同一个幂等键已经存在,IPC 返回现有任务的状态快照,界面切到进度视图,而无需新建任务。
队列本身采用状态机思路:queued、running、waiting_approval、retrying、failed、completed。每次状态变化都写一条轻量事件,同时更新任务表的当前状态。worker 执行前先读取最新快照,执行后再写结果摘要。遇到确认点时,worker 只写入待确认内容和风险说明,然后暂停。用户在界面里确认后,主进程追加一条 approval 事件,worker 才继续。
关键步骤
落地时我先改 IPC 边界。preload 只暴露 submitTask、getTask、watchTask 三个入口,避免把通用 ipcRenderer 暴露给页面。主进程拿到请求后先标准化目标路径、模型参数和工具集合,再计算 payload_hash。这一步越早做,后面的去重越稳定。
接着补本地持久化。桌面端不一定需要远端队列,SQLite 或嵌入式 KV 已经能覆盖单机任务恢复。关键是副作用步骤前后都要有记录:准备写文件前写 waiting_approval,真正写入后写 effect_committed,失败时写错误类型和下一次允许重试时间。这样应用重启后可以从最后一个稳定状态继续。
最后把 UI 从“等待 Promise”改成“订阅任务状态”。按钮点击后立即拿到 task_id,界面展示排队、执行、确认和完成状态。用户重复点击时,看到的是同一个任务的当前进度。这个体验变化很小,但后台行为从一次临时调用变成了可查询、可恢复、可审计的工作流。
可复用经验
桌面端 AI 自动化最好把三件事拆开:用户意图、队列任务、真实副作用。用户意图可以重复提交,队列任务要靠幂等键合并,真实副作用只能在确认点之后执行。只要这条边界清楚,后续接 BullMQ、LangGraph 或自研 worker 都会顺很多。
我的经验是,别等任务失败后再补恢复能力。先把幂等键、状态机、确认点和错误分类写清楚,哪怕第一版 worker 只有一个本地进程,也能让 Agent 工作流从“按钮触发一段异步代码”升级成可维护的桌面端自动化能力。