Add configurable goal token budget limits (#37878)

## What changed

- Add `goals.max_goal_token_budget` as a positive-integer configuration setting.
- Use the configured maximum as the default budget for new goals and when `tokenBudget` is reset to `null`.
- Reject goal creation and updates whose token budget exceeds the configured maximum, including requests through goal tools and `thread/goal/set`.
- Respect managed configuration precedence and per-thread configuration overrides.

## Testing

- Cover configuration parsing and managed overrides.
- Cover defaulting, resetting, and rejecting oversized budgets through the goal service, goal tools, and app-server API.

GitOrigin-RevId: f8d7e6418cdc237d454c8cf47bb32ba0d44a60cf
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-10 21:00:00 +00:00
1 parent a603d7ca5c
commit a9dee37f9c
14 files changed
+391 -26

No files matched your search

+20 -2
View File
@@ -36,6 +36,7 @@ pub(crate) struct GoalToolExecutor {
analytics: GoalAnalytics,
event_emitter: GoalEventEmitter,
metrics: GoalMetrics,
max_goal_token_budget: Option<i64>,
}
#[derive(Clone, Copy)]
@@ -89,6 +90,7 @@ impl GoalToolExecutor {
analytics,
event_emitter,
metrics,
max_goal_token_budget: None,
}
}
@@ -99,6 +101,7 @@ impl GoalToolExecutor {
analytics: GoalAnalytics,
event_emitter: GoalEventEmitter,
metrics: GoalMetrics,
max_goal_token_budget: Option<i64>,
) -> Self {
Self {
kind: GoalToolKind::Create,
@@ -108,6 +111,7 @@ impl GoalToolExecutor {
analytics,
event_emitter,
metrics,
max_goal_token_budget,
}
}
@@ -127,6 +131,7 @@ impl GoalToolExecutor {
analytics,
event_emitter,
metrics,
max_goal_token_budget: None,
}
}
}
@@ -185,7 +190,9 @@ impl GoalToolExecutor {
request.objective = request.objective.trim().to_string();
validate_thread_goal_objective(&request.objective)
.map_err(FunctionCallError::RespondToModel)?;
validate_goal_budget(request.token_budget).map_err(FunctionCallError::RespondToModel)?;
request.token_budget = request.token_budget.or(self.max_goal_token_budget);
validate_goal_budget(request.token_budget, self.max_goal_token_budget)
.map_err(FunctionCallError::RespondToModel)?;
let goal = self
.state_db
@@ -397,12 +404,23 @@ where
.map_err(|err| FunctionCallError::RespondToModel(err.to_string()))
}
pub(crate) fn validate_goal_budget(value: Option<i64>) -> Result<(), String> {
pub(crate) fn validate_goal_budget(
value: Option<i64>,
max_goal_token_budget: Option<i64>,
) -> Result<(), String> {
if let Some(value) = value
&& value <= 0
{
return Err("goal budgets must be positive when provided".to_string());
}
if let Some(value) = value
&& let Some(max_goal_token_budget) = max_goal_token_budget
&& value > max_goal_token_budget
{
return Err(format!(
"goal token budget {value} exceeds the maximum allowed goal token budget of {max_goal_token_budget}"
));
}
Ok(())
}