Skip to content

goal: pause continuation loops on usage limits and blockers#23094

Merged
etraut-openai merged 11 commits into
mainfrom
etraut/goal-usage-limited-blocked
May 18, 2026
Merged

goal: pause continuation loops on usage limits and blockers#23094
etraut-openai merged 11 commits into
mainfrom
etraut/goal-usage-limited-blocked

Conversation

@etraut-openai
Copy link
Copy Markdown
Collaborator

@etraut-openai etraut-openai commented May 17, 2026

Addresses #22833, #22245, #23067

Why

/goal can keep synthesizing turns even when the next turn cannot make meaningful progress. Hard usage exhaustion can replay failing turns, and repeated permission or external-resource blockers can keep burning tokens while waiting for user or system intervention.

What changed

  • Add resumable blocked and usageLimited goal states. As with paused, goal continuation stops with these states.
  • Move to usageLimited after usage-limit failures.
  • Allow the built-in update_goal tool to set blocked only under explicit repeated-impasse guidance. Updated goal continuation prompt to specify that agent should use blocked only when it has made at least three attempts to get past an impasse.

Most of the files touched by this PR are because of the small app server protocol update.

Validation

I manually reproduced a number of situations where an agent can run into a true impasse and verified that it properly enters blocked state. I then resumed and verified that it once again entered blocked state several turns later if the impasse still exists.

I also manually reproduced the usage-limit condition by creating a simulated responses API endpoint that returns 429 errors with the appropriate error message. Verified that the goal runtime properly moves the goal into usageLimited state and TUI UI updates appropriately. Verified that /goal resume resumes (and immediately goes back into ussageLImited state if appropriate).

Follow-up PRs

Small changes will be needed to the GUI clients to properly handle the two new states.

@etraut-openai etraut-openai requested a review from a team as a code owner May 17, 2026 00:00
@etraut-openai
Copy link
Copy Markdown
Collaborator Author

Manual testing note: I verified the usage-limit path with a local simulated Responses server that returned HTTP 429 errors, and confirmed the goal transitions into the usage-limited state instead of continuing to synthesize failing turns.

@jif-oai
Copy link
Copy Markdown
Collaborator

jif-oai commented May 18, 2026

@codex review

Copy link
Copy Markdown
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok after my comments

if args.status != ThreadGoalStatus::Complete {
if !matches!(
args.status,
ThreadGoalStatus::Complete | ThreadGoalStatus::Blocked
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this lets blocked overwrite a just-written budget_limited status. update_goal accounts the turn first, so if that accounting crosses the token budget we write budget_limited, then this path calls set_thread_goal(Blocked ...) and the state update only preserves budget-limited for paused

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good catch. Fixed.

AppThreadGoalStatus::Paused => "Commands: /goal edit, /goal resume, /goal clear",
AppThreadGoalStatus::Paused
| AppThreadGoalStatus::Blocked
| AppThreadGoalStatus::UsageLimited => "Commands: /goal edit, /goal resume, /goal clear",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now treat blocked and usage-limited as resumable in the menu/footer, but the thread-resume prompt path still only checks ThreadGoalStatus::Paused.
Am I missing something?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that we wouldn't want to resume in those cases, but it makes sense to present the user with a choice. If they haven't resolved the block or usage limit, then they can always choose to cancel the resume.

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5da03a9e57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/core/src/goals.rs
Comment thread codex-rs/core/src/session/turn.rs
Comment thread codex-rs/core/src/tools/handlers/goal/update_goal.rs
@etraut-openai
Copy link
Copy Markdown
Collaborator Author

@codex review

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a67e33a1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.await?;
let Some(goal) = state_db
.thread_goals()
.usage_limit_active_thread_goal(self.conversation_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind usage-limit updates to the turn's goal

When a usage-limit error from an old turn races with thread/goal/set replacing or resuming the goal, this unqualified update marks whichever goal is currently active or budget-limited as usageLimited. The accounting path above already uses the turn's expected goal id, but this call discards it, so a newly created/resumed goal can be stopped even though it never hit the usage limit; please filter this status update by the turn's active goal id as the other goal mutations do with expected_goal_id.

Useful? React with 👍 / 👎.

@etraut-openai etraut-openai merged commit 0d344ac into main May 18, 2026
31 checks passed
@etraut-openai etraut-openai deleted the etraut/goal-usage-limited-blocked branch May 18, 2026 18:28
@github-actions github-actions Bot locked and limited conversation to collaborators May 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants