Skip to content

refactor: modularize event loop node class methods and helpers #6633

Merged
TimothyZhang7 merged 15 commits into
aden-hive:mainfrom
sundaram2021:refactor/event-loop-node-modularization
Mar 26, 2026
Merged

refactor: modularize event loop node class methods and helpers #6633
TimothyZhang7 merged 15 commits into
aden-hive:mainfrom
sundaram2021:refactor/event-loop-node-modularization

Conversation

@sundaram2021

Copy link
Copy Markdown
Contributor

Description

Modularizes EventLoopNode by extracting helper responsibilities into focused event_loop modules while preserving existing behavior and public compatibility. The orchestration flow remains in event_loop_node.py, and the extracted logic is wired back through thin delegation methods.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)

Related Issues

Fixes #6596

Changes Made

  • Extracted event loop helper logic into focused modules for compaction, cursor persistence, event publishing, judge evaluation, stall detection, synthetic tools, subagent execution, and tool result handling.
  • Updated EventLoopNode to delegate to the extracted modules while keeping the main orchestration logic in place.
  • Preserved compatibility for subagent escalation routing, compaction limits, output learning/spillover behavior, and existing imports/behavior expected by tests.

Testing

Describe the tests you ran to verify your changes:

  • Unit tests pass (cd core && pytest tests/)
  • Lint passes (cd core && ruff check .)
  • Manual testing performed

Targeted verification performed:

  • tests/test_event_loop_node.py
  • tests/test_event_loop_wiring.py
  • tests/test_node_conversation.py::TestIsContextTooLargeError
  • tests/test_node_conversation.py::TestLlmCompact
  • tests/test_subagent.py -k "EscalationReceiver or SubagentJudge or wait_for_response_registers_receiver_in_registry"
  • tests/test_subagent_escalation_e2e.py::test_escalation_cleanup_after_completion
  • ruff check framework/graph/event_loop_node.py framework/graph/event_loop

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Screenshots (if applicable)

Not applicable.

Extract EventLoopNode helper logic into focused event_loop modules while keeping the node responsible for orchestration.

Preserve the existing behavior and compatibility for compaction, event publishing, cursor persistence, synthetic tools, judge evaluation, stall detection, tool result handling, and subagent escalation wiring.
Apply Ruff formatting to the extracted event loop modules, the EventLoopNode wrappers, and the OpenRouter key check script so the lint CI format check passes cleanly.
@sundaram2021 sundaram2021 changed the title Modularize event loop helpers refactor: modularize event loop node class methods and helpers Mar 19, 2026
@sundaram2021

sundaram2021 commented Mar 20, 2026

Copy link
Copy Markdown
Contributor Author

please review it and let me know if any changes needed

cc: @TimothyZhang7 @Hundao @bryanadenhq

@sundaram2021

sundaram2021 commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

hey @TimothyZhang7 please review this PR (assigned by yourself)
and let me know if there's any changes needed .
thanks

@TimothyZhang7 TimothyZhang7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The decomposition direction is right — event_loop_node.py was genuinely too large, and the 8 submodule boundaries (compaction, cursor_persistence, event_publishing, judge_pipeline, stall_detector, subagent_executor, synthetic_tools, tool_result_handler) are cohesive. Backward compatibility is preserved. But there are structural issues that should be addressed before merge.

Circular import not resolved

judge_pipeline.py imports back from the parent it was extracted from:

# judge_pipeline.py
from framework.graph.event_loop_node import JudgeVerdict

JudgeVerdict should be moved to a shared types file (e.g. framework/graph/event_loop/types.py) so submodules don't depend on their parent.

Any used as a circular-import workaround

subagent_executor.py, compaction.py, and judge_pipeline.py type key parameters as Any with comments like # NodeContext, # LoopConfig, # OutputAccumulator. event_publishing.py shows this is avoidable — it imports EventBus and NodeContext directly. The others should use TYPE_CHECKING guards to get real types without runtime circular imports.

Callback injection as circular-import workaround

compaction.compact() takes build_message_inventory_fn, publish_context_usage_fn, and write_debug_log_fn as optional Callable parameters. This is dependency injection to dodge a circular import — better to fix the import structure so the function can call them directly.

Local imports inside delegation methods

def _build_ask_user_tool(self) -> Tool:
    from framework.graph.event_loop.synthetic_tools import build_ask_user_tool
    return build_ask_user_tool()

Method-level imports work but are a symptom of the same unresolved circular import architecture.

Minor

  • Em dash changed to hyphen in a user-visible return string (— description:- description:) — a semantic change embedded in a refactor.
  • core/frontend/package-lock.json appears to be a spurious change unrelated to this refactor.
  • Several meaningful inline comments and docstrings were deleted from event_loop_node.py that documented non-obvious behavior.

The core issue across items 1–4 is the same: the import graph between event_loop_node.py and its submodules wasn't fully resolved, so the circular dependency was patched over rather than fixed. Resolving the JudgeVerdict circular dep and using TYPE_CHECKING imports would address most of it.

@sundaram2021

Copy link
Copy Markdown
Contributor Author

thanks for the review , I'm looking into these things

