# AgentCore.jl - Tools Deep Dive ## Tool Types (from types.jl) ### AgentTool (struct) ```julia struct AgentTool{TParameters, TDetails} name::String # tool identifier label::String # display name description::String # what it does parameters::TParameters # JSON schema or type execute::Function # (tool_call_id, params, signal, on_update, context) -> AgentToolResult prepare_arguments::Union{Function, Nothing} execution_mode::Union{ToolExecutionMode, Nothing} end ``` ### AgentToolResult (struct) ```julia struct AgentToolResult{T} content::Vector{MessageContent} details::T usage::Union{Usage, Nothing} added_tool_names::Union{Vector{String}, Nothing} terminate::Union{Bool, Nothing} end ``` ### ToolCall (struct) ```julia struct ToolCall type::String # always "tool" id::String # unique identifier name::String # tool name to execute arguments::Dict{String, Any} # JSON-like arguments partial_json::Union{String, Nothing} end ``` ### ToolExecutionMode (enum) ```julia @enum ToolExecutionMode begin EXECUTION_SEQUENTIAL = "sequential" EXECUTION_PARALLEL = "parallel" end ``` ## Tool Execution Flow ``` AssistantMessage (from LLM) content::Vector{MessageContent} └─ Contains: TextContent[] and ToolCall[] ▼ Agent.execute() (in agent.jl) └─ before_tool_call hook (Agent.before_tool_call, optional) Input: BeforeToolCallContext Output: BeforeToolCallResult (block, reason) ▼ For each ToolCall: tool = find_tool(name) tool.execute(tool_call_id, args, signal, on_update, context) ▼ AgentToolResult{T}(content, details, usage, added_tool_names, terminate) ▼ └─ after_tool_call hook (Agent.after_tool_call, optional) Input: AfterToolCallContext Output: AfterToolCallResult (patches: content, details, is_error, usage, terminate) ▼ ToolResultMessage (one per ToolCall) role: "toolResult" tool_call_id::String tool_name::String content::Vector{MessageContent} details::Any usage::Union{Usage, Nothing} added_tool_names::Union{Vector{String}, Nothing} is_error::Bool timestamp::Timestamp ▼ Append to AgentState.messages └─ Next turn: LLM sees tool results as input ``` ## Built-in Tools ### 1. BashTool (`tools/bash.jl`) ```julia struct BashExecution command::String cwd::String env::Dict{String, String} inherit_env::Bool end mutable struct BashPrepare{TContext} function::Function context::TContext signal::Union{Any, Nothing} end mutable struct BashToolOptions{TContext} command_prefix::Union{String, Nothing} prepare::Union{BashPrepare{TContext}, Nothing} end mutable struct BashToolDetails truncation::Union{Any, Nothing} full_output_path::Union{String, Nothing} end function createBashTool{TContext}(options::Union{BashToolOptions{TContext}, Nothing}=nothing) where TContext ``` **Execute signature**: `(tool_call_id, params, signal, on_update, context) -> AgentToolResult` **Note**: The actual bash execution is a TODO stub in the current source. ### 2. ReadTool (`tools/read.jl`) ```julia mutable struct ReadToolDetails truncation::Union{Any, Nothing} end mutable struct ReadToolOptions auto_resize_images::Bool image_processor::Union{Any, Nothing} end function createReadTool{TContext}(options::Union{ReadToolOptions, Nothing}=nothing) where TContext ``` **Execute signature**: `(tool_call_id, params, signal, on_update, context) -> AgentToolResult` ### 3. WriteTool (`tools/write.jl`) ```julia function createWriteTool{TContext}() where TContext ``` **Execute signature**: `(tool_call_id, params, signal, on_update, context) -> AgentToolResult` ### 4. EditTool (`tools/edit.jl`) ```julia mutable struct EditToolDetails diff::String patch::String first_changed_line::Union{Int64, Nothing} end function createEditTool{TContext}() where TContext ``` **Execute signature**: `(tool_call_id, params, signal, on_update, context) -> AgentToolResult` ## Tool Hooks (on Agent struct) The `Agent` struct in `agent.jl` has these hook fields: ```julia mutable struct Agent ... before_tool_call::Union{Function, Nothing} after_tool_call::Union{Function, Nothing} prepare_next_turn::Union{Function, Nothing} prepare_next_turn_with_context::Union{Function, Nothing} ... end ``` Configured via `Agent(Dict(...))` options: - `:beforeToolCall` → `Agent.before_tool_call` - `:afterToolCall` → `Agent.after_tool_call` - `:prepareNextTurn` → `Agent.prepare_next_turn` - `:prepareNextTurnWithContext` → `Agent.prepare_next_turn_with_context` ### BeforeToolCallContext / BeforeToolCallResult (from types.jl) ```julia struct BeforeToolCallContext assistant_message::AssistantMessage tool_call::ToolCall args::Any context::AgentContext end struct BeforeToolCallResult block::Union{Bool, Nothing} reason::Union{String, Nothing} end ``` ### AfterToolCallContext / AfterToolCallResult (from types.jl) ```julia struct AfterToolCallContext assistant_message::AssistantMessage tool_call::ToolCall args::Any result::AgentToolResult is_error::Bool context::AgentContext end struct AfterToolCallResult content::Union{Vector{MessageContent}, Nothing} details::Union{Any, Nothing} is_error::Union{Bool, Nothing} usage::Union{Usage, Nothing} terminate::Union{Bool, Nothing} end ``` ### PrepareNextTurnContext / AgentLoopTurnUpdate (from types.jl) ```julia struct PrepareNextTurnContext message::AssistantMessage tool_results::Vector{ToolResultMessage} context::AgentContext new_messages::Vector{AgentMessage} end struct AgentLoopTurnUpdate context::Union{AgentContext, Nothing} model::Union{Model, Nothing} thinking_level::Union{ThinkingLevel, Nothing} end ``` ## Tool Execution Modes ### Sequential Execution ```julia # Configure on Agent agent = Agent(Dict( :toolExecution => EXECUTION_SEQUENTIAL, )) ``` ### Parallel Execution (default) ```julia agent = Agent(Dict( :toolExecution => EXECUTION_PARALLEL, )) ``` Tools can also specify their own mode: ```julia agent_tool = AgentTool( "name", "label", "description", params_schema, execute_fn, nothing, EXECUTION_SEQUENTIAL, # or EXECUTION_PARALLEL ) ``` ## Tool Exports (from tools/index.jl) ```julia export createBashTool, createReadTool, createWriteTool, createEditTool, BashExecution, BashPrepare, BashToolDetails, BashToolInput, BashToolOptions, EditToolDetails, EditToolInput, ReadToolDetails, ReadToolInput, ReadToolOptions, ReadImageProcessor, ReadImageProcessorResult, WriteToolInput ``` ## Example: Creating and Using Tools ```julia using AgentCore # Create tools bash_tool = createBashTool() read_tool = createReadTool() write_tool = createWriteTool() # Configure hooks before_hook = (context, signal) -> begin println("About to execute: $(context.tool_call.name)") return nothing end after_hook = (context, signal) -> begin if context.is_error println("Tool failed: $(context.tool_call.name)") else println("Tool completed: $(context.tool_call.name)") end return nothing end # Create agent with tools and hooks agent = Agent(Dict( :systemPrompt => "You are a helpful assistant with file system access.", :tools => [bash_tool, read_tool, write_tool], :beforeToolCall => before_hook, :afterToolCall => after_hook, :toolExecution => EXECUTION_PARALLEL, )) # Run prompt prompt(agent, "List files in current directory and read the first one") ``` ## Best Practices 1. **Use sequential execution** for tools that depend on shared state 2. **Use parallel execution** for independent operations 3. **Implement before_tool_call hook** for logging and validation 4. **Implement after_tool_call hook** for result modification 5. **Use prepare_next_turn hook** for dynamic model/thinking level changes 6. **Return terminate=true** from tool when agent should stop 7. **Include usage statistics** in tool results when possible