Repository navigation
[Agent Core] 为 Agent Loop 增加 LLM Hard Cutoff - #161
Merged
rojim666 merged 1 commit intoSep 12, 2026
Merged
Conversation
Signed-off-by: Ace <[email protected]>
Contributor
There was a problem hiding this comment.
🟡 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_sthrough 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 writescontext.messagesafter an interrupted run, resuming the session can replay this unmatched conclude prompt. Defer the append until_chat()succeeds or roll it back whenRunDeadlineExceededis 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 returnsblock, the later branch still marksTOKEN_BUDGET_EXHAUSTEDinstead ofMAX_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 settingmax_wall_clock_exceeded, and the caller then labels the runexceeded_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 earlieradmission.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 somax_wall_clock_exceededremains 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→CORErecord below is emitted, leaving only aCORE→LLMapi_callin 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}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
变更
这是 Issue #69 中可独立 Review 的 PR-02,针对 Python Agent Loop 增加 LLM hard cutoff。
NOT_STARTED。interrupted / max_wall_clock_exceeded;普通 Provider 自身超时仍保留原有llm_error语义。remaining_s。验证
104 passedcompileall:通过git diff --check:通过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