定义成果目标

告诉 Agent "完成"的样子,让它不断迭代直到达到目标。


outcome(成果目标)将会话从对话提升为工作。你定义最终结果应该是什么样子以及如何衡量质量。Agent 朝着该目标工作,自我评估并迭代直到满足成果目标。

当你定义成果目标时,系统会自动配置一个评分器来根据评分标准评估产出物。它利用独立的上下文窗口,以避免受到主 Agent 实现选择的影响。

评分器会返回按标准分解的结果:确认产出物满足评分标准,或者指出当前工作与需求之间的具体差距。该反馈会被传递回 Agent 用于下一次迭代。

Note

所有 Managed Agents API 请求都需要 managed-agents-2026-04-01 beta header。SDK 会自动设置该 beta header。

创建评分标准

评分标准是一个描述按标准评分的 markdown 文档。评分标准是必需的。

编写有效评分标准的技巧

将评分标准构建为明确的、可评分的标准,例如"CSV 包含一个带有数值的价格列"而不是"数据看起来不错"。评分器独立对每个标准评分,因此模糊的标准会产生嘈杂的评估结果。

如果你手头没有评分标准,可以尝试给 Claude 一个已知优质产出物的示例,让它分析为什么该内容是好的,然后将分析转化为评分标准。这种折中方法通常比从零开始编写标准产生更好的结果。

评分标准示例:

# DCF 模型评分标准

## 收入预测
- 使用过去 5 个财年的历史收入数据
- 预测至少 5 年的收入
- 增长率假设明确说明且合理

## 成本结构
- 销货成本和运营费用分开建模
- 利润率与历史趋势一致,或偏差有合理说明

## 折现率
- WACC 使用已说明的股权成本和债务成本假设进行计算
- Beta、无风险利率和股权风险溢价有来源或合理说明

## 终值
- 使用永续增长法或退出倍数法(说明使用了哪种)
- 终值增长率不超过长期 GDP 增长率

## 输出质量
- 所有数据在单个 .xlsx 文件中,工作表标签清晰
- 关键假设在单独的"假设"工作表上
- 包含 WACC 和终值增长率的敏感性分析

将评分标准作为 user.define_outcome 上的内联文本传递(参见下一节),或通过 Files API 上传以便在会话间复用:

需要 beta header files-api-2025-04-14

rubric=$(curl -fsSL https://api.anthropic.com/v1/files \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01,files-api-2025-04-14" \
  -F file=@/tmp/rubric.md)
rubric_id=$(jq -r '.id' <<<"$rubric")
printf 'Uploaded rubric: %s\n' "$rubric_id"
RUBRIC_ID=$(ant beta:files upload \
  --file /tmp/rubric.md \
  --transform id --raw-output)
