From 04cdbe43e1e7c07c5a58bce03aa3f8dc45f5c4b6 Mon Sep 17 00:00:00 2001 From: "animus-launchapp-gitprovider[bot]" <4147602+animus-launchapp-gitprovider[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 00:29:40 +0000 Subject: [PATCH] Agent-level reasoning_effort is dropped before reaching the harness (codex shows "reasoning effort: none") --- .../src/agent_runtime_config.rs | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/crates/orchestrator-config/src/agent_runtime_config.rs b/crates/orchestrator-config/src/agent_runtime_config.rs index a363280f..aa58d84e 100644 --- a/crates/orchestrator-config/src/agent_runtime_config.rs +++ b/crates/orchestrator-config/src/agent_runtime_config.rs @@ -588,9 +588,17 @@ fn merge_workflow_runtime_overlay(base: &mut AgentRuntimeConfig, workflow: &crat } for (agent_id, profile) in &workflow.agent_profiles { match base.agents.get_mut(agent_id) { - Some(existing) => merge_agent_profile(existing, profile), + Some(existing) => { + merge_agent_profile(existing, profile); + // Explicitly carry fields that the external protocol crate's + // merge_agent_profile may not propagate at the pinned tag. + apply_workflow_agent_profile_overlay(existing, profile); + } None => { - base.agents.insert(agent_id.clone(), profile.to_profile()); + let mut compiled = profile.to_profile(); + // to_profile() may not carry all overlay fields; apply them explicitly. + apply_workflow_agent_profile_overlay(&mut compiled, profile); + base.agents.insert(agent_id.clone(), compiled); } } } @@ -641,6 +649,21 @@ fn merge_workflow_runtime_overlay(base: &mut AgentRuntimeConfig, workflow: &crat } } +/// Carry overlay fields from a WorkflowConfig agent profile that +/// `to_profile()` / `merge_agent_profile` in the external protocol crate may +/// not propagate at the pinned tag. Applied after every call to those functions +/// so values declared in project YAML (e.g. `reasoning_effort`, `permission_mode`) +/// always reach the compiled `AgentRuntimeConfig.agents` map and are visible +/// through `phase_reasoning_effort()` / `phase_permission_mode()`. +fn apply_workflow_agent_profile_overlay(compiled: &mut AgentProfile, overlay: &AgentProfileOverlay) { + if let Some(effort) = overlay.reasoning_effort.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + compiled.reasoning_effort = Some(effort.to_string()); + } + if let Some(mode) = overlay.permission_mode.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + compiled.permission_mode = Some(mode.to_string()); + } +} + pub(crate) fn merge_agent_runtime_overlay(base: &mut AgentRuntimeConfig, overlay: &AgentRuntimeOverlay) { for tool in &overlay.tools_allowlist { if !tool.trim().is_empty() && !base.tools_allowlist.iter().any(|candidate| candidate.eq_ignore_ascii_case(tool)) @@ -4247,6 +4270,49 @@ agents: assert_eq!(restored.permission_mode.as_deref(), Some("plan")); } + /// Regression test for TASK-446: `reasoning_effort` set on a WorkflowConfig + /// agent profile must survive the compile step and be returned by + /// `phase_reasoning_effort()` via the agent-profile fallback when no + /// phase-level `runtime.reasoning_effort` override is present. + #[test] + fn reasoning_effort_roundtrips_from_workflow_yaml_into_compiled_config() { + let yaml = r#" +agents: + code-checker: + description: "Code quality reviewer" + reasoning_effort: medium +phases: + code-check: + mode: agent + agent_id: code-checker + directive: "Review code quality." +"#; + let config = crate::workflow_config::parse_yaml_workflow_config(yaml).expect("parse YAML"); + + // Verify the YAML parser carries reasoning_effort into AgentProfileOverlay. + let overlay = config.agent_profiles.get("code-checker").expect("code-checker profile parsed"); + assert_eq!(overlay.reasoning_effort.as_deref(), Some("medium")); + + let mut runtime = seeded_agent_runtime_config(); + merge_workflow_runtime_overlay(&mut runtime, &config); + + // The compiled AgentProfile must carry reasoning_effort. + let profile = runtime.agent_profile("code-checker").expect("code-checker profile compiled"); + assert_eq!( + profile.reasoning_effort.as_deref(), + Some("medium"), + "reasoning_effort must survive the WorkflowConfig→AgentRuntimeConfig compile step" + ); + + // End-to-end: phase_reasoning_effort must return it via the agent-profile + // fallback (the phase has no runtime.reasoning_effort override). + assert_eq!( + runtime.phase_reasoning_effort("code-check"), + Some("medium"), + "phase_reasoning_effort must fall back to agent profile when phase runtime.reasoning_effort is absent" + ); + } + #[test] fn merge_agent_profile_overlay_permission_mode_wins_and_absent_inherits() { let mut base: AgentProfile = serde_json::from_value(serde_json::json!({