一个散步助手可以记住“想去河边”“回程买面包”,但“记住偏好”和“代替执行”之间,隔着一条很实在的线。尤其当 Agent 能调用浏览器或商店接口时,一条随口记下的愿望,不该直接变成订单。
把动作写成状态,而不是一句自然语言
我会把清单分成建议、待确认、已完成三栏。加入清单只产生建议;涉及购买的项目必须由用户显式确认,才能进入待执行状态。完成又是另一个动作,不能因为确认过就自动打勾。普通散步地点也保留“未执行”的状态,免得下一次打开时,系统编出一次不存在的行程。
10 月 22 日移交给 AAIF 的 Agent Skills强调可复用工作流。我的理解是:工作流越容易复用,越要把确认点放在状态转换里。仅在提示词末尾写“请谨慎”,既不能留下明确记录,也难以阻止代码把建议直接当命令。
记忆要有来源和删除口
下面的纯函数不发送请求、不定位,也不下单。每条项目保留创建来源,购买确认必须传入一条与当前项目 ID 对应的确认记录。示例中的记录仍由调用方构造,生产系统必须由可信的用户交互产生,不能相信模型自己给出的approved:true。
保存清单时,还应区分一次性任务与长期偏好。一次买过面包,不等于以后每次都要买;删除一条记录,也应该能删除它对推荐的影响。这里没有做跨设备同步或账号身份验证,本地示例只演示最容易被省略的状态约束。
执行node walk.js会验证购买未确认时被拒绝、确认不能串用、已完成项目不重复执行。它不会核实真实路线、安全状况或商店营业时间,这些需要实际服务与当下信息。一个值得信任的散步助手,应该让人走得轻松,同时把决定权留在人手里。
可独立运行的核心例子
function create(id,title,kind='walk') { if(!id || !title.trim() || !['walk','purchase'].includes(kind)) throw Error('无效项目'); return {id,title,kind,state:'suggested',source:'user-entered'}; } function confirm(item,event) { if(item.state!=='suggested') throw Error('状态不允许确认'); if(event?.itemId!==item.id || event?.actor!=='user') throw Error('缺少本项目确认'); return {...item,state:'ready',confirmationId:event.id}; } function complete(item) { if(item.state!=='ready') throw Error('尚未确认或已经完成'); return {...item,state:'done'}; } function mustReject(fn) {let ok=false;try{fn();}catch{ok=true;}if(!ok)throw Error('应拒绝');} const bread=create('bread','回程买面包','purchase'); mustReject(()=>complete(bread)); mustReject(()=>confirm(bread,{id:'e0',actor:'user',itemId:'other'})); const ready=confirm(bread,{id:'e1',actor:'user',itemId:'bread'}); const done=complete(ready); mustReject(()=>complete(done)); if(bread.state!=='suggested' || done.state!=='done') throw Error('状态污染'); console.log(JSON.stringify({before:bread,after:done},null,2));