rubric = client.beta.files.upload(file=Path("/tmp/rubric.md"))
print(f"Uploaded rubric: {rubric.id}")
const rubric = await client.beta.files.upload({
  file: await toFile(readFile("/tmp/rubric.md"), "/tmp/rubric.md"),
});
console.log(`Uploaded rubric: ${rubric.id}`);
var rubric = await client.Beta.Files.Upload(new()
{
    File = File.OpenRead("/tmp/rubric.md"),
});
Console.WriteLine({{CONTENT}}quot;Uploaded rubric: {rubric.ID}");
f, err := os.Open("/tmp/rubric.md")
if err != nil {
	panic(err)
}

uploaded, err := client.Beta.Files.Upload(ctx, anthropic.BetaFileUploadParams{
	File: anthropic.File(f, "/tmp/rubric.md", "text/markdown"),
})
if err != nil {
	panic(err)
}
fmt.Printf("Uploaded rubric: %s\n", uploaded.ID)
var rubric = client.beta().files().upload(
    FileUploadParams.builder()
        .file(Path.of("/tmp/rubric.md"))
        .build());
IO.println("Uploaded rubric: " + rubric.id());
$rubric = $client->beta->files->upload(
    file: fopen('/tmp/rubric.md', 'r'),
);
echo "Uploaded rubric: {$rubric->id}\n";
rubric = client.beta.files.upload(file: Pathname.new("/tmp/rubric.md"))
puts "Uploaded rubric: #{rubric.id}"

创建带有成果目标的会话

创建会话后,发送 user.define_outcome 事件。Agent 会立即开始工作;不需要额外的 user.message 事件。

# Create a session
session=$(curl -fsSL https://api.anthropic.com/v1/sessions \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  --json @- <<EOF
{
  "agent": "$agent_id",
  "environment_id": "$environment_id",
  "title": "Financial analysis on Costco"
}
EOF
)
session_id=$(jq -r '.id' <<<"$session")

# Define the outcome — agent starts working on receipt
curl -fsSL "https://api.anthropic.com/v1/sessions/$session_id/events" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01" \
  --json @- >/dev/null <<EOF
{
  "events": [
    {
      "type": "user.define_outcome",
      "description": "Build a DCF model for Costco in .xlsx",
      "rubric": {"type": "text", "content": "# DCF Model Rubric\n..."},
      "max_iterations": 5
    }
  ]
}
EOF
# or: "rubric": {"type": "file", "file_id": "$rubric_id"}
# "max_iterations" is optional; default 3, max 20
# Create a session
SESSION_ID=$(ant beta:sessions create \
  --agent "$AGENT_ID" \
  --environment-id "$ENVIRONMENT_ID" \
  --title "Financial analysis on Costco" \
  --transform id --raw-output)

# Define the outcome — agent starts working on receipt
ant beta:sessions:events send \
  --session-id "$SESSION_ID" <<YAML
events:
  - type: user.define_outcome
    description: Build a DCF model for Costco in .xlsx
    rubric: {type: file, file_id: $RUBRIC_ID}
    # or: rubric: {type: text, content: "..."}
    max_iterations: 5  # optional; default 3, max 20
YAML
# Create a session
session = client.beta.sessions.create(
    agent=agent.id,
    environment_id=environment.id,
    title="Financial analysis on Costco",
)

# Define the outcome — agent starts working on receipt
client.beta.sessions.events.send(
    session_id=session.id,
    events=[
        {
            "type": "user.define_outcome",
            "description": "Build a DCF model for Costco in .xlsx",
            "rubric": {"type": "text", "content": RUBRIC},
            # or: "rubric": {"type": "file", "file_id": rubric.id},
            "max_iterations": 5,  # optional; default 3, max 20
        }
    ],
)
// Create a session
const session = await client.beta.sessions.create({
  agent: agent.id,
  environment_id: environment.id,
  title: "Financial analysis on Costco",
});

// Define the outcome — agent starts working on receipt
await client.beta.sessions.events.send(session.id, {
  events: [
    {
      type: "user.define_outcome",
      description: "Build a DCF model for Costco in .xlsx",
      rubric: { type: "text", content: RUBRIC },
      // or: rubric: { type: "file", file_id: rubric.id },
      max_iterations: 5, // optional; default 3, max 20
    },
  ],
});
// Create a session
var session = await client.Beta.Sessions.Create(new()
{
    Agent = agent.ID,
    EnvironmentID = environment.ID,
    Title = "Financial analysis on Costco",
});

// Define the outcome — agent starts working on receipt
await client.Beta.Sessions.Events.Send(session.ID, new()
{
    Events =
    [
        new BetaManagedAgentsUserDefineOutcomeEventParams
        {
            Type = "user.define_outcome",
            Description = "Build a DCF model for Costco in .xlsx",
            Rubric = new BetaManagedAgentsTextRubricParams { Type = "text", Content = Rubric },
            // or: Rubric = new BetaManagedAgentsFileRubricParams { Type = "file", FileID = rubric.ID },
            MaxIterations = 5, // optional; default 3, max 20
        },
    ],
});
// Create a session
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
	Agent: anthropic.BetaSessionNewParamsAgentUnion{
		OfString: anthropic.String(agent.ID),
	},
	EnvironmentID: environment.ID,
	Title:         anthropic.String("Financial analysis on Costco"),
})
if err != nil {
	panic(err)
}

