name: 'Feature Request'
about: 'Suggest an idea for this project'
title: 'refactor: Unify Tool Execution Logic Across Interactive, Non-Interactive, and ACP Modes'
labels: ['type/feature-request', 'status/needs-triage', 'area/architecture']
What would you like to be added?
Refactor the tool execution architecture to unify the duplicated logic across three execution modes:
Current Architecture
The Qwen Code CLI currently has three execution modes with significantly different implementations:
| Mode |
Entry Point |
Reasoning Loop |
Tool Scheduler |
Key Files |
| Interactive |
startInteractiveUI() |
AgentCore.runReasoningLoop() |
CoreToolScheduler |
agent-core.ts, coreToolScheduler.ts |
| Non-Interactive |
runNonInteractive() |
Custom while(true) loop |
CoreToolScheduler |
nonInteractiveCli.ts, nonInteractiveToolExecutor.ts |
| ACP Mode |
runAcpAgent() |
Session.#executePrompt() |
Direct tool.execute() |
acp-integration/session/Session.ts |
Key Differences
-
Tool Execution Path
- Interactive & Non-Interactive: Both use
CoreToolScheduler via executeToolCall()
- ACP Mode: Directly calls
tool.build().execute() in Session.runTool()
-
Reasoning Loop
- Interactive: Uses
AgentCore.runReasoningLoop() from packages/core
- Non-Interactive: Custom loop in
nonInteractiveCli.ts
- ACP Mode: Custom
#executePrompt() in Session.ts
-
Permission Handling (Duplicated in both CoreToolScheduler and Session.runTool()):
// CoreToolScheduler.ts
const finalPermission = await evaluatePermissionRules(pm, defaultPermission, ctx);
// Session.ts (runTool)
const { finalPermission, pmForcedAsk } = await evaluatePermissionRules(
pm, defaultPermission, pmCtx
);
Why is this needed?
Problems with Current Design
-
Code Duplication
- Permission handling logic is implemented separately in
CoreToolScheduler and Session.runTool()
- Tool execution flow (build → permission check → execute → record result) is duplicated
- Event emission logic differs between modes
-
Maintenance Burden
- Bug fixes must be applied to multiple locations
- New features (e.g., tool retry logic, timeout handling) need to be implemented 2-3 times
- Tests must be written for each mode separately, tripling test maintenance
-
Inconsistent Behavior
- Risk of modes diverging in behavior over time
- Some modes may receive bug fixes while others are forgotten
- Documentation becomes harder to maintain
-
Onboarding Difficulty
- New contributors must understand three different code paths
- Unclear which mode to modify when adding new features
- Code reviews require checking multiple implementations
Real-World Impact
From git history analysis:
- Commit
32e7b632b: "refactor: centralize IDE diff interaction in CoreToolScheduler" - This refactoring only affected Interactive/Non-Interactive modes, ACP mode still has separate logic
- Permission handling comments in both files explicitly reference each other:
"same as coreToolScheduler" - indicating awareness of duplication but no unified solution
Expected Goals After Refactoring
Primary Objectives
-
Single Source of Truth for Tool Execution
- Extract a unified
ToolExecutor interface
- All three modes call the same core execution logic
- Mode-specific behavior handled via adapters/plugins
-
Unified Permission Handling
- Centralize L3/L4/L5 permission flow in one location
- Remove duplicated
evaluatePermissionRules calls
- Consistent behavior across all modes
-
Shared Reasoning Loop
- Consider making
AgentCore.runReasoningLoop() the single reasoning loop
- ACP mode's
#executePrompt() could delegate to AgentCore
- Reduce custom loop implementations
Proposed Architecture
┌─────────────────────────────────────────────────────────┐
│ Mode Entry Points │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Interactive │ │Non-Interactive│ │ ACP │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Unified Tool Executor │ │
│ │ (refactored CoreToolScheduler)│ │
│ └────────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ Tool.build().execute() │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Implementation Approach
Option A: Adapter Pattern (Recommended)
interface ToolExecutionAdapter {
schedule(request: ToolCallRequestInfo, signal: AbortSignal): Promise<ToolCallResponseInfo>;
}
class CoreToolSchedulerAdapter implements ToolExecutionAdapter { ... }
class AcpToolExecutionAdapter implements ToolExecutionAdapter { ... }
Option B: Composition Pattern
// ACP Session.ts uses existing executeToolCall
const response = await executeToolCall(config, request, signal, {
outputUpdateHandler: acpOutputHandler,
onConfirm: acpPermissionHandler,
});
Success Criteria
Additional Context
Related Files
packages/core/src/core/coreToolScheduler.ts - Core tool scheduler for Interactive/Non-Interactive
packages/core/src/core/nonInteractiveToolExecutor.ts - Wrapper for non-interactive mode
packages/cli/src/acp-integration/session/Session.ts - ACP mode implementation (lines 700-1100: runTool method)
packages/core/src/agents/runtime/agent-core.ts - AgentCore reasoning loop
Historical Context
From git log analysis:
- ACP integration was added later than the core tool scheduler
- ACP mode chose independent implementation over refactoring to avoid breaking changes
- Multiple refactoring commits (e.g.,
32e7b632b) centralized logic for Interactive/Non-Interactive but didn't include ACP
Scope Considerations
This refactoring should be backwards compatible:
- No changes to public APIs or CLI behavior
- No changes to ACP protocol messages
- Internal implementation only
Potential Risks
- ACP mode has tight coupling with protocol-specific event emission
- Permission flow has subtle differences (e.g., ACP's
requestPermission RPC call)
- Requires careful testing across all three modes
Priority: High - This technical debt accumulates with each new feature
Estimated Effort: 2-3 weeks for full refactoring with proper testing
Breaking Changes: None (internal refactoring only)
name: 'Feature Request'
about: 'Suggest an idea for this project'
title: 'refactor: Unify Tool Execution Logic Across Interactive, Non-Interactive, and ACP Modes'
labels: ['type/feature-request', 'status/needs-triage', 'area/architecture']
What would you like to be added?
Refactor the tool execution architecture to unify the duplicated logic across three execution modes:
Current Architecture
The Qwen Code CLI currently has three execution modes with significantly different implementations:
startInteractiveUI()AgentCore.runReasoningLoop()CoreToolScheduleragent-core.ts,coreToolScheduler.tsrunNonInteractive()while(true)loopCoreToolSchedulernonInteractiveCli.ts,nonInteractiveToolExecutor.tsrunAcpAgent()Session.#executePrompt()tool.execute()acp-integration/session/Session.tsKey Differences
Tool Execution Path
CoreToolSchedulerviaexecuteToolCall()tool.build().execute()inSession.runTool()Reasoning Loop
AgentCore.runReasoningLoop()frompackages/corenonInteractiveCli.ts#executePrompt()inSession.tsPermission Handling (Duplicated in both
CoreToolSchedulerandSession.runTool()):Why is this needed?
Problems with Current Design
Code Duplication
CoreToolSchedulerandSession.runTool()Maintenance Burden
Inconsistent Behavior
Onboarding Difficulty
Real-World Impact
From git history analysis:
32e7b632b: "refactor: centralize IDE diff interaction in CoreToolScheduler" - This refactoring only affected Interactive/Non-Interactive modes, ACP mode still has separate logic"same as coreToolScheduler"- indicating awareness of duplication but no unified solutionExpected Goals After Refactoring
Primary Objectives
Single Source of Truth for Tool Execution
ToolExecutorinterfaceUnified Permission Handling
evaluatePermissionRulescallsShared Reasoning Loop
AgentCore.runReasoningLoop()the single reasoning loop#executePrompt()could delegate toAgentCoreProposed Architecture
Implementation Approach
Option A: Adapter Pattern (Recommended)
Option B: Composition Pattern
Success Criteria
evaluatePermissionRulescall site)Additional Context
Related Files
packages/core/src/core/coreToolScheduler.ts- Core tool scheduler for Interactive/Non-Interactivepackages/core/src/core/nonInteractiveToolExecutor.ts- Wrapper for non-interactive modepackages/cli/src/acp-integration/session/Session.ts- ACP mode implementation (lines 700-1100:runToolmethod)packages/core/src/agents/runtime/agent-core.ts- AgentCore reasoning loopHistorical Context
From git log analysis:
32e7b632b) centralized logic for Interactive/Non-Interactive but didn't include ACPScope Considerations
This refactoring should be backwards compatible:
Potential Risks
requestPermissionRPC call)Priority: High - This technical debt accumulates with each new feature
Estimated Effort: 2-3 weeks for full refactoring with proper testing
Breaking Changes: None (internal refactoring only)