Skip to content

[Agent Core] 为 Agent Loop 增加 LLM Hard Cutoff - #161

Merged
rojim666 merged 1 commit into
rojim666:mainfrom
z2Ace0107:codex/issue-69-pr02-llm-hard-cutoff-latest
Sep 12, 2026
Merged

rojim666 merged 1 commit into
rojim666:mainfrom
z2Ace0107:codex/issue-69-pr02-llm-hard-cutoff-latest

Conversation

@z2Ace0107

Copy link
Copy Markdown
Contributor

变更

这是 Issue #69 中可独立 Review 的 PR-02,针对 Python Agent Loop 增加 LLM hard cutoff。

  • 每次普通 LLM 请求前检查当前 Run 的剩余墙钟时间。
  • 使用 AgentLoop 外层可取消边界终止慢 Provider 请求。
  • deadline 到达后忽略迟到的 LLM 响应,不再启动新的工具、wrap-up 或 conclude 请求。
  • 排队但未启动的工具记录为 NOT_STARTED。
  • 将 deadline 终态统一为 interrupted / max_wall_clock_exceeded;普通 Provider 自身超时仍保留原有 llm_error 语义。
  • 更新 Provider contract、Anthropic/OpenAI、Smart、Tracing wrapper 和测试 doubles,传递 remaining_s。
  • deadline 中断不会触发 memory evolution。

验证

  • PR-02 focused tests:104 passed
  • Ruff:通过
  • 7 个生产源文件 mypy:通过
  • compileall:通过
  • git diff --check:通过
  • 全量 Python 套件:1310 passed, 2 skipped, 19 failed, 8 errors;剩余失败集中在主线已有的 Sztubuddy 提示词资源大小写、Prompt Harness、Markdown 链接和子进程导入问题,未出现 PR-02 focused 断言失败。

范围

本 PR 只处理 AgentLoop 的 LLM hard cutoff 和相关契约/测试迁移。Provider 内部 timeout/retry、retry/backoff、工具 timeout、权限等待、Subagent deadline、context compaction 和完整 cleanup 继续留在后续 PR。

Refs #69

@rojim666
rojim666 marked this pull request as ready for review September 12, 2026 08:52
@rojim666
rojim666 self-requested a review as a code owner September 12, 2026 08:52
Copilot AI lite review requested due to automatic review settings September 12, 2026 08:52
@rojim666
rojim666 merged commit 601faa6 into rojim666:main Sep 12, 2026
0 of 2 checks passed

Copilot AI left a comment

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.

🟡 Changes recommended

Unresolved moderate issues affect deadline terminal states, skipped-tool observability, wrap-up persistence, and cancellation tracing.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds LLM hard-cutoff support to the Python Agent Loop, including deadline propagation, cancellation, late-response suppression, and provider/test updates.

Changes:

  • Propagates remaining_s through providers and wrappers.
  • Cancels overdue LLM requests and normalizes deadline outcomes.
  • Prevents post-deadline follow-ups and records skipped tools.
  • Updates provider contracts and focused tests.
File summaries
File Summary
py-runtime/tests/unit/test_tracing_provider.py Verifies tracing deadline propagation.
py-runtime/tests/unit/test_smart_routing.py Verifies remaining-time forwarding.
py-runtime/tests/unit/test_runner.py Tests cutoff behavior and memory-evolution handling.
py-runtime/tests/unit/test_loop.py Tests loop deadline enforcement and cancellation.
py-runtime/tests/integration/test_deadline_foundation.py Covers deadline integration behavior.
py-runtime/src/sztu_code/core/trace/provider.py Forwards deadline data through tracing. Moderate issue (1 vote): cancellation can omit the LLM→CORE trace record.
py-runtime/src/sztu_code/core/loop.py Implements hard cutoff and skipped-tool handling. Moderate issues: skipped tools lack observable NOT_STARTED events (3 votes); wrap-up instructions can leave orphaned turns (2 votes); deadline races can produce incorrect terminal reasons in the loop, block paths, and conclusion paths (1 vote each).
py-runtime/src/sztu_code/core/llm/smart.py Forwards remaining budget through routing.
py-runtime/src/sztu_code/core/llm/provider.py Updates the Anthropic provider contract.
py-runtime/src/sztu_code/core/llm/openai_provider.py Updates the OpenAI provider contract.
py-runtime/src/sztu_code/core/llm/base.py Updates the provider protocol.
py-runtime/src/sztu_code/core/deadline.py Adds deadline exception and compatibility helpers.
Review details