// Define the outcome — agent starts working on receipt
_, err = client.Beta.Sessions.Events.Send(ctx, session.ID, anthropic.BetaSessionEventSendParams{
	Events: []anthropic.BetaManagedAgentsEventParamsUnion{{
		OfUserDefineOutcome: &anthropic.BetaManagedAgentsUserDefineOutcomeEventParams{
			Description: "Build a DCF model for Costco in .xlsx",
			Rubric: anthropic.BetaManagedAgentsUserDefineOutcomeEventParamsRubricUnion{
				OfText: &anthropic.BetaManagedAgentsTextRubricParams{Content: rubric},
			},
			// or: OfFile: &anthropic.BetaManagedAgentsFileRubricParams{FileID: uploaded.ID},
			MaxIterations: anthropic.Int(5), // optional; default 3, max 20
		},
	}},
})
if err != nil {
	panic(err)
}
// Create a session
var session = client.beta().sessions().create(
    SessionCreateParams.builder()
        .agent(agent.id())
        .environmentId(environment.id())
        .title("Financial analysis on Costco")
        .build());

// Define the outcome — agent starts working on receipt
client.beta().sessions().events().send(
    session.id(),
    EventSendParams.builder()
        .addEvent(BetaManagedAgentsUserDefineOutcomeEventParams.builder()
            .description("Build a DCF model for Costco in .xlsx")
            .rubric(BetaManagedAgentsTextRubricParams.builder().content(RUBRIC).build())
            // or: .rubric(BetaManagedAgentsFileRubricParams.builder().fileId(rubric.id()).build())
            .maxIterations(5) // optional; default 3, max 20
            .build())
        .build());
// Create a session
$session = $client->beta->sessions->create(
    agent: $agent->id,
    environmentID: $environment->id,
    title: 'Financial analysis on Costco',
);

// Define the outcome — agent starts working on receipt
$client->beta->sessions->events->send(
    $session->id,
    events: [
        [
            'type' => 'user.define_outcome',
            'description' => 'Build a DCF model for Costco in .xlsx',
            'rubric' => ['type' => 'text', 'content' => $rubricText],
            // or: 'rubric' => ['type' => 'file', 'file_id' => $rubric->id],
            'max_iterations' => 5, // optional; default 3, max 20
        ],
    ],
);
# Create a session
session = client.beta.sessions.create(
  agent: agent.id,
  environment_id: environment.id,
  title: "Financial analysis on Costco"
)

# Define the outcome — agent starts working on receipt
client.beta.sessions.events.send_(
  session.id,
  events: [
    {
      type: "user.define_outcome",
      description: "Build a DCF model for Costco in .xlsx",
      rubric: {type: "text", content: RUBRIC},
      # or: rubric: {type: "file", file_id: rubric.id},
      max_iterations: 5 # optional; default 3, max 20
    }
  ]
)

成果目标事件

以成果目标为导向的会话进度通过事件呈现。

  • agent.* 事件(消息、工具使用等)显示朝向成果目标的进度。
  • span.outcome_evaluation_* 事件仅在以成果目标为导向的会话中发出,显示迭代循环次数和评分器的反馈过程。
  • 你也可以向以成果目标为导向的会话发送 user.message 事件,在工作进展中指导 Agent,但这不是必需的;Agent 知道要工作直到用完迭代次数或达成成果目标。
  • user.interrupt 事件会暂停当前成果目标的工作,并将 span.outcome_evaluation_end.result 标记为 interrupted,允许你启动新的成果目标。
  • 最终成果目标评估后,会话可以作为对话式会话继续,或者可以启动新的成果目标。会话将保留之前成果目标的历史记录。

定义成果目标用户事件

Note

一次只支持一个成果目标,但你可以按顺序链接多个成果目标。为此,在上一个成果目标的终止事件之后发送新的 user.define_outcome 事件。

这是你发送以启动成果目标的事件。它会在收到时被回显,包含 processed_at 时间戳和 outcome_id

