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
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestLiveClaudeSDKCancelResume(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
out := make(chan proto.Envelope, 64)
request := proto.PromptRequestPayload{RunID: uuid.NewString(), Input: proto.TextInput(prompt), AgentSessionID: resume, ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": "MiniMax-M3", "system_prompt": "Follow the user's requested format. Preserve the exact verification value in conversation history. Use no tools."}}
request := proto.PromptRequestPayload{RunID: uuid.NewString(), Input: proto.TextInput(prompt), AgentSessionID: resume, ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": "MiniMax-M3", "system_prompt": "Follow the user's requested format. Preserve the exact verification value in conversation history. Use no tools."}}
running, err := startSingleTurn(ctx, config, request, out)
if err != nil {
t.Fatal(err)
Expand Down
40 changes: 30 additions & 10 deletions apps/daemon/internal/agent/claudesdk/execution_controls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestExecutionControlsPreserveNativeDefaultsAndInstructions(t *testing.T) {
if err != nil {
t.Fatal(err)
}
request.ExecutionControls = &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}
request.ExecutionControls = &proto.ExecutionControls{TextVerbosity: "medium"}
before, _ := json.Marshal(request)
controlled, _, err := prepare(config, request)
if err != nil {
Expand All @@ -35,14 +35,18 @@ func TestExecutionControlsPreserveNativeDefaultsAndInstructions(t *testing.T) {
}

func TestExecutionControlsRejectUnsupportedProfilesBeforeLaunch(t *testing.T) {
enabled := true
cases := map[string]proto.ExecutionControls{
"empty": {}, "missing-search": {TextVerbosity: "medium"}, "missing-verbosity": {WebSearch: "disabled"},
"cached-search": {WebSearch: "cached", TextVerbosity: "medium"},
"live-search": {WebSearch: "live", TextVerbosity: "medium"},
"unknown-search": {WebSearch: "invalid", TextVerbosity: "medium"},
"low-verbosity": {WebSearch: "disabled", TextVerbosity: "low"},
"high-verbosity": {WebSearch: "disabled", TextVerbosity: "high"},
"unknown-verbosity": {WebSearch: "disabled", TextVerbosity: "invalid"},
"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"},
"unknown-verbosity": {TextVerbosity: "invalid"},
"no-effort": {ReasoningEffort: "none"},
"minimal-effort": {ReasoningEffort: "minimal"},
"summary": {ReasoningSummary: "auto"},
"service-tier": {ServiceTier: "priority"},
}
for name, controls := range cases {
t.Run(name, func(t *testing.T) {
Expand All @@ -51,7 +55,7 @@ func TestExecutionControlsRejectUnsupportedProfilesBeforeLaunch(t *testing.T) {
config := Config{Node: "must-not-run", Entrypoint: filepath.Join(root, "worker"), StateDir: filepath.Join(root, "state")}
request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("Original input."), ExecutionControls: &controls, AgentOptions: map[string]any{"model": "native-model"}}
_, err := startSingleTurn(t.Context(), config, request, make(chan proto.Envelope, 1))
if err == nil || !strings.Contains(err.Error(), "execution controls require") {
if err == nil || !strings.Contains(err.Error(), "not supported by this Harness") {
t.Fatal("unsupported controls did not fail at admission", err)
}
if _, err := os.Stat(config.StateDir); !os.IsNotExist(err) {
Expand All @@ -61,6 +65,22 @@ func TestExecutionControlsRejectUnsupportedProfilesBeforeLaunch(t *testing.T) {
}
}

func TestReasoningEffortReachesNativeOptions(t *testing.T) {
root := t.TempDir()
t.Setenv("OAC_RUNTIME_HOME", root)
config := Config{Entrypoint: filepath.Join(root, "worker"), StateDir: filepath.Join(root, "state")}
for _, effort := range []string{"low", "medium", "high", "xhigh", "max"} {
request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("hello"), ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium", ReasoningEffort: effort}, AgentOptions: map[string]any{"model": "native-model", "harness_config": map[string]any{"thinking": map[string]any{"type": "adaptive"}}}}
start, _, err := prepare(config, request)
if err != nil {
t.Fatal(effort, err)
}
if start.NativeModelOptions == nil || start.NativeModelOptions.Effort != effort || start.NativeModelOptions.Thinking["type"] != "adaptive" {
t.Fatalf("effort %s: %+v", effort, start.NativeModelOptions)
}
}
}

func TestMCPWithoutEnvironmentNoneRejectedBeforeSetup(t *testing.T) {
root := t.TempDir()
t.Setenv("OAC_RUNTIME_HOME", root)
Expand All @@ -81,7 +101,7 @@ func TestStructuredOutputConfigurationReachesNativeUnchanged(t *testing.T) {
t.Setenv("OAC_RUNTIME_HOME", root)
config := Config{Entrypoint: filepath.Join(root, "worker"), StateDir: filepath.Join(root, "state")}
schema := json.RawMessage(`{"type":"object","properties":{"n":{"const":9007199254740992}}}`)
request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("Original input."), ObserveMessages: true, DisableSubagents: true, AgentOptions: map[string]any{"model": "model", "system_prompt": "Original instructions."}, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium", OutputFormat: &proto.OutputFormat{Type: "json_schema", Schema: schema}}}
request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("Original input."), ObserveMessages: true, DisableSubagents: true, AgentOptions: map[string]any{"model": "model", "system_prompt": "Original instructions."}, ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium", OutputFormat: &proto.OutputFormat{Type: "json_schema", Schema: schema}}}
start, _, err := prepare(config, request)
if err != nil {
t.Fatal(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func TestLiveClaudeExecutorReuseAndCancel(t *testing.T) {
_ = os.WriteFile(filepath.Join(proof, "executor-evidence.json"), raw, 0600)
}
defer persist()
request := proto.PromptRequestPayload{DisableExecutionEnvironment: true, DisableSubagents: true, ObserveMessages: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": model, "system_prompt": "Follow requested formats briefly. Remember the exact verification marker across the conversation. Use no tools."}}
request := proto.PromptRequestPayload{DisableExecutionEnvironment: true, DisableSubagents: true, ObserveMessages: true, ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": model, "system_prompt": "Follow requested formats briefly. Remember the exact verification marker across the conversation. Use no tools."}}
factory := NewExecutorFactory(config)
prepared := time.Now()
owner, err := factory(ctx, request)
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/claudesdk/harness_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ func TestHarnessConfigReachesBridgeAndOwnsSnapshot(t *testing.T) {
root := t.TempDir()
t.Setenv("OAC_RUNTIME_HOME", root)
config := Config{Entrypoint: filepath.Join(root, "main.js"), StateDir: filepath.Join(root, "state")}
native := map[string]any{"effort": "high", "thinking": map[string]any{"type": "enabled", "budgetTokens": 1024}}
req := proto.PromptRequestPayload{AgentOptions: map[string]any{"model": "fixture", "harness_config": native}}
native := map[string]any{"thinking": map[string]any{"type": "enabled", "budgetTokens": 1024}}
req := proto.PromptRequestPayload{AgentOptions: map[string]any{"model": "fixture", "harness_config": native}, ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium", ReasoningEffort: "high"}}
start, _, err := prepareConfiguration(config, req)
if err != nil {
t.Fatal(err)
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/live_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func TestLiveClaudeSDKTextResume(t *testing.T) {
requestStart := len(requests)
mu.Unlock()
out := make(chan proto.Envelope, 64)
request := proto.PromptRequestPayload{RunID: uuid.NewString(), Input: proto.TextInput(prompt), AgentSessionID: resume, ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": "MiniMax-M3", "system_prompt": "Answer briefly and preserve the exact verification value in the conversation. Use no tools."}}
request := proto.PromptRequestPayload{RunID: uuid.NewString(), Input: proto.TextInput(prompt), AgentSessionID: resume, ObserveMessages: true, DisableExecutionEnvironment: true, DisableSubagents: true, ExecutionControls: &proto.ExecutionControls{TextVerbosity: "medium"}, AgentOptions: map[string]any{"model": "MiniMax-M3", "system_prompt": "Answer briefly and preserve the exact verification value in the conversation. Use no tools."}}
if success != nil {
request.AgentOptions["system_prompt"] = "Call lookup exactly once as requested, then report both result parts and any prior verification value. Never retry a failed tool."
request.FunctionTools = []proto.FunctionTool{{Name: "lookup", Description: "Return a synthetic verification value.", Parameters: json.RawMessage(`{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false}`)}}
Expand Down
9 changes: 3 additions & 6 deletions apps/daemon/internal/agent/claudesdk/native_model_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ type nativeModelOptions struct {
Thinking map[string]any `json:"thinking,omitempty"`
}

func compileNativeModelOptions(config proto.HarnessConfig) *nativeModelOptions {
if len(config) == 0 {
func compileNativeModelOptions(config proto.HarnessConfig, effort string) *nativeModelOptions {
if len(config) == 0 && effort == "" {
return nil
}
options := &nativeModelOptions{}
if value, exists := config["effort"]; exists {
options.Effort = value.(string)
}
options := &nativeModelOptions{Effort: effort}
if value, exists := config["thinking"]; exists {
options.Thinking = value.(map[string]any)
}
Expand Down
14 changes: 8 additions & 6 deletions apps/daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
if err != nil {
return startRequest{}, nil, err
}
start.NativeModelOptions = compileNativeModelOptions(modelConfiguration.HarnessConfig)
if err := harnessconfiguration.Declaration.ValidateExecutionControls(req.ExecutionControls); err != nil {
return fail(err.Error())
}
effort := ""
if req.ExecutionControls != nil {
effort = req.ExecutionControls.ReasoningEffort
}
start.NativeModelOptions = compileNativeModelOptions(modelConfiguration.HarnessConfig, effort)
if err := req.ValidateToolSearch(true); err != nil {
return startRequest{}, nil, err
}
Expand All @@ -74,11 +81,6 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
if err := validateMCP(req); err != nil {
return startRequest{}, nil, err
}
// Search is disabled by the fixed native tool profile. Medium selects the
// SDK's default text generation; it has no native verbosity-level option.
if controls := req.ExecutionControls; controls != nil && (controls.WebSearch != "disabled" || controls.TextVerbosity != "medium") {
return fail("execution controls require disabled web search and medium text verbosity")
}
if req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil {
format := req.ExecutionControls.OutputFormat
if format.Type != "json_schema" || !req.ObserveMessages || !req.DisableSubagents || req.MCPHTTPServers != nil || (req.LocalEnvironment != nil && (len(req.LocalEnvironment.MCP) != 0 || len(req.LocalEnvironment.Skills) != 0)) {
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: "enabled", TextVerbosity: "medium"}
req.ExecutionControls = &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "live", 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 apps/daemon/internal/agent/claudesdk/restrictions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestTextFactoryAcceptsRestrictiveCapabilities(t *testing.T) {
controls *proto.ExecutionControls
}{
{"environment", true, false, nil}, {"subagents", false, true, nil}, {"both", true, true, nil},
{"execution-controls", true, true, &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium"}},
{"execution-controls", true, true, &proto.ExecutionControls{TextVerbosity: "medium"}},
} {
t.Run(test.name, func(t *testing.T) {
root := t.TempDir()
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestTextFactoryRejectsUnsupportedInput(t *testing.T) {
request := proto.PromptRequestPayload{RunID: "run", Input: proto.TextInput("hello"), AgentOptions: map[string]any{"model": "fake"}}
switch kind {
case "execution-controls":
request.ExecutionControls = &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "low"}
request.ExecutionControls = &proto.ExecutionControls{TextVerbosity: "low"}
case "tool":
request.FunctionTools = []proto.FunctionTool{{}}
case "outside":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestWorkspaceStructuredPreparationQualificationAndFrozenSchema(t *testing.T
req := preparationRequest()
req.ObserveMessages = true
schema := `{"type":"object","properties":{"n":{"const":9007199254740992}}}`
req.ExecutionControls = &proto.ExecutionControls{WebSearch: "disabled", TextVerbosity: "medium", OutputFormat: &proto.OutputFormat{Type: "json_schema", Schema: json.RawMessage(schema)}}
req.ExecutionControls = &proto.ExecutionControls{TextVerbosity: "medium", OutputFormat: &proto.OutputFormat{Type: "json_schema", Schema: json.RawMessage(schema)}}
e, err := NewExecutorFactory(config)(t.Context(), req)
if mode == "structured-missing" {
if err == nil || !strings.Contains(err.Error(), "workspace structured output") {
Expand Down
2 changes: 0 additions & 2 deletions apps/daemon/internal/agent/codex/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ func discoverWithCheck(parent context.Context, options agent.DiscoveryOptions, i
for _, capability := range []*proto.CapabilitySupport{&caps.LocalEnvironment, &caps.WorkspaceReadPreparation, &caps.WorkspaceOutputExport, &caps.RetainedNativeHistory} {
agent.Narrow(capability, local)
}
agent.Narrow(&caps.ExecutionControls, SupportsTextVerbosity)
agent.Narrow(&caps.TextVerbosity, SupportsTextVerbosity)
runtime.Executor = NewExecutorFactory(options.StateRoot)
fmt.Fprintf(options.Stdout, "Codex preflight ok (%s)\n", version)
return runtime
Expand Down
42 changes: 31 additions & 11 deletions apps/daemon/internal/agent/codex/execution_controls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,52 @@ import (
"github.com/MiniMax-AI/OpenAgentCore/internal/agentdaemon/proto"
)

func TestExecutionControlsSelectNativeSettings(t *testing.T) {
func ptr[T any](v T) *T { return &v }

func TestExecutionControlsMapToNativeSettings(t *testing.T) {
t.Setenv("OAC_RUNTIME_HOME", t.TempDir())
for _, search := range []string{"disabled", "cached", "live"} {
for _, verbosity := range []string{"low", "medium", "high"} {
plan, err := BuildSessionPlan(testStateRoot(t), "state", map[string]any{"model": "test-model"}, &proto.ExecutionControls{WebSearch: search, TextVerbosity: verbosity})
base := [][2]string{{"tools.experimental_request_user_input.enabled", "false"}}
for _, tc := range []struct {
name string
controls *proto.ExecutionControls
want [][2]string
}{
{"absent controls send nothing", nil, nil},
{"empty controls send nothing", &proto.ExecutionControls{}, nil},
{"verbosity", &proto.ExecutionControls{TextVerbosity: "high"}, [][2]string{{"model_verbosity", `"high"`}}},
{"search without settings", &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "live", ContextSize: "medium"}},
[][2]string{{"web_search", `"live"`}, {"tools.web_search.context_size", `"medium"`}}},
{"search settings", &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "cached", ContextSize: "high", AllowedDomains: []string{"a.example", "b.example"},
Location: &proto.SearchLocation{City: ptr("Paris"), Timezone: ptr("Europe/Paris")}}},
[][2]string{{"web_search", `"cached"`}, {"tools.web_search.context_size", `"high"`}, {"tools.web_search.allowed_domains", `["a.example","b.example"]`},
{"tools.web_search.location.city", `"Paris"`}, {"tools.web_search.location.timezone", `"Europe/Paris"`}}},
{"empty domain list stays distinct from null", &proto.ExecutionControls{WebSearch: &proto.WebSearchControl{Mode: "disabled", ContextSize: "low", AllowedDomains: []string{}}},
[][2]string{{"web_search", `"disabled"`}, {"tools.web_search.context_size", `"low"`}, {"tools.web_search.allowed_domains", `[]`}}},
{"reasoning and tier", &proto.ExecutionControls{ReasoningEffort: "max", ReasoningSummary: "concise", ServiceTier: "priority"},
[][2]string{{"model_reasoning_effort", `"max"`}, {"model_reasoning_summary", `"concise"`}, {"service_tier", `"priority"`}}},
} {
t.Run(tc.name, func(t *testing.T) {
plan, err := BuildSessionPlan(testStateRoot(t), "state", map[string]any{"model": "test-model"}, tc.controls)
if err != nil {
t.Fatal(err)
}
plan.Cleanup()
want := [][2]string{{"tools.experimental_request_user_input.enabled", "false"}, {"web_search", `"` + search + `"`}, {"model_verbosity", `"` + verbosity + `"`}}
if !reflect.DeepEqual(plan.ExtraConfig, want) {
if want := append(append([][2]string{}, base...), tc.want...); !reflect.DeepEqual(plan.ExtraConfig, want) {
t.Fatalf("config = %v, want %v", plan.ExtraConfig, want)
}
}
})
}
}

func TestExecutionControlsRejectIncompleteOrInvalidValues(t *testing.T) {
func TestExecutionControlsRejectUndeclaredValues(t *testing.T) {
t.Setenv("OAC_RUNTIME_HOME", t.TempDir())
for _, controls := range []proto.ExecutionControls{
{}, {WebSearch: "disabled"}, {TextVerbosity: "medium"},
{WebSearch: "invalid", TextVerbosity: "medium"}, {WebSearch: "disabled", TextVerbosity: "invalid"},
{WebSearch: &proto.WebSearchControl{Mode: "indexed", ContextSize: "medium"}},
{TextVerbosity: "invalid"}, {ReasoningEffort: "ultra"}, {ReasoningSummary: "none"}, {ServiceTier: "scale"},
} {
if plan, err := BuildSessionPlan(testStateRoot(t), "state", nil, &controls); err == nil {
plan.Cleanup()
t.Fatal("invalid controls accepted", controls)
t.Fatal("undeclared controls accepted", controls)
}
}
}
Loading
Loading