-
Notifications
You must be signed in to change notification settings - Fork 3
Composable tools #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Composable tools #13
Conversation
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
Owner
tcdent
commented
Dec 21, 2025
- Add AgentRegistry to manage multiple agents with mutex-wrapped access
- Route tool requests/results by agent_id through the system
- Add ToolEffect enum for agent-spawning, IDE commands, notifications
- ToolExecutor carries agent_id through the tool lifecycle
- App uses registry.next() in main select loop
- Add AgentRegistry to manage multiple agents with mutex-wrapped access - Route tool requests/results by agent_id through the system - Add ToolEffect enum for agent-spawning, IDE commands, notifications - ToolExecutor carries agent_id through the tool lifecycle - App uses registry.next() in main select loop
- Add TaskTool for spawning background agents with ToolEffect::SpawnAgent - Add SubAgentConfig with model, max_tokens, thinking_budget, tool_access - Add ToolAccess enum: Full, ReadOnly, None - Add ToolRegistry::read_only() for sub-agents (excludes task tool) - Update ToolResult to carry effects from tool execution - Effects now flow through ToolEvent::Completed to App
- Add Agent::with_tools() constructor for custom ToolRegistry - Store oauth in App for sub-agent reuse - Implement SpawnAgent effect in apply_effect - Add SUB_AGENT_PROMPT for background research agents - Sub-agent gets config from general.sub_agent settings - Tool access determined by ToolAccess config (Full/ReadOnly/None)
- Add AgentsConfig with foreground and background AgentConfig - Move model, max_tokens, thinking_budget, tool_access to AgentConfig - Keep general settings (working_dir, max_retries, compaction) in GeneralConfig - Add AgentRuntimeConfig to combine what Agent needs from both configs - Update Agent to use AgentRuntimeConfig - Update app.rs to use new config structure - Simplify SpawnAgent effect to use AgentRuntimeConfig::background()
- AwaitingApproval: show in transcript and request approval only for primary - OutputDelta: stream to transcript only for primary agent - Completed: update status and render only for primary agent - Background agents auto-approve tools without UI interaction - Tool effects and agent result submission still process for all agents
- Remove unused set_primary and len methods from AgentRegistry - Remove unused READ_ONLY_TOOLS constant and is_read_only function - Add #[allow(dead_code)] to remove method (will be needed for cleanup) - Add #[allow(dead_code)] to IdeOpen and Notify ToolEffect variants
Replace ide_post_actions with ToolEffect variants: - Add IdeReloadBuffer effect for buffer reloads after file changes - Add column parameter to IdeOpen effect for full navigation - Tools now return IDE effects from execute() instead of ide_post_actions() - Remove IdeAction enum and execute() method from Ide trait - Handle IdeReloadBuffer and IdeOpen in apply_effect() This simplifies the tool interface and unifies all post-execution side effects through the single effects system.
Tools can now be defined as compositions of effects rather than imperative code. This enables: - Declarative tool pipelines with pre/approval/execute/post phases - Effect types: validation, IDE integration, file I/O, agent spawning - PipelineExecutor interprets effects and handles suspension/resumption - edit_file demonstrates dual implementation (Tool + ComposableTool) The effect pipeline for edit_file: Pre: IdeOpen -> IdeShowPreview ---: AwaitApproval Exec: WriteFile -> Output Post: IdeReloadBuffer -> IdeClosePreview This lays groundwork for more composable, testable tool definitions.
- Tool::ide_preview now delegates to ComposableTool::compose() and extracts the preview from pre-effects - Added ComposableTool implementation for write_file - Both edit_file and write_file now derive previews from effects This eliminates duplicate preview logic - the effect pipeline is the single source of truth for IDE integration.
Instead of accumulating post effects during interpretation, collect them from the pipeline's post phase at completion time. This makes the pipeline the single source of truth for all effects.
- Remove ToolEffect, use Effect everywhere - Remove IdeEffect, use Effect directly in PipelineEvent - Simplify ToolPipeline from pre/execute/post to flat effect chain - Use .then() chain with .await_approval() as approval boundary - Update app.rs apply_effect to use Effect enum directly - Remove ~650 lines of redundant code
- Remove Tool trait entirely, use ComposableTool for all tools - Convert read_file, shell, fetch_url, web_search, open_file, task to ComposableTool with compose() returning ToolPipeline - Move effect interpretation (shell, fetch, search) into ToolExecutor - Update ToolRegistry to use Arc<dyn ComposableTool> - Remove ~466 lines of duplicate code Tools are now declarative effect chains: read_file: validate → await_approval → ReadFile shell: await_approval → Shell edit_file: IdeOpen → IdeShowPreview → await_approval → WriteFile → ...
Tool Executor: - Replace StepAction enum with Option<ToolEvent> - Inline start_next() into next() method - Add ActivePipeline::new() constructor - Add ToolEvent constructors: completed(), error(), delta(), delegate(), awaiting_approval() - Split pending_response into pending_effect and pending_approval with proper types - AwaitingApproval now uses oneshot::Sender<ToolDecision> instead of EffectResult - Add TODO noting sequential execution limitation Validation Handlers: - Add ValidateFile (unified file exists + readable check) - Add ValidateFileWritable for write permission checks - Add ValidateNoUnsavedEdits for IDE dirty buffer checks - Move ValidateEdits from shared handlers to edit_file.rs - Add IdeCheckUnsavedEdits effect in pipeline.rs IDE Integration: - Add has_unsaved_changes() to Nvim with path normalization - Fix has_unsaved_changes.lua to normalize paths with vim.fn.fnamemodify() - Add nvim_buf_is_loaded() check for buffer validation App Layer: - Simplify decide_pending_tool() to send ToolDecision directly - Remove EffectResult conversion for approvals - Add TODO for background agent auto-approve review
These methods log errors internally rather than propagating them. Callers never meaningfully handled these errors anyway - they were always ignored with let _ = or .ok(). This cleans up all call sites to just be clean function calls.
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.