Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions agent/agent_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,9 +1084,6 @@ def init_agent(
# models to "give up" prematurely on complex tasks (#7915).
agent._budget_exhausted_injected = False
agent._budget_grace_call = False
# True only during the one post-budget grace turn; read by the tool
# dispatchers to refuse side-effecting tools then (Guard D-core).
agent._in_budget_grace = False

# Optional wall-clock run budget (seconds per run_conversation turn).
# Explicit constructor arg wins; else resolved from config.yaml
Expand Down
114 changes: 0 additions & 114 deletions agent/budget_grace_gate.py

This file was deleted.

12 changes: 0 additions & 12 deletions agent/conversation_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -2652,20 +2652,8 @@ def run_conversation(
# Grace call: the budget is exhausted but we gave the model one
# more chance. Consume the grace flag so the loop exits after
# this iteration regardless of outcome.
#
# ``_in_budget_grace`` is recomputed every iteration (default False) and
# set True ONLY for the grace turn. The tool dispatchers read it to
# refuse side-effecting tools during the grace turn (deny-by-default,
# read-only allowlist) — see ``agent/budget_grace_gate.py`` (Guard
# D-core). Refusing a call must NOT re-arm ``_budget_grace_call``: the
# flag is already cleared here, so the loop exits after this iteration
# whether the model's tool calls execute or are refused. A
# deny-that-loops would itself be a runaway, so the gate only blocks
# execution; it never extends the loop.
agent._in_budget_grace = False
if agent._budget_grace_call:
agent._budget_grace_call = False
agent._in_budget_grace = True
elif not agent.iteration_budget.consume():
_turn_exit_reason = "budget_exhausted"
if not agent.quiet_mode:
Expand Down
7 changes: 0 additions & 7 deletions agent/fork_ext/tool_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import json
from typing import Any

from agent.budget_grace_gate import grace_block_message, is_readonly_grace_tool


def tool_search_scoped_names(agent) -> frozenset:
try:
Expand Down Expand Up @@ -80,11 +78,6 @@ def resolve_tool_search_unwrap(agent, function_name: str, function_args: dict[st


def pre_tool_block_from_builtin_gate(agent, function_name: str, tool_scope_block: str | None) -> dict[str, str] | None:
if getattr(agent, "_in_budget_grace", False) and not is_readonly_grace_tool(function_name):
return {
"message": grace_block_message(function_name),
"error_type": "budget_grace_block",
}
if tool_scope_block is not None:
return {
"message": tool_scope_block,
Expand Down
27 changes: 5 additions & 22 deletions agent/tool_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@
_detect_tool_failure,
)
from agent.tool_guardrails import ToolGuardrailDecision
from agent.budget_grace_gate import (
grace_block_result,
)
from agent.fork_ext.tool_gate import (
pre_tool_block_from_builtin_gate,
resolve_tool_search_unwrap,
Expand Down Expand Up @@ -783,13 +780,8 @@ def _advance_start_order(callback=None) -> None:
begin_execution(callback)

# ── Block ladder ────────────────────────────────────────────────
# parity NOTE (upstream->fork merge 2026-08-08): the fork-only
# budget-grace gate (Guard D-core) used to live in each dispatcher.
# Upstream moved plugin/scope/guardrail blocking here, so the gate is
# re-grafted at the head of THIS ladder — the single choke point both
# dispatchers funnel through — rather than duplicated per path. It
# runs FIRST so an exhausted-budget turn is refused before any plugin
# hook or guardrail bookkeeping observes the call.
# Fork tool-search scope block first (agent/fork_ext/tool_gate.py),
# then plugin pre_tool_call hooks, then guardrails.
block_message = pre_tool_block_from_builtin_gate(
agent, function_name, scope_block
)
Expand Down Expand Up @@ -841,18 +833,9 @@ def _resolve_pre_tool_block():
_advance_start_order()
state["blocked"] = True
if block_message is not None:
# parity NOTE (upstream->fork merge 2026-08-08): the fork's
# budget-grace refusal must carry the budget_grace_block
# metadata key (asserted by test_block_message_and_result_shape
# and test_grace_block_result_metadata_key_present_in_real_dispatch),
# so it uses the shared grace_block_result() rather than the
# generic {"error": ...} envelope every other block emits.
if block_error_type == "budget_grace_block":
result = grace_block_result(function_name)
else:
result = json.dumps(
{"error": block_message}, ensure_ascii=False
)
result = json.dumps(
{"error": block_message}, ensure_ascii=False
)
error_type = block_error_type
error_message = block_message
else:
Expand Down
Loading
Loading