@sundaram2021

Copy link
Copy Markdown
Contributor Author

Review

The decomposition direction is right — event_loop_node.py was genuinely too large, and the 8 submodule boundaries (compaction, cursor_persistence, event_publishing, judge_pipeline, stall_detector, subagent_executor, synthetic_tools, tool_result_handler) are cohesive. Backward compatibility is preserved. But there are structural issues that should be addressed before merge.

Circular import not resolved

judge_pipeline.py imports back from the parent it was extracted from:

# judge_pipeline.py
from framework.graph.event_loop_node import JudgeVerdict

JudgeVerdict should be moved to a shared types file (e.g. framework/graph/event_loop/types.py) so submodules don't depend on their parent.

Any used as a circular-import workaround

subagent_executor.py, compaction.py, and judge_pipeline.py type key parameters as Any with comments like # NodeContext, # LoopConfig, # OutputAccumulator. event_publishing.py shows this is avoidable — it imports EventBus and NodeContext directly. The others should use TYPE_CHECKING guards to get real types without runtime circular imports.

Callback injection as circular-import workaround

compaction.compact() takes build_message_inventory_fn, publish_context_usage_fn, and write_debug_log_fn as optional Callable parameters. This is dependency injection to dodge a circular import — better to fix the import structure so the function can call them directly.

Local imports inside delegation methods

def _build_ask_user_tool(self) -> Tool:
    from framework.graph.event_loop.synthetic_tools import build_ask_user_tool
    return build_ask_user_tool()

Method-level imports work but are a symptom of the same unresolved circular import architecture.

Minor

  • Em dash changed to hyphen in a user-visible return string (— description:- description:) — a semantic change embedded in a refactor.
  • core/frontend/package-lock.json appears to be a spurious change unrelated to this refactor.
  • Several meaningful inline comments and docstrings were deleted from event_loop_node.py that documented non-obvious behavior.

The core issue across items 1–4 is the same: the import graph between event_loop_node.py and its submodules wasn't fully resolved, so the circular dependency was patched over rather than fixed. Resolving the JudgeVerdict circular dep and using TYPE_CHECKING imports would address most of it.

I have addressed most of the issues . please review it and let me know if any further changes needed

cc: @TimothyZhang7 , thanks

@levxn levxn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small fixes, address them, thanks!

Comment thread core/framework/graph/event_loop_node.py Outdated
@@ -81,28 +139,14 @@ async def _describe_images_as_text(image_content: list[dict[str, Any]]) -> str |
if description:
count = len(image_content)
label = "image" if count == 1 else f"{count} images"
return f"[{label} attached description: {description}]"
return f"[{label} attached \u2014 description: {description}]"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert to keep this a clean refactor with no unintended diffs.

Comment thread core/framework/graph/event_loop_node.py Outdated
@@ -81,28 +139,14 @@ async def _describe_images_as_text(image_content: list[dict[str, Any]]) -> str |
if description:
count = len(image_content)
label = "image" if count == 1 else f"{count} images"
return f"[{label} attached description: {description}]"
return f"[{label} attached \u2014 description: {description}]"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return f"[{label} attached \u2014 description: {description}]"
return f"[{label} attached description: {description}]"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These "peer": true additions are unrelated to this backend refactor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverted

Comment thread core/framework/graph/event_loop_node.py Outdated
char_limit=self._LLM_COMPACT_CHAR_LIMIT,
max_depth=self._LLM_COMPACT_MAX_DEPTH,
max_context_tokens=self._config.max_context_tokens,
)

async def _llm_compact_split(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is a deadcode, can be removed, and also its relevant import in line 28.

@sundaram2021 sundaram2021 Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__llm_compact_split is used in compaction.py line number 182
for summarising the messages when context limit hits

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the dead _llm_compact_split import and delegation wrapper in ore/framework/graph/event_loop_node.py. The one you are using in compaction.py has been declared separately inside its respective file, check properly.

@sundaram2021
sundaram2021 requested a review from levxn March 26, 2026 02:47
@sundaram2021

Copy link
Copy Markdown
Contributor Author

Small fixes, address them, thanks!

done

cc: @levxn

@sundaram2021
sundaram2021 force-pushed the refactor/event-loop-node-modularization branch from 1eac28d to 9667dd2 Compare March 26, 2026 16:27

@levxn levxn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase off main cleanly and ensure only the EventLoopNode modularization changes are in this PR. As-is, this cannot be reviewed or merged safely.

@sundaram2021
sundaram2021 requested a review from levxn March 26, 2026 19:23
@sundaram2021

Copy link
Copy Markdown
Contributor Author

Please rebase off main cleanly and ensure only the EventLoopNode modularization changes are in this PR. As-is, this cannot be reviewed or merged safely.

done , please look now
cc: @levxn

@levxn levxn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to merge, @TimothyZhang7 you can confirm from your end as well

@TimothyZhang7 TimothyZhang7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@TimothyZhang7
TimothyZhang7 merged commit 38b79ed into aden-hive:main Mar 26, 2026
7 checks passed
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.

Refactor EventLoopNode into focused modules to improve maintainability and testability

3 participants