Suppressed comments (5)

py-runtime/src/sztu_code/core/loop.py:1148

  • The conclude path has the same orphan-message race: the user instruction is appended before _chat(), but a deadline cancellation returns (False, "") without an assistant message. Since runner persistence writes context.messages after an interrupted run, resuming the session can replay this unmatched conclude prompt. Defer the append until _chat() succeeds or roll it back when RunDeadlineExceeded is caught.
        if context.wall_clock_exceeded():
            context.mark_interrupted(TerminationReason.MAX_WALL_CLOCK_EXCEEDED)
            return (False, "")
        context.messages.append({"role": "user", "content": instruction})

py-runtime/src/sztu_code/core/loop.py:450

  • This is the only deadline check before StepStartedEvent, intervention handling, and _admit_request(), all of which can await. If the deadline expires during that work and admission returns block, the later branch still marks TOKEN_BUDGET_EXHAUSTED instead of MAX_WALL_CLOCK_EXCEEDED, so a run that never starts an LLM request gets the wrong terminal reason. Re-check the deadline before non-LLM terminal branches and give the wall-clock reason precedence.
            if context.wall_clock_exceeded():
                context.mark_interrupted(TerminationReason.MAX_WALL_CLOCK_EXCEEDED)
                break

py-runtime/src/sztu_code/core/loop.py:1058

  • The deadline check is after the admission.action == "block" early return. If _admit_request() crosses the wall-clock deadline while publishing its admission event, this function returns at the block branch without setting max_wall_clock_exceeded, and the caller then labels the run exceeded_max_steps. Check the deadline before handling the block return so the deadline reason wins.
        if context.wall_clock_exceeded():
            context.mark_interrupted(TerminationReason.MAX_WALL_CLOCK_EXCEEDED)
            return ""

py-runtime/src/sztu_code/core/loop.py:1147

  • The same race exists in _conclude: _admit_request() can cross the deadline before this check, then the earlier admission.action == "block" return bypasses the wall-clock status and the caller treats the result as an ordinary max-steps conclusion. Move the deadline check before that early return so max_wall_clock_exceeded remains the terminal reason.
        if context.wall_clock_exceeded():
            context.mark_interrupted(TerminationReason.MAX_WALL_CLOCK_EXCEEDED)
            return (False, "")

py-runtime/src/sztu_code/core/trace/provider.py:78

  • When the new outer hard-cutoff cancels this await, execution jumps out before the LLM→CORE record below is emitted, leaving only a CORE→LLM api_call in the trace. The resulting trace cannot distinguish a deadline cutoff from an abrupt provider failure or show cleanup outcome, so the new cancellation path loses the timeout observability expected for this feature. Add an exception/deadline trace record around the inner call while preserving cancellation propagation.
        add_remaining_s(self._inner.chat, inner_kwargs, remaining_s)
        result = await self._inner.chat(
            messages, tool_schemas, bus, run_id, **inner_kwargs
  • Files reviewed: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +187 to +193
def _deadline_skipped_tool_result() -> ToolResult:
return ToolResult(
content="Tool call skipped because the run wall-clock deadline was reached.",
is_error=True,
error_type="deadline_exceeded",
execution_state=ToolExecutionState.NOT_STARTED,
)
Comment on lines +1056 to +1059
if context.wall_clock_exceeded():
context.mark_interrupted(TerminationReason.MAX_WALL_CLOCK_EXCEEDED)
return ""
context.messages.append({"role": "user", "content": instruction})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants