登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

多 Agent 移交怎样保留任务上下文

来源:17golang原创

时间:2026-10-17 23:37:39 119浏览 收藏

多 Agent 移交时,最容易犯的错是把上一位 Agent 的完整消息历史原样塞给下一位。这样看似没有丢上下文,实际却把已经过期的猜测、工具回执和无关对话一起传了过去。更稳妥的做法是把“任务连续性”和“对话原文”拆开:用稳定的上下文标识关联任务,用结构化移交信封描述当前事实,再按优先级裁剪历史。

官方资料入口:https://huggingface.co/docs

A2A 规范:https://github.com/a2aproject/A2A/blob/main/docs/specification.md

LangGraph 持久化说明:https://langchain-ai.github.io/langgraph/concepts/durable_execution/

一句话结论:移交时至少保留任务目标、硬约束、已确认决定、当前产物引用、待解决问题和上下文版本;原始消息只作为可选追溯材料,不应该成为唯一状态来源。

先判断:丢的是哪一类上下文

我在设计多 Agent 流程时,第一次遇到“接手 Agent 重复询问”并不是模型能力问题,而是移交包只有一串消息。接手方无法判断哪些内容已经确认,哪些只是上一个 Agent 的临时猜测,也不知道当前应该修改哪个产物。

可以先把状态分成三层:

  • 连续性层:用 context_id 关联同一组任务,用 task_id 标识某一次具体工作。
  • 执行层:记录目标、约束、已确认决定、待办、预算和当前负责人。
  • 产物层:记录文件、结构化结果或工具输出的版本、摘要和引用关系。

A2A 的模型也把 contextId、taskId、Message 和 Artifact 分开:上下文负责把相关交互归组,任务负责描述有生命周期的工作,消息负责沟通,产物负责交付结果。这个分层比“所有东西都放进 history”更适合跨 Agent 移交。

多 Agent 移交信封中的上下文标识、任务状态、执行字段和产物引用关系说明图
图1:多 Agent 移交信封的静态结构图,说明上下文标识、执行状态与产物引用的边界;这是说明图,不是运行截图。

把移交内容固定成一个可序列化信封

移交协议不必一开始就做成复杂标准,但字段名称和含义要稳定。下面这个最小信封只解决“接手方下一步做什么”这一件事:

{
  "schema_version": 2,
  "context_id": "ctx_01J9",
  "task_id": "task_104",
  "handoff_id": "handoff_07",
  "goal": "把客户问题整理成可发布的技术答复",
  "constraints": [
    "只使用已确认的官方资料",
    "不重复调用已经成功的外部工具"
  ],
  "decisions": [
    "保留当前答案结构",
    "把未确认的版本号标为待核实"
  ],
  "open_questions": ["是否需要补充兼容性说明"],
  "artifacts": [
    {"name": "draft", "uri": "store://answer/104", "version": 3}
  ],
  "budget": {"max_tool_calls": 4, "max_tokens": 6000},
  "owner": "review_agent",
  "status": "ready"
}

这个 JSON 是数据格式,不能在其中插入注释;字段含义应放在协议文档或相邻说明中。实际实现时,schema_version 用于兼容演进,handoff_id 用于幂等,artifacts 只保存引用和版本,不把大段正文重复嵌入每一次移交。

让接手方只拿到“够用”的上下文

上下文裁剪不是简单截取最后几条消息。更可靠的顺序是:先保留硬约束和已确认决定,再保留当前产物和未解决问题,最后才考虑历史对话。低优先级消息可以压缩成带来源的摘要,不能把猜测摘要成确定事实。

from dataclasses import dataclass
from typing import Any

@dataclass
class HandoffContext:
    # 这些字段是接手 Agent 开始工作所必需的最小状态
    goal: str
    constraints: list[str]
    decisions: list[str]
    open_questions: list[str]
    artifacts: list[dict[str, Any]]
    history_summary: str = ""

def build_context(state: dict[str, Any], summary: str) -> HandoffContext:
    # 未确认的推测不进入 decisions,避免接手方把猜测当成事实
    return HandoffContext(
        goal=state["goal"],
        constraints=list(state.get("constraints", [])),
        decisions=list(state.get("confirmed_decisions", [])),
        open_questions=list(state.get("open_questions", [])),
        artifacts=list(state.get("artifacts", [])),
        history_summary=summary,
    )

裁剪后要检查三件事:接手方是否知道成功条件,是否知道不能做什么,是否能定位要继续处理的产物。如果缺少其中一项,宁可缩小任务边界,也不要只靠更多历史消息来弥补。