{
  "type": "user.define_outcome",
  "description": "Build a DCF model for Costco in .xlsx",
  "rubric": { "type": "file", "file_id": "file_01..." },
  "max_iterations": 5
}

成果目标评估开始

当评分器开始一次迭代循环的评估时发出。iteration 字段是从 0 开始的修订计数器:0 是第一次评估,1 是第一次修订后的重新评估,依此类推。

{
  "type": "span.outcome_evaluation_start",
  "id": "sevt_01def...",
  "outcome_id": "outc_01a...",
  "iteration": 0,
  "processed_at": "2026-03-25T14:01:45Z"
}

成果目标评估进行中

评分器运行时发出的心跳。评分器的内部推理是不透明的:你能看到它在工作,但不知道它在想什么。

{
  "type": "span.outcome_evaluation_ongoing",
  "id": "sevt_01ghi...",
  "outcome_id": "outc_01a...",
  "processed_at": "2026-03-25T14:02:10Z"
}

成果目标评估结束

评分器完成一次迭代评估后发出。result 字段指示接下来会发生什么。

结果下一步
satisfied会话转换为 idle
needs_revisionAgent 开始新的迭代周期。
max_iterations_reached不再有进一步的评估周期。Agent 可能会在会话转换为 idle 之前执行一次最终修订。
failed会话转换为 idle。当评分标准从根本上与任务不匹配时返回,例如描述和评分标准相互矛盾。
interrupted仅在中断前已触发 outcome_evaluation_start 时发出。
{
  "type": "span.outcome_evaluation_end",
  "id": "sevt_01jkl...",
  "outcome_evaluation_start_id": "sevt_01def...",
  "outcome_id": "outc_01a...",
  "result": "satisfied",
  "explanation": "All 12 criteria met: revenue projections use 5 years of historical data, WACC assumptions are stated, sensitivity table is included...",
  "iteration": 0,
  "usage": {
    "input_tokens": 2400,
    "output_tokens": 350,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 1800
  },
  "processed_at": "2026-03-25T14:03:00Z"
}

检查成果目标状态

你可以监听事件流中的 span.outcome_evaluation_end,或者轮询 GET /v1/sessions/:id 并读取 outcome_evaluations[].result

