refactor: extract shared agent loop engine - #10
Merged
Merged
Conversation
zhanghanduo
force-pushed
the
refactor/agent-loop-engine
branch
5 times, most recently
from
September 1, 2026 02:30
6a5f713 to
81c32fd
Compare
zhanghanduo
force-pushed
the
refactor/agent-loop-engine
branch
from
September 1, 2026 02:34
81c32fd to
8266155
Compare
…ooks Review of the extracted agent-loop engine found the "every dropped assistant tool_call_id gets an answer" invariant held on the normal paths but not on observer-intervention paths, plus several hook boundaries where a host mistake became an engine failure. Correctness: - tool_exec: an interrupt waiter resolving to False (no observer asked to interrupt) or raising cancelled the fan-in tool anyway and reported its collected work as "interrupted by a follow-up user message". Only a genuine request now cancels, and a tool that finished in the same wake-up keeps its result. - agent_loop: skip_tool_execution, and continue_to_next_turn without pop_last_message, left the assistant tool_calls unanswered -> hard 400 on the next request. Both now synthesise answers, inserted beneath the assistant message so an injected user message cannot split the pair. - agent_loop: synthetic and executed tool_call_ids were numbered off different lists and collided once an observer skipped an earlier text-parsed call. Ids are now assigned once, before the split. - tool_exec: a non-positive result cap (the usual "unlimited" sentinel) sliced from the end and silently dropped every result's tail. - model_profile: a non-string `format` in the registry raised TypeError and aborted the whole load; a missing PyYAML disabled registry inference in total silence. Both are now per-row warnings / a loud error, and pyyaml is declared as a `registry` extra. - model_profile: tag-format models that fill the typed reasoning channel AND leave inline <think> tags duplicated the reasoning in history and kept it there with thinking_in_history=False. - agent_loop: the zero-filled usage fallback overwrote the previous turn's real token counts, so the context-overflow guard computed its estimate off 0 and never fired. - agent_loop: _pop_last_assistant_turn was a silent no-op once a user message had been injected mid-turn, letting a rollback plus continue_to_next_turn replay the same turn until the attempt buffer ran out. Boundaries: - tool_exec: resolve_timeout, on_call, transform_batch and the result formatters ran outside the guarded region, so one raising metering hook killed the whole batch and the surrounding loop. They now fall back to core behavior with a warning. - tool_exec: the fan-in tool names eligible for interruption are now a ToolExecutionHooks field instead of hardcoded product names. - agent_loop: bind_session and sticky_session_enabled both owned session affinity and resolved silently; bind_session now explicitly wins, warns, and is typed LLMClient -> LLMClient rather than Any -> Any. - agent_loop: HistoryPolicy.tool_result_max_chars was read by nobody. It now applies when the caller actually supplies a policy (never by dataclass default, which would newly truncate existing runs). - model_profile: MessageNormalizer.to_history declared three parameters while the engine calls it with four, so the Protocol was unimplementable. 21 regression tests, each verified to fail before its fix. 404 passed, ruff clean, pyright strict clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
Compatibility decisions
AgentLoopHooksowns session affinity, wall deadlines, provider-chain state, execution scope, cancellation cleanup, and spill-reference detectionToolExecutionHooksowns timeout calculation/waits, per-call context, metering, result transforms, and batch budgetingkeep_unknown_native_companions; unknown-only batches always remain executable for corrective errorsValidation
uv run ruff check agent_core testsuv run pyright agent_coreuv run pytest -q— 382 passeduv build