refactor(run_agent): extract AIAgent internals into agent/ modules (16k→3.8k lines, 76% reduction) - #27248
Merged
Conversation
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
run_agent.pyfrom 16,083 → 3,821 lines (-12,262, 76% reduction), redistributed across 14 cohesiveagent/*.pymodules. Behavior unchanged: every extraction keeps a thin forwarder method onAIAgentso call sites and test patches (patch("run_agent.OpenAI", ...),patch("run_agent.handle_function_call", ...), etc.) keep working.Why
run_agent.pyhad grown to 16k lines.run_conversationalone was 3,877 lines.__init__1,381. The file was painful to read, painful to grep, and painful to test-locate. AGENTS.md called it out as one of five "do not delegate edits on this file" load-bearing files for exactly that reason.This refactor leaves
AIAgentas the orchestrator — thin forwarder methods that delegate to focused modules inagent/.Changes
15 atomic commits, each individually green against
tests/run_agent/ + tests/agent/:agent/message_sanitization.pyagent/tool_dispatch_helpers.pyagent/process_bootstrap.py+agent/iteration_budget.pyagent/background_review.pyagent/conversation_compression.pyagent/system_prompt.pyagent/tool_executor.pyagent/stream_diag.pyagent/chat_completion_helpers.pyagent/codex_runtime.pyagent/agent_runtime_helpers.pyagent/conversation_loop.pyrun_conversation— the agent loopagent/agent_init.py__init__Architectural pattern
Every extraction follows the same shape, by design:
name(agent, ...)that takes the parentAIAgentas the first arg.self.Xbecomesagent.Xvia word-boundary regex (~163 occurrences inrun_conversationalone).AIAgentkeeps a thin forwarder method preserving the original signature.run_agent.X(_set_interrupt,handle_function_call,OpenAI,cleanup_vm,logger,AIAgent.Xclass attrs) resolve through a_ra()lazy reference inside each extracted module so the patch contract is preserved.inspect.getsource(AIAgent.run_conversation), etc.) updated to point at the new module location.The
_ra()indirection is doing real work — without it, dozens of test patches would silently miss the extracted code.Validation
run_agent.pylinestests/run_agent/+tests/agent/passingtests/run_agent/+tests/agent/failingThe one persistent failure (
tests/agent/test_auxiliary_client.py::TestGetTextAuxiliaryClient::test_custom_endpoint_uses_codex_wrapper_when_runtime_requests_responses_api) was confirmed failing onmainbefore any commit in this branch — it's unrelated to this refactor.Live E2E
End-to-end-verified from the worktree against real providers:
openai/gpt-5.4via OpenRouter (chat-completions, streaming) — text response, terminal tool, read_file, write_file, web_search, multi-tool chain, session resumeanthropic/claude-sonnet-4.6via OpenRouter (native Anthropic format path)moonshotai/kimi-k2-thinkingvia OpenRouter (reasoning-content path)Plus AIAgent direct-instantiation smoke (
__init__, forwarder methods all present,IterationBudgetconsume/refund, sanitization roundtrips).Test-file updates
Six tests were updated alongside the refactor — all are structural guards that scan the agent loop source for known patterns, and the patterns moved:
tests/run_agent/test_run_agent.py— 5inspect.getsource(AIAgent.run_conversation)rerouted toagent.conversation_loop.run_conversation;TestAnthropicInterruptHandlerrerouted to the extracted streaming/non-streaming callers;TestMemoryNudgeCounterPersistence/TestMemoryProviderTurnStartaccept eitherself.Xoragent.Xtests/run_agent/test_memory_nudge_counter_hydration.py— scan bothrun_agent.pyandagent/conversation_loop.pytests/run_agent/test_jsondecodeerror_retryable.py— scan bothtests/run_agent/test_tool_executor_contextvar_propagation.py— AST guard scans bothrun_agent.pyandagent/tool_executor.pyRisk
tests/suite was not run end-to-end (onlytests/run_agent/ + tests/agent/); CI is the source of truth for cross-file regressions._ra()lazy-reference pattern is the load-bearing piece that keeps test patches working. If a future test patches a newrun_agent.Xname, the extracted module's reference to that name may need routing.How to review
Easiest path: walk the commits in order. Each one tells you what was extracted and which tests it ran. The pattern is identical across all 15 — once you've reviewed the first two (sanitization, tool-dispatch helpers), the rest are mechanical applications of the same template.