Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion apps/daemon/internal/agent/claudesdk/execution_controls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,28 @@ func TestExecutionControlsPreserveNativeDefaultsAndInstructions(t *testing.T) {
}
}

func TestLiveWebSearchKeepsNativeToolsOnlyOnAWorkspace(t *testing.T) {
config := workspaceFixture(t)
request := workspaceRequest()
ordinary, _, err := prepare(config, request)
if err != nil {
t.Fatal(err)
}
request.ExecutionControls = &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "live", ContextSize: "medium"}, TextVerbosity: "medium"}
live, _, err := prepare(config, request)
if err != nil || !reflect.DeepEqual(ordinary, live) {
t.Fatal("live search changed the native web tools", err)
}
config.Workspace = nil
if _, _, err := prepare(config, request); err == nil || !strings.Contains(err.Error(), "live web search requires a dedicated workspace") {
t.Fatal("live search without a workspace was accepted", err)
}
}

func TestExecutionControlsRejectUnsupportedProfilesBeforeLaunch(t *testing.T) {
enabled := true
cases := map[string]proto.ExecutionControls{
"cached-search": {WebSearch: &proto.WebSearchControl{Mode: "cached", ContextSize: "medium"}},
"live-search": {WebSearch: &proto.WebSearchControl{Mode: "live", ContextSize: "medium"}},
"enabled-ptc": {ProgrammaticToolCalling: &enabled},
"low-verbosity": {TextVerbosity: "low"},
"high-verbosity": {TextVerbosity: "high"},
Expand Down
5 changes: 4 additions & 1 deletion apps/daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
if err := validateMCP(req); err != nil {
return startRequest{}, nil, err
}
// Without a search control Claude Code keeps its native web tools.
// Without a search control Claude Code keeps its native web tools, which only a workspace runs.
if controls := req.ExecutionControls; controls != nil && controls.WebSearch != nil {
if controls.WebSearch.Mode == "live" && config.Workspace == nil {
return fail("live web search requires a dedicated workspace")
}
start.DisableWebSearch = controls.WebSearch.Mode == "disabled"
}
if req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil {
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/preparation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func TestPreparationRejectsInputAndUnavailableProfilesBeforeLaunch(t *testing.T)
case "mcp":
req.MCPHTTPServers = &[]proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "remote", ServerURL: "https://example.test/mcp"}}
case "controls":
req.ExecutionControls = &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "live", ContextSize: "medium"}}
req.ExecutionControls = &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "cached", ContextSize: "medium"}}
}
if _, err := NewExecutorFactory(config)(t.Context(), req); err == nil {
t.Fatal("invalid preparation was accepted")
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/execution-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Execution carries the resolved values to the Harness and validates them against
| Harness | Web search | Programmatic tool calling |
| --- | --- | --- |
| Codex | `disabled`, `cached` and `live`, with `context_size`, `allowed_domains` and `location`, as native `web_search` and `tools.web_search` settings | `enabled: true` turns on native `code_mode`; `false` turns off `code_mode`, `code_mode_only` and `code_mode_prewarm` and checks native managed requirements before a thread starts or resumes |
| Claude Code | `disabled` only. On a workspace placement the native `claude_code` tool set runs without ask-user, scheduling, workflow, background and `ListAgents` tools, and `disabled` removes `WebSearch` and `WebFetch`; `environment: none` keeps no built-in tools | `false` only |
| Claude Code | `disabled` and `live`, with `context_size: "medium"` and no `allowed_domains` or `location`. On a workspace placement the native `claude_code` tool set runs without ask-user, scheduling, workflow, background and `ListAgents` tools; `disabled` removes `WebSearch` and `WebFetch`, and `live` keeps them. `environment: none` keeps no built-in tools and rejects `live` | `false` only |
| MiniMax Code | `disabled` only, through the native `features.webSearch=false` because the restricted profile has no search tool | `false` only |

## HTTP MCP
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/harness-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Native model parameters and provider protocols per Harness are in [model executi
| [Structured output](./execution-tools.md#structured-output) | Rejected | Verified on all placements | Rejected |
| [Deferred function discovery](./execution-tools.md#deferred-function-discovery) | Rejected | Verified: `none`, self-hosted; admitted: hosted | Rejected |
| [Disabled web search and programmatic tool calling](./execution-tools.md#web-search-and-programmatic-tool-calling) | Verified: `none`; admitted: hosted, self-hosted | Verified: `none`; admitted: hosted, self-hosted | Verified: `none`; admitted: hosted, self-hosted |
| Web search `cached` or `live` and programmatic tool calling `enabled` | Admitted on all placements | Rejected | Rejected |
| Web search `cached` or `live` and programmatic tool calling `enabled` | Admitted on all placements | `live` admitted on hosted and self-hosted, rejected on `none`; `cached` and `enabled` rejected | Rejected |
| [Service-origin HTTP MCP](./environments.md#public-mcp-connection-origin) | Verified: `none`; rejected elsewhere | Verified: `none`; rejected elsewhere | Rejected |
| [Environment-origin HTTP MCP](./environments.md#public-mcp-connection-origin) | Verified: hosted, self-hosted | Verified: hosted, self-hosted | Verified: hosted, self-hosted; `allowed_tools` null and `required` false only |
| [HTTP MCP bearer credentials](./execution-tools.md#http-mcp) | Verified | Verified | Verified |
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/execution-tools.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "执行工具"
source: contracts/agents-api/execution-tools.md
source_hash: ae8c47681587ccd0e7084c883be63a1f7064c1e780a5516536736943d8d45e08
source_hash: ad7fb570c29fc3d346ad406e794b15c7bdabd667634b2ac0e32506e52d964aaa
---

Agent 在 `tools` 中声明应用函数、控制项和 MCP 服务器,并可在 `text.format` 中声明输出 schema。本契约说明 Core 如何验证声明、哪些内容跨越 Runtime 边界,以及调用方如何恢复待执行操作。[Harness 能力](harness-capabilities.md)列出各 Harness 在不同部署位置支持的操作。原生工作区工具和 Environment Plugin MCP 属于 [Environment](environments.md#skills-plugins-and-environment-mcp)。
Expand Down Expand Up @@ -69,7 +69,7 @@ Core 发送 `PromptRequestPayload.ToolSearch` 和每个 `FunctionTool.DeferLoadi
| Harness | Web 搜索 | 程序化工具调用 |
| --- | --- | --- |
| Codex | `disabled`、`cached` 和 `live`,以及 `context_size`、`allowed_domains` 和 `location`,对应原生 `web_search` 与 `tools.web_search` 设置 | `enabled: true` 开启原生 `code_mode`;`false` 关闭 `code_mode`、`code_mode_only` 和 `code_mode_prewarm`,并在启动或恢复线程前检查原生受管要求 |
| Claude Code | 仅 `disabled`。在工作区位置运行原生 `claude_code` 工具集,但移除向用户提问、定时、工作流、后台和 `ListAgents` 工具,`disabled` 会移除 `WebSearch` 和 `WebFetch`;`environment: none` 不保留内置工具 | 仅 `false` |
| Claude Code | `disabled` 和 `live`,需使用 `context_size: "medium"`,且不支持 `allowed_domains` 或 `location`。在工作区位置运行原生 `claude_code` 工具集,但移除向用户提问、定时、工作流、后台和 `ListAgents` 工具;`disabled` 会移除 `WebSearch` 和 `WebFetch`,`live` 保留它们。`environment: none` 不保留内置工具,并拒绝 `live` | 仅 `false` |
| MiniMax Code | 仅 `disabled`,受限配置没有搜索工具,通过原生 `features.webSearch=false` 实现 | 仅 `false` |

## HTTP MCP {#http-mcp}
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/harness-capabilities.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Harness 能力"
source: contracts/agents-api/harness-capabilities.md
source_hash: ebe62fa0536333b51a80233883039ed732a6ce7232030cd90e8f8854dfde9859
source_hash: c76b7f62749647860c2623546574eadf28552aa4e525dad072dfc637091bb4b0
---

本页列出每个 Harness 在每种部署位置支持的能力。Core 根据 `internal/harnessconfig/<package>/declaration.go` 中 Harness 的声明决定准入,运行 Session 的 Runtime 也必须声明操作。所链接契约定义各操作;[Harness 接入](harness-onboarding.md#qualify-the-adapter)说明资格验证方法。
Expand Down Expand Up @@ -40,7 +40,7 @@ source_hash: ebe62fa0536333b51a80233883039ed732a6ce7232030cd90e8f8854dfde9859
| [结构化输出](execution-tools.md#structured-output) | 已拒绝 | 所有位置已验证 | 已拒绝 |
| [延迟函数发现](execution-tools.md#deferred-function-discovery) | 已拒绝 | 已验证:`none`、自托管;已准入:托管 | 已拒绝 |
| [禁用 Web 搜索与程序化工具调用](execution-tools.md#web-search-and-programmatic-tool-calling) | 已验证:`none`;已准入:托管、自托管 | 已验证:`none`;已准入:托管、自托管 | 已验证:`none`;已准入:托管、自托管 |
| Web 搜索 `cached` 或 `live`,以及程序化工具调用 `enabled` | 所有位置已准入 | 已拒绝 | 已拒绝 |
| Web 搜索 `cached` 或 `live`,以及程序化工具调用 `enabled` | 所有位置已准入 | `live` 在托管和自托管已准入,在 `none` 已拒绝;`cached` 和 `enabled` 已拒绝 | 已拒绝 |
| [服务端来源 HTTP MCP](environments.md#public-mcp-connection-origin) | 已验证:`none`;其他位置拒绝 | 已验证:`none`;其他位置拒绝 | 已拒绝 |
| [Environment 来源 HTTP MCP](environments.md#public-mcp-connection-origin) | 已验证:托管、自托管 | 已验证:托管、自托管 | 已验证:托管、自托管;仅 `allowed_tools` null、`required` false |
| [HTTP MCP bearer 凭据](execution-tools.md#http-mcp) | 已验证 | 已验证 | 已验证 |
Expand Down
2 changes: 1 addition & 1 deletion docs/api/public-agent-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ agents "/agents" -d '{

- **Update** changes only the fields you send. `metadata` replaces all pairs; `null` clears `name` or `instructions`.
- **Delete** keeps existing Sessions.
- **Tools** are functions, MCP servers, `tool_search` (Claude) and `web_search` with `mode: "disabled"`. Support depends on Harness and Environment; see [Execution tools](../../contracts/agents-api/execution-tools.md).
- **Tools** are functions, MCP servers, `tool_search` (Claude) and `web_search`. Support depends on Harness and Environment; see [Execution tools](../../contracts/agents-api/execution-tools.md).

## Sessions

Expand Down
4 changes: 2 additions & 2 deletions docs/zh/api/public-agent-api.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Agents API 指南"
source: docs/api/public-agent-api.md
source_hash: ad2aea1d04536a7b12079a14f9926fc91b5202d394c1cd132d6c0a004a489451
source_hash: 926187936e4c0aa8ac5298a1156463a15e893add8df2e4665bbc813204e4a6cd
---

Core 在 `/v1` 提供 OpenAI Agents API。本指南用官方 OpenAI SDK 和普通 HTTP 两种方式演示每项常见任务,并说明 Core 与 OpenAI 的差异。如果尚未启动过 Session,请先阅读[运行第一个 Session](../getting-started/quickstart.md)。
Expand Down Expand Up @@ -208,7 +208,7 @@ agents "/agents" -d '{

- **更新**只会更改你发送的字段。`metadata` 会替换所有键值对;`null` 会清除 `name` 或 `instructions`。
- **删除**不会影响现有 Session。
- **工具**包括函数、MCP 服务器、`tool_search`(Claude)以及设置了 `mode: "disabled"` 的 `web_search`。支持情况取决于 Harness 和 Environment;请参阅 [执行工具](../../../contracts/agents-api/zh/execution-tools.md)。
- **工具**包括函数、MCP 服务器、`tool_search`(Claude)以及 `web_search`。支持情况取决于 Harness 和 Environment;请参阅 [执行工具](../../../contracts/agents-api/zh/execution-tools.md)。

## Sessions {#sessions}

Expand Down
17 changes: 11 additions & 6 deletions internal/harnessconfig/claudesdk/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ var Declaration = harnessconfig.Declaration{
MCPHTTPRequired: proto.CapabilitySupported,
MCPHTTPBearerAuth: proto.CapabilitySupported,
},
// The SDK takes effort low to max. It has no verbosity, summary, service
// tier, search mode or search setting, and enabling programmatic tool calling
// is not wired. Native search has no cached mode or settings, so disabled is
// the only selectable mode and an absent control keeps native search; medium
// is the official default verbosity and needs no setting.
// The SDK takes effort low to max. It has no verbosity, summary or service
// tier, and enabling programmatic tool calling is not wired. Native WebSearch
// and WebFetch are live and have no cached mode or settings: disabled removes
// them, while live and an absent control keep them. Medium is the official
// default verbosity and needs no setting.
Execution: harnessconfig.ExecutionOptions{
WebSearchModes: []string{"disabled"},
WebSearchModes: []string{"disabled", "live"},
ReasoningEfforts: []string{"low", "medium", "high", "xhigh", "max"},
ProgrammaticToolCalling: []bool{false},
TextVerbosities: []string{"medium"},
Expand Down Expand Up @@ -107,10 +107,15 @@ func validateConfiguration(admission harnessconfig.Admission) error {
for _, raw := range admission.Tools {
var tool struct {
Type string `json:"type"`
Mode string `json:"mode"`
}
if json.Unmarshal(raw, &tool) != nil {
return harnessconfig.ErrInvalidInput
}
// environment:none runs no native tools, so live search has no home there.
if tool.Type == "web_search" && tool.Mode == "live" && environment.Type == "none" {
return errors.New("Live web search requires a managed or self-hosted execution Environment.")
}
search = search || tool.Type == "tool_search"
otherTools = otherTools || (tool.Type != "function" && tool.Type != "tool_search" && tool.Type != "web_search" && tool.Type != "programmatic_tool_calling")
}
Expand Down
12 changes: 12 additions & 0 deletions services/core/internal/execution/harness_admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ func TestClaudeHostedToolsRequireSeparateSupport(t *testing.T) {
}
}

func TestClaudeLiveSearchRequiresAWorkspace(t *testing.T) {
for _, mode := range []string{"disabled", "cached", "live"} {
for _, placement := range []string{"none", "openai_hosted"} {
raw := json.RawMessage(`{"agent":{"model":"fixture","tools":[{"type":"web_search","mode":"` + mode + `","context_size":"medium"}]},"environment":{"type":"` + placement + `"}}`)
want := mode == "disabled" || (mode == "live" && placement != "none")
if err := (Policy{}).ValidateSessionConfiguration("claude_sdk", raw); (err == nil) != want {
t.Fatalf("%s/%s: %v", mode, placement, err)
}
}
}
}

func TestValidatorPrecedencePreservesErrors(t *testing.T) {
snapshot := Snapshot{Agent: v1.Agent{Tools: []json.RawMessage{json.RawMessage(`{"type":"unknown"}`)}}}
_, decodeErr := executionTools(snapshot.Agent.Tools)
Expand Down
Loading