session=$(curl -fsSL "https://api.anthropic.com/v1/sessions/$session_id" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01")

jq -r '.outcome_evaluations[] | "\(.outcome_id): \(.result)"' <<<"$session"
# outc_01a...: satisfied
ant beta:sessions retrieve --session-id "$SESSION_ID" \
  --transform 'outcome_evaluations' --format yaml
session = client.beta.sessions.retrieve(session.id)

for outcome in session.outcome_evaluations:
    print(f"{outcome.outcome_id}: {outcome.result}")
    # outc_01a...: satisfied
const retrieved = await client.beta.sessions.retrieve(session.id);

for (const outcome of retrieved.outcome_evaluations) {
  console.log(`${outcome.outcome_id}: ${outcome.result}`);
  // outc_01a...: satisfied
}
session = await client.Beta.Sessions.Retrieve(session.ID);

foreach (var outcome in session.OutcomeEvaluations)
{
    Console.WriteLine({{CONTENT}}quot;{outcome.OutcomeID}: {outcome.Result}");
    // outc_01a...: satisfied
}
session, err = client.Beta.Sessions.Get(ctx, session.ID, anthropic.BetaSessionGetParams{})
if err != nil {
	panic(err)
}

for _, outcome := range session.OutcomeEvaluations {
	fmt.Printf("%s: %s\n", outcome.OutcomeID, outcome.Result)
	// outc_01a...: satisfied
}
var retrieved = client.beta().sessions().retrieve(session.id());

for (var outcome : retrieved.outcomeEvaluations()) {
    IO.println(outcome.outcomeId() + ": " + outcome.result());
    // outc_01a...: satisfied
}
$session = $client->beta->sessions->retrieve($session->id);

foreach ($session->outcomeEvaluations as $outcome) {
    echo "{$outcome->outcomeID}: {$outcome->result}\n";
    // outc_01a...: satisfied
}
session = client.beta.sessions.retrieve(session.id)

session.outcome_evaluations.each do
  puts "#{it.outcome_id}: #{it.result}"
  # outc_01a...: satisfied
end

获取交付物

Agent 将输出文件写入容器内的 /mnt/session/outputs/。会话进入空闲状态后,通过 Files API 按会话范围获取它们:

# List files produced by this session
files=$(curl -fsSL "https://api.anthropic.com/v1/files?scope_id=$session_id" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: managed-agents-2026-04-01")
jq -r '.data[] | "\(.id) \(.filename)"' <<<"$files"

# Download a file
file_id=$(jq -r '.data[0].id // empty' <<<"$files")
if [[ -n $file_id ]]; then
  curl -fsSL "https://api.anthropic.com/v1/files/$file_id/content" \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: managed-agents-2026-04-01" \
    -o /tmp/output.txt
fi
# List files produced by this session
ant beta:files list --scope-id "$SESSION_ID"

# Download a file
FILE_ID=$(ant beta:files list --scope-id "$SESSION_ID" \
  --transform 'data[0].id' --raw-output)
if [[ -n $FILE_ID ]]; then
  ant beta:files download --file-id "$FILE_ID" --output /tmp/output.txt
fi
# List files produced by this session
files = client.beta.files.list(scope_id=session.id)
for f in files:
    print(f.id, f.filename)

# Download a file
if files.data:
    content = client.beta.files.download(files.data[0].id)
    content.write_to_file("/tmp/output.txt")
// List files produced by this session
const files = await client.beta.files.list({ scope_id: session.id });
for (const f of files.data) {
  console.log(f.id, f.filename);
}

// Download a file
if (files.data.length > 0) {
  const content = await client.beta.files.download(files.data[0].id);
  await writeFile("/tmp/output.txt", new Uint8Array(await content.arrayBuffer()));
}
// List files produced by this session
var files = await client.Beta.Files.List(new() { ScopeID = session.ID });
foreach (var file in files.Data)
{
    Console.WriteLine({{CONTENT}}quot;{file.ID} {file.Filename}");
}

// Download a file
if (files.Data.Count > 0)
{
    var content = await client.Beta.Files.Download(files.Data[0].ID);
    await File.WriteAllBytesAsync("/tmp/output.txt", content);
}
// List files produced by this session
files, err := client.Beta.Files.List(ctx, anthropic.BetaFileListParams{
	// pass ScopeID: anthropic.String(session.ID) to filter
})
if err != nil {
	panic(err)
}
for _, file := range files.Data {
	fmt.Println(file.ID, file.Filename)
}

// Download a file
if len(files.Data) > 0 {
	resp, err := client.Beta.Files.Download(ctx, files.Data[0].ID, anthropic.BetaFileDownloadParams{})
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fileContent, _ := io.ReadAll(resp.Body)
	os.WriteFile("/tmp/output.txt", fileContent, 0o644)
}
// List files produced by this session
var files = client.beta().files().list(
    FileListParams.builder()/* pass .scopeId(session.id()) to filter */.build());
for (var file : files.data()) {
    IO.println(file.id() + " " + file.filename());
}

// Download a file
if (!files.data().isEmpty()) {
    try (HttpResponse response = client.beta().files().download(files.data().getFirst().id())) {
        try (InputStream body = response.body()) {
            Files.copy(body, Path.of("/tmp/output.txt"), StandardCopyOption.REPLACE_EXISTING);
        }
    }
}
// List files produced by this session
$files = $client->beta->files->list(/* pass scopeID: $session->id to filter */);
foreach ($files->data as $file) {
    echo "{$file->id} {$file->filename}\n";
}

// Download a file
if (count($files->data) > 0) {
    $content = $client->beta->files->download($files->data[0]->id);
    file_put_contents('/tmp/output.txt', $content);
}
# List files produced by this session
files = client.beta.files.list(scope_id: session.id)
files.data.each { puts "#{it.id} #{it.filename}" }

# Download a file
if (first = files.data.first)
  content = client.beta.files.download(first.id)
  File.binwrite("/tmp/output.txt", content.read)
end