mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Allow user-requested goal pauses through update_goal (#44290)
## Why `update_goal` only accepted `complete` and `blocked`, preventing the agent from pausing a goal in response to an explicit user request. ## What changed - Accept `paused` and account for final goal progress when pausing, with budget limits taking precedence. - Update tool instructions and goal prompts to allow pauses only at the user's explicit request, report the returned status, and stop goal work. A later resume revokes the pause request. ## Testing Extend coverage for pause accounting, budget-limit precedence, rejection of resume and system-limit statuses, and preservation of a tool-paused goal when resuming a thread. GitOrigin-RevId: c3c15a51f848ce6eb65854d51b1a1bc75bb456a7
This commit is contained in:
1 parent
d117c2eb02
commit
fa7af3883d
7 files changed
+187
-102
No files matched your search
@@ -11,6 +11,7 @@ use app_test_support::create_fake_rollout_with_text_elements;
|
|||||||
use app_test_support::create_fake_rollout_with_token_usage;
|
use app_test_support::create_fake_rollout_with_token_usage;
|
||||||
use app_test_support::create_final_assistant_message_sse_response;
|
use app_test_support::create_final_assistant_message_sse_response;
|
||||||
use app_test_support::create_mock_responses_server_repeating_assistant;
|
use app_test_support::create_mock_responses_server_repeating_assistant;
|
||||||
|
use app_test_support::create_mock_responses_server_sequence;
|
||||||
use app_test_support::create_mock_responses_server_sequence_unchecked;
|
use app_test_support::create_mock_responses_server_sequence_unchecked;
|
||||||
use app_test_support::rollout_path;
|
use app_test_support::rollout_path;
|
||||||
use app_test_support::test_absolute_path;
|
use app_test_support::test_absolute_path;
|
||||||
@@ -2894,8 +2895,25 @@ async fn thread_resume_rejects_archived_session_by_id() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn thread_resume_keeps_paused_goal_paused() -> Result<()> {
|
async fn thread_resume_keeps_tool_paused_goal_paused() -> Result<()> {
|
||||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
let server = create_mock_responses_server_sequence(vec![
|
||||||
|
responses::sse(vec![
|
||||||
|
responses::ev_response_created("create-goal"),
|
||||||
|
responses::ev_function_call(
|
||||||
|
"create-goal-call",
|
||||||
|
"create_goal",
|
||||||
|
r#"{"objective":"keep polishing"}"#,
|
||||||
|
),
|
||||||
|
responses::ev_completed("create-goal"),
|
||||||
|
]),
|
||||||
|
responses::sse(vec![
|
||||||
|
responses::ev_response_created("pause-goal"),
|
||||||
|
responses::ev_function_call("pause-goal-call", "update_goal", r#"{"status":"paused"}"#),
|
||||||
|
responses::ev_completed("pause-goal"),
|
||||||
|
]),
|
||||||
|
create_final_assistant_message_sse_response("The goal is paused.")?,
|
||||||
|
])
|
||||||
|
.await;
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
mock_responses_config(&server.uri()).write(codex_home.path())?;
|
mock_responses_config(&server.uri()).write(codex_home.path())?;
|
||||||
let config_path = codex_home.path().join("config.toml");
|
let config_path = codex_home.path().join("config.toml");
|
||||||
@@ -2925,7 +2943,7 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> {
|
|||||||
thread_id: thread.id.clone(),
|
thread_id: thread.id.clone(),
|
||||||
client_user_message_id: None,
|
client_user_message_id: None,
|
||||||
input: vec![UserInput::Text {
|
input: vec![UserInput::Text {
|
||||||
text: "materialize this thread".to_string(),
|
text: "Create a goal to keep polishing, then pause it.".to_string(),
|
||||||
text_elements: Vec::new(),
|
text_elements: Vec::new(),
|
||||||
}],
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -2943,22 +2961,14 @@ async fn thread_resume_keeps_paused_goal_paused() -> Result<()> {
|
|||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let goal_id = mcp
|
let goal_id = mcp
|
||||||
.send_raw_request(
|
.send_raw_request("thread/goal/get", Some(json!({ "threadId": thread.id })))
|
||||||
"thread/goal/set",
|
|
||||||
Some(json!({
|
|
||||||
"threadId": thread.id,
|
|
||||||
"objective": "keep polishing",
|
|
||||||
"status": "paused",
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
let _goal: ThreadGoalSetResponse =
|
let goal: ThreadGoalGetResponse =
|
||||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(goal_id)).await??;
|
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(goal_id)).await??;
|
||||||
timeout(
|
assert_eq!(
|
||||||
DEFAULT_READ_TIMEOUT,
|
goal.goal.expect("goal should exist").status,
|
||||||
mcp.read_stream_until_notification_message("thread/goal/updated"),
|
ThreadGoalStatus::Paused
|
||||||
)
|
);
|
||||||
.await??;
|
|
||||||
mcp.clear_message_buffer();
|
mcp.clear_message_buffer();
|
||||||
|
|
||||||
let resume_id = mcp
|
let resume_id = mcp
|
||||||
|
|||||||
@@ -61,9 +61,9 @@ pub fn create_update_goal_tool() -> ToolSpec {
|
|||||||
let properties = BTreeMap::from([(
|
let properties = BTreeMap::from([(
|
||||||
"status".to_string(),
|
"status".to_string(),
|
||||||
JsonSchema::string_enum(
|
JsonSchema::string_enum(
|
||||||
vec![json!("complete"), json!("blocked")],
|
vec![json!("complete"), json!("blocked"), json!("paused")],
|
||||||
Some(
|
Some(
|
||||||
"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."
|
"Required. `paused` requires an explicit user request. 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(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -72,14 +72,14 @@ pub fn create_update_goal_tool() -> ToolSpec {
|
|||||||
ToolSpec::Function(ResponsesApiTool {
|
ToolSpec::Function(ResponsesApiTool {
|
||||||
name: UPDATE_GOAL_TOOL_NAME.to_string(),
|
name: UPDATE_GOAL_TOOL_NAME.to_string(),
|
||||||
description: r#"Update the existing goal.
|
description: r#"Update the existing goal.
|
||||||
Use this tool only to mark the goal achieved or genuinely blocked.
|
Set status to `paused` only at the user's explicit request to pause this goal, never on your own initiative. Ask if unclear; a later resume revokes that request. Report the returned status and stop goal work. Budget limits take precedence over pausing.
|
||||||
Set status to `complete` only when the objective has actually been achieved and no required work remains.
|
Set status to `complete` only when the objective has actually been achieved and no required work remains.
|
||||||
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.
|
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.
|
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`.
|
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 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.
|
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, budget-limit, or usage-limit a goal; those status changes are controlled by the user or system.
|
You cannot use this tool to 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."#
|
When marking a budgeted goal achieved with status `complete`, report the final token usage from the tool result to the user."#
|
||||||
.to_string(),
|
.to_string(),
|
||||||
strict: false,
|
strict: false,
|
||||||
|
|||||||
@@ -238,10 +238,10 @@ impl GoalToolExecutor {
|
|||||||
let args: UpdateGoalArgs = parse_arguments(invocation.function_arguments()?)?;
|
let args: UpdateGoalArgs = parse_arguments(invocation.function_arguments()?)?;
|
||||||
if !matches!(
|
if !matches!(
|
||||||
args.status,
|
args.status,
|
||||||
ThreadGoalStatus::Complete | ThreadGoalStatus::Blocked
|
ThreadGoalStatus::Complete | ThreadGoalStatus::Blocked | ThreadGoalStatus::Paused
|
||||||
) {
|
) {
|
||||||
return Err(FunctionCallError::RespondToModel(
|
return Err(FunctionCallError::RespondToModel(
|
||||||
"update_goal can only mark the existing goal complete or blocked; pause, resume, budget-limited, and usage-limited status changes are controlled by the user or system"
|
"update_goal can only mark the existing goal complete, blocked, or paused at the user's explicit request; resume, budget-limited, and usage-limited status changes are controlled by the user or system"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -249,9 +249,10 @@ impl GoalToolExecutor {
|
|||||||
self.account_active_goal_progress(
|
self.account_active_goal_progress(
|
||||||
match args.status {
|
match args.status {
|
||||||
ThreadGoalStatus::Complete => codex_state::GoalAccountingMode::ActiveOrComplete,
|
ThreadGoalStatus::Complete => codex_state::GoalAccountingMode::ActiveOrComplete,
|
||||||
ThreadGoalStatus::Blocked => codex_state::GoalAccountingMode::ActiveOrStopped,
|
ThreadGoalStatus::Blocked | ThreadGoalStatus::Paused => {
|
||||||
|
codex_state::GoalAccountingMode::ActiveOrStopped
|
||||||
|
}
|
||||||
ThreadGoalStatus::Active
|
ThreadGoalStatus::Active
|
||||||
| ThreadGoalStatus::Paused
|
|
||||||
| ThreadGoalStatus::UsageLimited
|
| ThreadGoalStatus::UsageLimited
|
||||||
| ThreadGoalStatus::BudgetLimited => unreachable!("status validated above"),
|
| ThreadGoalStatus::BudgetLimited => unreachable!("status validated above"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ Budget:
|
|||||||
|
|
||||||
The system has marked the goal as budget_limited, so do not start new substantive work for this goal. Wrap up this turn soon: summarize useful progress, identify remaining work or blockers, and leave the user with a clear next step.
|
The system has marked the goal as budget_limited, so do not start new substantive work for this goal. Wrap up this turn soon: summarize useful progress, identify remaining work or blockers, and leave the user with a clear next step.
|
||||||
|
|
||||||
Do not call update_goal unless the goal is actually complete.
|
Do not call update_goal unless the goal is actually complete or the user explicitly requests a pause; budget_limited takes precedence over paused.
|
||||||
@@ -53,4 +53,4 @@ Blocked audit:
|
|||||||
- Once the blocked threshold is satisfied, do not keep reporting that you are still blocked while leaving the goal active; call update_goal with status "blocked".
|
- Once the blocked threshold is satisfied, do not keep reporting that you are still blocked while leaving the goal active; call update_goal with status "blocked".
|
||||||
- Never use status "blocked" merely because the work is hard, slow, uncertain, incomplete, or would benefit from clarification.
|
- Never use status "blocked" merely because the work is hard, slow, uncertain, incomplete, or would benefit from clarification.
|
||||||
|
|
||||||
Do not call update_goal unless the goal is complete or the strict blocked audit above is satisfied. Do not mark a goal complete merely because the budget is nearly exhausted or because you are stopping work.
|
Call update_goal only after the completion or blocked audit passes, or when the user explicitly requests pausing this goal. For a requested pause, use status "paused", report the returned status, and stop goal work; never pause on your own initiative. Do not mark a goal complete merely because the budget is nearly exhausted or because you are stopping work.
|
||||||
@@ -13,4 +13,4 @@ Budget:
|
|||||||
|
|
||||||
Adjust the current turn to pursue the updated objective. Avoid continuing work that only served the previous objective unless it also helps the updated objective.
|
Adjust the current turn to pursue the updated objective. Avoid continuing work that only served the previous objective unless it also helps the updated objective.
|
||||||
|
|
||||||
Do not call update_goal unless the updated goal is actually complete.
|
Do not call update_goal unless the updated goal is actually complete or the user explicitly requests a pause.
|
||||||
@@ -1050,84 +1050,158 @@ async fn usage_limit_stale_turn_does_not_stop_current_goal() -> anyhow::Result<(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_goal_can_block_and_accounts_final_progress() -> anyhow::Result<()> {
|
async fn update_goal_can_stop_and_accounts_final_progress() -> anyhow::Result<()> {
|
||||||
|
for (status, token_budget, expected_status, state_status) in [
|
||||||
|
(
|
||||||
|
ThreadGoalStatus::Blocked,
|
||||||
|
100_i64,
|
||||||
|
ThreadGoalStatus::Blocked,
|
||||||
|
codex_state::ThreadGoalStatus::Blocked,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ThreadGoalStatus::Paused,
|
||||||
|
100,
|
||||||
|
ThreadGoalStatus::Paused,
|
||||||
|
codex_state::ThreadGoalStatus::Paused,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ThreadGoalStatus::Paused,
|
||||||
|
20,
|
||||||
|
ThreadGoalStatus::BudgetLimited,
|
||||||
|
codex_state::ThreadGoalStatus::BudgetLimited,
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let runtime = test_runtime().await?;
|
||||||
|
let thread_id = test_thread_id()?;
|
||||||
|
seed_thread_metadata(runtime.as_ref(), thread_id).await?;
|
||||||
|
let harness = GoalExtensionHarness::new(runtime.clone(), thread_id).await?;
|
||||||
|
harness.start_turn("turn-1", &TokenUsage::default()).await;
|
||||||
|
|
||||||
|
let tools = harness.tools();
|
||||||
|
let create_tool = tool_by_name(&tools, "create_goal");
|
||||||
|
create_tool
|
||||||
|
.handle(tool_call(
|
||||||
|
"create_goal",
|
||||||
|
"call-create-goal",
|
||||||
|
json!({ "objective": "ship goal extension backend", "token_budget": token_budget }),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
harness.sink.clear();
|
||||||
|
|
||||||
|
harness
|
||||||
|
.record_token_usage(
|
||||||
|
"turn-1",
|
||||||
|
&token_usage(
|
||||||
|
/*input_tokens*/ 20, /*cached_input_tokens*/ 5,
|
||||||
|
/*output_tokens*/ 8, /*reasoning_output_tokens*/ 2,
|
||||||
|
/*total_tokens*/ 30,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let update_tool = tool_by_name(&tools, "update_goal");
|
||||||
|
let invocation = tool_call(
|
||||||
|
"update_goal",
|
||||||
|
"call-update-goal",
|
||||||
|
json!({ "status": status }),
|
||||||
|
);
|
||||||
|
let output = update_tool.handle(invocation.clone()).await?;
|
||||||
|
let result = output.code_mode_result(&invocation.payload);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
json!({
|
||||||
|
"goal": {
|
||||||
|
"threadId": thread_id,
|
||||||
|
"objective": "ship goal extension backend",
|
||||||
|
"status": expected_status,
|
||||||
|
"tokenBudget": token_budget,
|
||||||
|
"tokensUsed": 23,
|
||||||
|
"timeUsedSeconds": 0,
|
||||||
|
"createdAt": result["goal"]["createdAt"],
|
||||||
|
"updatedAt": result["goal"]["updatedAt"],
|
||||||
|
},
|
||||||
|
"remainingTokens": (token_budget - 23).max(0),
|
||||||
|
"completionBudgetReport": serde_json::Value::Null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let goal = runtime
|
||||||
|
.thread_goals()
|
||||||
|
.get_thread_goal(thread_id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("goal should exist"))?;
|
||||||
|
assert_eq!(23, goal.tokens_used);
|
||||||
|
assert_eq!(state_status, goal.status);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
CapturedGoalEvent {
|
||||||
|
event_id: "call-update-goal".to_string(),
|
||||||
|
turn_id: Some("turn-1".to_string()),
|
||||||
|
status: if token_budget > 23 {
|
||||||
|
ThreadGoalStatus::Active
|
||||||
|
} else {
|
||||||
|
ThreadGoalStatus::BudgetLimited
|
||||||
|
},
|
||||||
|
tokens_used: 23,
|
||||||
|
},
|
||||||
|
CapturedGoalEvent {
|
||||||
|
event_id: "call-update-goal".to_string(),
|
||||||
|
turn_id: Some("turn-1".to_string()),
|
||||||
|
status: expected_status,
|
||||||
|
tokens_used: 23,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
harness.sink.goal_events()
|
||||||
|
);
|
||||||
|
harness
|
||||||
|
.record_token_usage(
|
||||||
|
"turn-1",
|
||||||
|
&token_usage(
|
||||||
|
/*input_tokens*/ 40, /*cached_input_tokens*/ 5,
|
||||||
|
/*output_tokens*/ 10, /*reasoning_output_tokens*/ 2,
|
||||||
|
/*total_tokens*/ 52,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
harness
|
||||||
|
.notify_tool_finish("turn-1", "call-shell", "shell")
|
||||||
|
.await;
|
||||||
|
harness.stop_turn("turn-1").await;
|
||||||
|
assert_eq!(
|
||||||
|
Some(goal),
|
||||||
|
runtime.thread_goals().get_thread_goal(thread_id).await?
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_goal_rejects_resume_and_system_limit_statuses() -> anyhow::Result<()> {
|
||||||
let runtime = test_runtime().await?;
|
let runtime = test_runtime().await?;
|
||||||
let thread_id = test_thread_id()?;
|
let thread_id = test_thread_id()?;
|
||||||
seed_thread_metadata(runtime.as_ref(), thread_id).await?;
|
seed_thread_metadata(runtime.as_ref(), thread_id).await?;
|
||||||
let harness = GoalExtensionHarness::new(runtime.clone(), thread_id).await?;
|
let tools = installed_tools(runtime, thread_id).await;
|
||||||
harness.start_turn("turn-1", &TokenUsage::default()).await;
|
|
||||||
|
|
||||||
let tools = harness.tools();
|
|
||||||
let create_tool = tool_by_name(&tools, "create_goal");
|
|
||||||
create_tool
|
|
||||||
.handle(tool_call(
|
|
||||||
"create_goal",
|
|
||||||
"call-create-goal",
|
|
||||||
json!({ "objective": "ship goal extension backend" }),
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
harness.sink.clear();
|
|
||||||
|
|
||||||
harness
|
|
||||||
.record_token_usage(
|
|
||||||
"turn-1",
|
|
||||||
&token_usage(
|
|
||||||
/*input_tokens*/ 20, /*cached_input_tokens*/ 5, /*output_tokens*/ 8,
|
|
||||||
/*reasoning_output_tokens*/ 2, /*total_tokens*/ 30,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
let update_tool = tool_by_name(&tools, "update_goal");
|
let update_tool = tool_by_name(&tools, "update_goal");
|
||||||
let invocation = tool_call(
|
for status in ["active", "budgetLimited", "usageLimited"] {
|
||||||
"update_goal",
|
let result = update_tool
|
||||||
"call-update-goal",
|
.handle(tool_call(
|
||||||
json!({ "status": "blocked" }),
|
"update_goal",
|
||||||
);
|
"call-update-goal",
|
||||||
let output = update_tool.handle(invocation.clone()).await?;
|
json!({ "status": status }),
|
||||||
let result = output.code_mode_result(&invocation.payload);
|
))
|
||||||
|
.await;
|
||||||
assert_eq!(
|
let error = match result {
|
||||||
result,
|
Ok(_) => panic!("agent must not set {status}"),
|
||||||
json!({
|
Err(error) => error,
|
||||||
"goal": {
|
};
|
||||||
"threadId": thread_id,
|
assert_eq!(
|
||||||
"objective": "ship goal extension backend",
|
error,
|
||||||
"status": "blocked",
|
FunctionCallError::RespondToModel(
|
||||||
"tokensUsed": 23,
|
"update_goal can only mark the existing goal complete, blocked, or paused at the user's explicit request; resume, budget-limited, and usage-limited status changes are controlled by the user or system".to_string()
|
||||||
"timeUsedSeconds": 0,
|
)
|
||||||
"createdAt": result["goal"]["createdAt"],
|
);
|
||||||
"updatedAt": result["goal"]["updatedAt"],
|
}
|
||||||
},
|
|
||||||
"remainingTokens": serde_json::Value::Null,
|
|
||||||
"completionBudgetReport": serde_json::Value::Null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
let goal = runtime
|
|
||||||
.thread_goals()
|
|
||||||
.get_thread_goal(thread_id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("goal should exist"))?;
|
|
||||||
assert_eq!(23, goal.tokens_used);
|
|
||||||
assert_eq!(codex_state::ThreadGoalStatus::Blocked, goal.status);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
vec![
|
|
||||||
CapturedGoalEvent {
|
|
||||||
event_id: "call-update-goal".to_string(),
|
|
||||||
turn_id: Some("turn-1".to_string()),
|
|
||||||
status: ThreadGoalStatus::Active,
|
|
||||||
tokens_used: 23,
|
|
||||||
},
|
|
||||||
CapturedGoalEvent {
|
|
||||||
event_id: "call-update-goal".to_string(),
|
|
||||||
turn_id: Some("turn-1".to_string()),
|
|
||||||
status: ThreadGoalStatus::Blocked,
|
|
||||||
tokens_used: 23,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
harness.sink.goal_events()
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user