module agentCore # export prompt using JSON, DataStructures, Dates, UUIDs, HTTP, Random, PrettyPrinting, Serialization, DataFrames, Serde using GeneralUtils using ..type, ..util, ..llmfunction # ---------------------------------------------- 100 --------------------------------------------- # """ Private agent loop. Runs in a background `@spawn` task. Waits on `inputChannel` and `followUpChannel`, processing whichever has a message first. On each iteration, dispatches the message through `_process_message` and sends the result to `outputChannel`. Exits on `:shutdown` signal. # Arguments - `agent::yiemAgent`: The agent whose loop to run # Returns - `nothing` — the loop runs until `:shutdown` is received or an error occurs # Notes - This function is automatically spawned as a background task when a `yiemAgent` is created. - On any error, logs the error with `@error` and exits the loop. - Message priority: `inputChannel` messages are checked before `followUpChannel` messages. # Examples ```jldoctest julia> # Called automatically by yiemAgent constructor ``` """ function _agent_loop(agent::yiemAgent) try processing_task = nothing """ cases: 1) agent -> idle, user msg -> nothing typeof(processing_task) == Nothing agent._state.activeRun -> false agent.inputChannel -> nothing agent.followUpChannel -> nothing 2) agent -> idle, user msg -> new msg typeof(processing_task) == Nothing agent._state.activeRun -> false agent.inputChannel -> new msg agent.followUpChannel -> nothing 3) agent -> running, user msg -> nothing typeof(processing_task) == Task, istaskdone(processing_task) -> false agent._state.activeRun -> true agent.inputChannel -> nothing agent.followUpChannel -> nothing 4) agent -> running, user msg -> new msg typeof(processing_task) == Task, istaskdone(processing_task) -> false agent._state.activeRun -> true agent.inputChannel -> new msg agent.followUpChannel -> nothing 5) agent -> running, user msg -> nothing, user msg follow up -> new msg typeof(processing_task) == Task, istaskdone(processing_task) -> false agent._state.activeRun -> true agent.inputChannel -> nothing agent.followUpChannel -> new msg 6) agent -> idle, user msg -> nothing typeof(processing_task) == Task, istaskdone(processing_task) -> true agent._state.activeRun -> false agent.inputChannel -> nothing agent.followUpChannel -> nothing """ while true result = nothing msg = nothing while msg === nothing if isready(agent.inputChannel) # message will be taken then process in _process_message() msg = fetch!(agent.inputChannel) else yield() end end # Check for shutdown signal if msg === :shutdown # Drain all remaining messages in the input channel if isready(agent.inputChannel) while isready(agent.inputChannel) _ = take!(agent.inputChannel) end end if isready(agent.followUpChannel) while isready(agent.followUpChannel) _ = take!(agent.followUpChannel) end end #TODO make sure every running tools ended properly break end # make active if agent._state.activeRun == false # Dispatch message through the processing pipeline processing_task = @spawn _process_message(agent, msg) agent._state.activeRun = true end # during agent runs, check followUp message after _process_message() is done if typeof(processing_task) == Task && istaskdone(processing_task) == false # if followUp message available, add them all to agent.inputChannel if isready(agent.followUpChannel) while isready(agent.followUpChannel) followMsg = take!(agent.followUpChannel) put!(agent.inputChannel, followMsg) end end continue # continue to process user message in the next loop elseif typeof(processing_task) == Task && istaskdone(processing_task) == true # if agent runs is done but followUpChannel has messages, discard all message in it. # when agent work is done it should not accept follow up msg. # user should put new message in inputChannel instead if isready(agent.followUpChannel) while isready(agent.followUpChannel) _ = take!(agent.followUpChannel) end end result = fetch(processing_task) put!(agent.outputChannel, result) agent._state.activeRun = false processing_task = nothing end end catch e # On any error, send error response and exit the loop @error "Agent loop failed" error=e end end """ Process a single message through the agent pipeline. This is the core processing function where LLM calls, tool execution, and response generation should be implemented. Currently a placeholder that echoes back the received message. # Arguments - `agent::yiemAgent`: The agent processing the message - `msg`: The message to process (from `inputChannel` or `followUpChannel`) # Returns - An `assistantMessage` instance with the processed response # Notes - Implement the full processing pipeline: 1. Add `msg` to `agent._state.messages` 2. Call `agent.formatMsgForLLM(agent._state)` 1 to format for LLM 3. If `agent.preprocessMessages` is set, call it on the formatted messages 4. Call the LLM (blocking — the task waits here) 5. If agent has tools, handle tool calls in a loop 6. Build `assistantMessage` and return it # Examples ```jldoctest julia> # Currently returns a placeholder echo response ``` """ function _process_message(agent::yiemAgent, msg) # WORKING Replace with actual processing logic # 2. Call agent.formatMsgForLLM(agent._state) to format for LLM # 3. If preprocessMessages is set, call agent.preprocessMessages(...) # 4. Call the LLM (blocking — the task waits here) # 5. If agent has tools, handle tool calls in a loop # 6. Build assistantMessage and return it # Placeholder: echo back the message as a simple response @warn "TODO: implement _process_message" return assistantMessage( role="assistant", content=[textContent("Received: $(msg)")], api="", model="", usage=nothing, stopReason="end_turn", errorMessage=nothing, timestamp=now(), ) end end # end of module