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 while true msg = nothing while msg === nothing if isready(agent.inputChannel) && agent._state.activeRun == false # allow _process_message() to run yield() elseif isready(agent.inputChannel) && agent._state.activeRun == true msg = fetch!(agent.inputChannel) # Check for shutdown signal if msg === :shutdown msg = take!(agent.inputChannel) end else error("undefined condition: isready(inputChannel)=$(isready(agent.inputChannel)), activeRun=$(agent._state.activeRun), msg=$msg") end end # Check for shutdown signal if msg === :shutdown #TODO make sure every running tools ended properly break end # make active agent._state.activeRun = true # Dispatch message through the processing pipeline result = _process_message(agent, msg) # check followUp message. if there are, add them all to agent.inputChannel hasMore = isready(agent.followUpChannel) while isready(agent.followUpChannel) followMsg = take!(agent.followUpChannel) put!(agent.inputChannel, followMsg) hasMore = true end if !isready(agent.inputChannel) && !hasMore # no more messages queued — safe to send response put!(agent.outputChannel, result) 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