区分 contextId、taskId 与产物版本

跨任务连续性最怕三个标识混用。可以把它们理解成“会话文件夹、当前工单、文件版本”:同一个 context_id 下可以有多个 task_id;一个任务结束后,后续修订应该新建任务,但仍然关联同一上下文;产物则用自己的版本号或内容摘要追踪。

LangGraph 的持久化文档也强调了短期 checkpoint 与跨线程 store 的差异:前者保存当前线程的状态快照,后者保存应用定义的长期键值数据。移交时不要把长期知识库当成当前任务状态,也不要把一次性工具回执写成永久记忆。

对象应该保存什么不应该承担什么
context_id相关任务和消息的归组关系不直接等同于当前任务状态
task_id本次工作的状态、负责人和生命周期不在终态后原地重启
artifact_version当前可接受产物及其来源不替代任务状态
history_summary必要的决策背景和证据索引不把未确认推测写成结论

失败时先回滚移交版本,再决定是否重试

移交失败通常有两种:接手 Agent 没有确认收到,或者已经收到但执行副作用失败。两种情况的处理不同。前者只重试同一个 handoff_id;后者应创建新的任务版本,引用上一个已确认产物,避免重复发送邮件、写库或提交发布。

def accept_handoff(store, envelope: dict) -> str:
    # 幂等键让网络重试不会再次创建同一份移交任务
    key = (envelope["context_id"], envelope["handoff_id"])
    old = store.get_ack(key)
    if old:
        return old

    # 只接受比当前状态更新且能找到产物版本的信封
    if envelope["status"] != "ready":
        raise ValueError("移交状态不是 ready")
    for artifact in envelope.get("artifacts", []):
        if not store.has_version(artifact["uri"], artifact["version"]):
            raise ValueError("产物版本不存在")

    ack_id = store.save_ack(key, status="accepted")
    return ack_id

回滚时只回到最近一次 accepted 或 committed 的版本,不回到“最后一条消息”。如果外部副作用已经发生,状态里应明确写入结果和幂等键,再由补偿任务处理;不能靠再次移交来猜测外部系统是否成功。

多 Agent 上下文裁剪与失败恢复中的保留字段、摘要字段、产物版本和回滚边界说明图
图2:上下文裁剪与恢复的静态关系图,展示保留字段、历史摘要、产物版本和回滚边界;这是说明图,不是运行截图。

按运行手册观察移交是否变差

上线后不需要先做复杂评测,先把几项可观察信号记下来:

  1. 触发信号:接手方重复提问、重复调用工具、产物版本倒退,或在同一上下文里出现两个负责人。
  2. 快速判断:查看 context_id、task_id、handoff_id 是否连续,确认 ack 是否已经落库。
  3. 处理动作:对比移交前后的 schema_version、保留字段和产物版本,优先修复字段丢失,不要先增加上下文长度。
  4. 回滚路径:恢复最近一次已确认的产物版本,标记当前任务为需要人工输入或重新规划。
  5. 复盘记录:记录移交包大小、摘要比例、ack 延迟、重试次数、重复副作用和被裁掉的字段类别。

对我来说,最实用的验收标准不是“下一位 Agent 看到了多少历史”,而是“它能否在不追问已确认事实的情况下完成下一步,并且失败后能定位到一个明确版本”。只要这个标准满足,短上下文往往比全量历史更稳定。

几个容易混淆的问题

是不是把所有消息都保存下来才安全?

可以保存原始消息用于审计,但不要把它当成唯一执行状态。执行字段应结构化、可版本化,历史消息只提供背景和追溯。

接手 Agent 是否必须收到完整的 contextId 历史?

不必须。它至少需要当前移交信封和能读取产物版本的引用;是否加载更早历史,应由当前任务的缺口决定。

什么时候应该新建 taskId?

当上一项任务已经进入终态,或者新的请求会产生独立交付物、独立副作用时,新建 taskId 并复用 context_id。不要在终态任务上原地覆盖状态。

怎样防止摘要把猜测变成事实?

把“已确认决定”和“待验证假设”放在不同字段,摘要中保留来源引用和更新时间;没有证据的内容只能进入 open_questions。

多 Agent 移交的核心不是传递更多文本,而是传递一份可执行的状态快照:它有稳定标识、有明确边界、有产物版本,也允许在失败时回滚。先把这四点做稳,再考虑更长的历史、更复杂的记忆和更自动化的编排。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>