From 919dbbefd1b2a627da577cd7441ce25907d16c73 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Thu, 4 Jun 2026 23:10:37 -0700 Subject: [PATCH] Align goal extension behavior with core --- codex-rs/ext/goal/src/lib.rs | 6 +----- codex-rs/ext/goal/src/runtime.rs | 18 +++++++++--------- codex-rs/ext/goal/src/spec.rs | 14 +++++++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/codex-rs/ext/goal/src/lib.rs b/codex-rs/ext/goal/src/lib.rs index 8433e1f7df..bb640c6ce8 100644 --- a/codex-rs/ext/goal/src/lib.rs +++ b/codex-rs/ext/goal/src/lib.rs @@ -1,8 +1,4 @@ -//! Extension crate sketch for the `/goal` feature. -//! -//! This crate is intentionally not wired into the host yet. It contains the -//! goal tool specs, extension registration shape, and the parts of runtime -//! accounting that can be represented with today's extension API. +//! Extension crate for the `/goal` feature. mod accounting; mod api; diff --git a/codex-rs/ext/goal/src/runtime.rs b/codex-rs/ext/goal/src/runtime.rs index b58d1244e6..f31da6d9f6 100644 --- a/codex-rs/ext/goal/src/runtime.rs +++ b/codex-rs/ext/goal/src/runtime.rs @@ -281,6 +281,15 @@ impl GoalRuntimeHandle { return Ok(()); } + let Some(thread_manager) = self.inner.thread_manager.upgrade() else { + tracing::debug!("skipping goal continuation because thread manager is unavailable"); + return Ok(()); + }; + let Ok(thread) = thread_manager.get_thread(self.inner.thread_id).await else { + tracing::debug!("skipping goal continuation because live thread is unavailable"); + return Ok(()); + }; + let Some(goal) = self .inner .state_dbs @@ -296,16 +305,7 @@ impl GoalRuntimeHandle { self.inner.accounting_state.clear_active_goal(); return Ok(()); } - let item = continuation_steering_item(&protocol_goal_from_state(goal)); - let Some(thread_manager) = self.inner.thread_manager.upgrade() else { - tracing::debug!("skipping goal continuation because thread manager is unavailable"); - return Ok(()); - }; - let Ok(thread) = thread_manager.get_thread(self.inner.thread_id).await else { - tracing::debug!("skipping goal continuation because live thread is unavailable"); - return Ok(()); - }; if let Err(err) = thread.try_start_turn_if_idle(vec![item]).await { let reason = err.reason(); diff --git a/codex-rs/ext/goal/src/spec.rs b/codex-rs/ext/goal/src/spec.rs index 70e89e4fbb..2c92c03848 100644 --- a/codex-rs/ext/goal/src/spec.rs +++ b/codex-rs/ext/goal/src/spec.rs @@ -34,7 +34,8 @@ pub fn create_create_goal_tool() -> ToolSpec { ( "token_budget".to_string(), JsonSchema::integer(Some( - "Optional positive token budget for the new active goal.".to_string(), + "Positive token budget for the new goal. Omit unless explicitly requested." + .to_string(), )), ), ]); @@ -62,7 +63,7 @@ pub fn create_update_goal_tool() -> ToolSpec { JsonSchema::string_enum( vec![json!("complete"), json!("blocked")], Some( - "Required. Set to complete only when the objective is achieved and no required work remains. Set to blocked only when the goal cannot currently proceed without a user decision, missing dependency, or external unblock." + "Required. Set to `complete` only when the objective is achieved and no required work remains. Set to `blocked` only after the same blocking condition has recurred for at least three consecutive goal turns and the agent is at an impasse. After a previously blocked goal is resumed, the resumed run starts a fresh blocked audit." .to_string(), ), ), @@ -71,11 +72,14 @@ pub fn create_update_goal_tool() -> ToolSpec { ToolSpec::Function(ResponsesApiTool { name: UPDATE_GOAL_TOOL_NAME.to_string(), description: r#"Update the existing goal. -Use this tool only to mark the goal achieved or blocked. +Use this tool only to mark the goal achieved or genuinely blocked. Set status to `complete` only when the objective has actually been achieved and no required work remains. -Set status to `blocked` only when the goal cannot currently proceed until something external changes. +Set status to `blocked` only when the same blocking condition has repeated for at least three consecutive goal turns, counting the original/user-triggered turn and any automatic continuations, and the agent cannot make meaningful progress without user input or an external-state change. +If the user resumes a goal that was previously marked `blocked`, treat the resumed run as a fresh blocked audit. If the same blocking condition then repeats for at least three consecutive resumed goal turns, set status to `blocked` again. +Once the blocked threshold is satisfied, do not keep reporting that you are still blocked while leaving the goal active; set status to `blocked`. +Do not use `blocked` merely because the work is hard, slow, uncertain, incomplete, or would benefit from clarification. Do not mark a goal complete merely because its budget is nearly exhausted or because you are stopping work. -You cannot use this tool to pause, resume, or budget-limit a goal; those status changes are controlled by the user or system. +You cannot use this tool to pause, resume, budget-limit, or usage-limit a goal; those status changes are controlled by the user or system. When marking a budgeted goal achieved with status `complete`, report the final token usage from the tool result to the user."# .to_string(), strict: false,