三个 Agent 都报告成功,但订单字段在 handoff 环节丢失——时间戳无法证明因果关系,需要跨 Run 的身份标识来追踪完整链路。
我的分类 Agent 提取了一条退款请求及其订单引用。
退款专员说引用缺失。
升级 Agent 随后开了一条通用支持工单。
三个运行都成功完成了。
单独看,每条轨迹都讲了一个可信的故事。放在一起看,它们相互矛盾。问题出在那些绿色运行之间的交接处。
这就是多 Agent 调试的棘手之处:一次交接可能丢失那个关键字段,而所有组件都报告成功。
诱人的调查思路是:
找到分类轨迹;
搜索几毫秒后的专员轨迹;
假设下一次升级属于同一请求。
这在只有一个用户的演示中有效。在队列、重试、并行会话、工作并发和时钟差异下就失效了。
时间邻近性有助于发现,但它是脆弱的因果证据。
10:00:00.100 triage completed
10:00:00.102 triage completed
10:00:00.110 specialist started
10:00:00.111 specialist started
Which triage run produced which specialist run?
The timestamps cannot answer.
这种关系需要一个能跨越边界的身份。
对于跨运行的workflow,我需要四个小的元数据片段:
sessionId:用户旅程或workflow实例;
workflowName:可复用的workflow类型;
handoffFrom 和 handoffTo:声明的边;以及
retryOf 加 attempt,当一个运行是重试时。
以下是一个经过 agent-inspect@6.17.6 验证的合成 TypeScript 示例:
import { inspectRun, step } from "agent-inspect";
const traceDir = ".agent-inspect";
const sessionId = "sess-support-042";
const triage = await inspectRun(
"triage-agent",
async () => {
return step("extract-request", async () => ({
category: "refund",
orderRef: "internal-value",
}));
},
{
traceDir,
metadata: {
sessionId,
workflowName: "support-refund",
handoffFrom: "triage-agent",
handoffTo: "refund-specialist",
},
},
);
// The bug: orderRef disappears while building the handoff payload.
const specialistInput = { category: triage.category };
const specialist = await inspectRun(
"refund-specialist",
async () => {
return step(
"read-handoff",
async () => ({
needsEscalation: !("orderRef" in specialistInput),
}),
{
metadata: {
receivedFields: Object.keys(specialistInput),
orderRefPresent: "orderRef" in specialistInput,
},
},
);
},
{
traceDir,
metadata: {
sessionId,
workflowName: "support-refund",
handoffFrom: "refund-specialist",
handoffTo: "escalation-agent",
},
},
);
if (specialist.needsEscalation) {
await inspectRun(
"escalation-agent",
async () => {
await step("open-ticket", async () => undefined);
},
{
traceDir,
metadata: { sessionId, workflowName: "support-refund" },
},
);
}
注意轨迹没有存储什么:订单引用本身。字段名和存在位足以定位这个破损的边界。
列出分组的workflow活动:
npx agent-inspect sessions --dir .agent-inspect
然后检查声明的交接和每个运行的时序:
npx agent-inspect session sess-support-042 \
--dir .agent-inspect \
--timeline \
--diagnostics
会话视图首先给调查一个有界的形状:
session: sess-support-042
triage-agent
handoff: triage-agent -> refund-specialist
|
v
refund-specialist
handoff: refund-specialist -> escalation-agent
|
v
escalation-agent
three runs: completed
然后检查专员运行的有界步骤元数据:
npx agent-inspect view <refund-specialist-run-id> \
--dir .agent-inspect \
--verbose
read-handoff
receivedFields: category
orderRefPresent: false
升级不是原始的失败。当 specialistInput 被构造时,字段就消失了。
事后听起来显而易见。在运行被关联之前,很容易因为它产生了可见的症状而责怪最后一个 Agent。
显式字段改进了不仅仅是轨迹查看器。它们在代码审查中暴露了预期的拓扑。
AgentInspect 拒绝仅从时间戳制造因果链接。如果 attempt 大于 1 而没有 retryOf,关系可以标记为关联而非显式,并附上解释歧义的诊断。
这种区分很重要。一张有捏造边的令人信服的图,不如一张承认不确定性的不完整图。
可观测性解释了什么被执行了。它不应该是第一个发现缺失必填字段的地方。
在发送前和接收后添加运行时验证:
type RefundHandoff = {
category: "refund";
orderRef: string;
};
function assertRefundHandoff(
value: Record<string, unknown>,
): asserts value is RefundHandoff {
if (value.category !== "refund") {
throw new Error("Invalid refund category");
}
if (typeof value.orderRef !== "string" || value.orderRef.length === 0) {
throw new Error("Missing orderRef at refund handoff");
}
}
schema 检查阻止了坏的交接。会话轨迹显示当行为仍然令人惊讶时,哪个边界和版本实际被执行了。这些是互补的工作。
多 Agent workflow会演进。今天的分类 Agent 可能发出 orderRef,而新版专员期望 orderId。即使两个字段都是字符串,那也是协议变更。
向步骤或运行元数据添加一个有界的契约标识符:
metadata: {
sessionId,
workflowName: "support-refund",
handoffFrom: "triage-agent",
handoffTo: "refund-specialist",
handoffContract: "refund-request/v2",
receivedFields: ["category", "orderRef"],
}
这个标识符让审查者能够问:生产者和消费者是否使用了相同的边界定义,而无需存储业务值本身。它还分离了两类否则看起来相同的失败:
missing field
├─ producer never emitted it
├─ transport dropped it
├─ mapper renamed it
└─ consumer expected another contract version
AgentInspect 将这些值视为元数据;它不强制执行 refund-request/v2。你的 schema 验证器或消息层仍然拥有那个保证。
会话索引不是 workflow引擎。它不传递消息、不保证字段消费、也不验证每个交接 schema。当前的 handoff 相关 TraceContract 支持不是完整的策略语言。
会话标识符也可能成为敏感的关联数据。使用随机标识符而不是业务值,保持元数据有界,在向团队外部共享轨迹之前应用适当的脱敏策略。
pinned sessions and outcomes 文档描述了当前的边界。
对于多 Agent 系统,从以下问题开始:
在哪个边界上,状态不再匹配 workflow的承诺?
一个显式会话 ID、两个交接端点,以及几个安全的字段存在检查,可以将三条绿色轨迹变成一个可解释的失败。
你的多 Agent 系统在哪里验证交接:发送前、接收后,还是两者都有?