# Walkthrough: YiemAgent Agent Framework ## Structural Guidance - One **overview sequence diagram** per participant (high-level flow) - **Detailed sub-diagrams** for complex interaction points (tool call loop, MCP discovery) - Use sequence diagrams for request/response patterns; lifecycle states for multi-step operations --- ## External Caller _The Developer calling `YiemAgent.jl` via `promptAgent`, `takeResponse`, `followUp`, and `stopAgent`._ ### Session Start: Create Agent **WT-001**: Developer creates an `yiemAgent` instance with `llmCall` and `mcpServer` > Cross-reference: `2-solution_design.md:SD-001` (channel-based async), `SD-004` (MCP struct) **Details**: ```julia mcp = mcpServer(conn, topic, senderID) agent = yiemAgent(llmCall, mcpServer=mcp, systemPrompt="You are a helpful assistant") ``` **Sequence**: ```mermaid sequenceDiagram participant Dev as Developer part agent YiemAgent Constructor Dev->>agent: yiemAgent(llmCall, ; mcpServer=mcp) agent->>agent: create channels (input=16, followUp=32, output=16) agent->>agent: registerAllTools(tools, mcp) agent->>mcp: mcp("tools/list", sessionId) mcp-->>agent: response (tools array + nextCursor) agent->>agent: _wrap_mcp_tool for each tool agent->>agent: registerTool(tools, listTools_tool) agent->>agent: println("[toolRegistry] Discovered N MCP tools") agent->>agent: yiemAgent struct with placeholder task agent->>agent: @spawn _agentLoop(agent) agent-->>Dev: yiemAgent instance ``` > **Traceability**: FR-004 (tool discovery), US-001 (agent creation) ### Send Message: User Prompt **WT-002**: Developer sends a user message to the agent > Cross-reference: `2-solution_design.md:SD-002` (background task dispatch) **Sequence**: ```mermaid sequenceDiagram participant Dev as Developer part agent AgentLoop Dev->>agent: promptAgent(agent, text="What's the weather?") agent->>agent: put!(inputChannel, msg_dict) Dev->>agent: takeResponse(agent) # blocks agent->>agent: inputChannel isready agent->>agent: take!(inputChannel) agent->>Dev: (returns agent for chaining) ``` > **Traceability**: FR-001 (channel processing), PR-001, SD-001, SD-002 ### Receive Response **WT-003**: Agent returns response on `outputChannel` **Sequence**: ```mermaid sequenceDiagram participant Dev as Developer part agent AgentLoop AgentLoop->>AgentLoop: _processMessage runs AgentLoop->>AgentLoop: while loop, llmCall, tool calls... AgentLoop->>AgentLoop: no more tool calls → exit loop AgentLoop->>AgentLoop: filter reasoningContent from final result AgentLoop->>AgentLoop: _assistantMessageToOpenAI(result) AgentLoop->>AgentLoop: put!(outputChannel, respondToUI) AgentLoop->>Dev: take!(outputChannel) # unblocks Dev->>Dev: receive OpenAI dict response ``` > **Traceability**: FR-001, FR-003, FR-006, PR-001 ### Send Follow-up While Agent Busy **WT-004**: Developer sends a follow-up message while agent is processing a tool call **Sequence**: ```mermaid sequenceDiagram participant Dev as Developer part agent AgentLoop Dev->>agent: promptAgent(text="What's the weather in Tokyo?") # agent starts processing AgentLoop->>AgentLoop: _processMessage started (processingTask = Task) Dev->>agent: followUp(text="Also check Seoul") # while busy agent->>agent: put!(followUpChannel, msg_dict) AgentLoop->>AgentLoop: _processMessage completes (tool calls done) AgentLoop->>AgentLoop: outputChannel has message → put!(outputChannel, response), take!(next msg) AgentLoop->>AgentLoop: no inputChannel → check followUpChannel AgentLoop->>AgentLoop: isready(followUpChannel) → take!(followUpMsg), put!(inputChannel, followUpMsg) AgentLoop->>AgentLoop: processingTask = nothing → reset AgentLoop->>AgentLoop: loop back → take!(inputChannel) from followUp Dev->>agent: takeResponse() → blocks AgentLoop->>Dev: next response via outputChannel ``` > **Traceability**: FR-001 (follow-up priority), PR-001, SD-001, SD-002 ### Stop Agent **WT-005**: Developer stops the agent gracefully **Sequence**: ```mermaid sequenceDiagram participant Dev as Developer part agent AgentLoop Dev->>agent: stopAgent(agent) agent->>agent: put!(inputChannel, :shutdown) AgentLoop->>AgentLoop: check newUserMsg === :shutdown AgentLoop->>AgentLoop: drain inputChannel (while isready) AgentLoop->>AgentLoop: drain followUpChannel (while isready) AgentLoop->>AgentLoop: break, catch, fetch(_agentLoop) Dev->>agent: close(inputChannel, outputChannel, followUpChannel) Dev-->>Dev: nothing (agent destroyed) ``` > **Traceability**: FR-001, NFR-201 (graceful exit), PR-001 --- ## LLM Provider _The external LLM API (e.g. Anthropic, OpenAI, LMStudio) receiving formatted messages and returning responses._ ### Receive Formatted Message **WT-010**: Agent sends LLM request in OpenAI format > Cross-reference: `2-solution_design.md:SD-003` (formatMsgForLLM) **Sequence**: ```mermaid sequenceDiagram part agent AgentLoop AgentLoop->>AgentLoop: preparedContext = prepareContext(state) AgentLoop->>AgentLoop: formattedMessages = formatMsgForLLM(preparedContext) Note over AgentLoop: messages: [system, user, assistant, tool, ...]
tools: [{"function": {"name": "getWeather", "parameters": {...}}}] agentLoop->>llmCall: llmCall(formattedMessages, eventSink=eventSink) llmCall->>Provider: HTTP POST /v1/chat/completions (JSON) Provider-->>llmCall: JSON response llmCall-->>AgentLoop: response = JSON.Object{String, Any}(...) ``` > **Traceability**: FR-002, SD-003, SD-009 ### Receive Response: No Tool Calls **WT-11**: Agent receives text-only response **Sequence**: ```mermaid sequenceDiagram llmCall-->>AgentLoop: response = {finish_reason=> "stop", message=> {role=> "assistant", content=> "It's 22°C in Bangkok"}} AgentLoop->>AgentLoop: _extractToolCalls(response) AgentLoop->>AgentLoop: hasToolCalls = false AgentLoop->>AgentLoop: assistant_msg = assistantMessage(content=[textContent("It's 22°C")], ...) AgentLoop->>AgentLoop: push!(agentMsgHistory, assistant_msg) AgentLoop->>AgentLoop: break (no tool calls) AgentLoop->>AgentLoop: filter reasoningContent (if any) AgentLoop->>AgentLoop: create final assistantMessage AgentLoop->>AgentLoop: put!(outputChannel, OpenAI format dict) ``` > **Traceability**: FR-003 (loop exit), FR-006 (OpenAI conversion), PR-003 ### Receive Response: With Tool Calls **WT-012**: Agent receives tool call response **Sequence**: ```mermaid sequenceDiagram llmCall-->>AgentLoop: response = {finish_reason=> "tool_calls",
message=> {role=> "assistant",
reasoning_content=> "I need to call getWeather...",
tool_calls=> [{"id"=>"call_1", "function"=>{name=>"getWeather", arguments=>"{\"city\":\"Bangkok\"}"}}, ...]}} AgentLoop->>AgentLoop: _extractToolCalls(response) AgentLoop->>AgentLoop: hasToolCalls = true
toolCallList = [agentToolCall("getWeather", ...)] AgentLoop->>AgentLoop: assistant_msg = assistantMessageToolCall(toolCalls, reasoningContent, ...) AgentLoop->>AgentLoop: push!(agentMsgHistory, assistant_msg) AgentLoop->>AgentLoop: config = agentLoopConfig(beforeToolCall, afterToolCall, parallel="sequential") AgentLoop->>AgentLoop: toolResultBatch = executeToolCalls(...) ... ``` > **Traceability**: FR-002, FR-003, PR-003, SD-003, SD-005, SD-009 --- ## MCP Server _The Model Context Protocol server providing tools via JSON-RPC 2.0 (transport via NATS or HTTP). ### Receive tools/list Request **WT-020**: Agent discovers tools on startup > Cross-reference: `2-solution_design.md:SD-004` (MCP integration) **Sequence**: ```mermaid sequenceDiagram part agent AgentLoop agent->>mcp: mcp("tools/list", sessionId; eventSink=eventSink) Note over mcp: JSON-RPC 2.0: {"jsonrpc"=>"2.0", method=>"tools/list", "id"=>..., "params"=>{...}} MCP_server-->>agent: response = {
"result" => {
"tools" => [name, label, description, inputSchema], ...
, "nextCursor" => 123
}
} agent->>agent: for tool_def in tools_array
_wrap_mcp_tool(mcp, tool_def) agent->>agent: while cursor !== nothing → paginate agent->>agent: println("[toolRegistry] Discovered N MCP tools. Total: $(length(tools))") ``` > **Traceability**: FR-004 (MCP discovery), PR-004 ### Receive tools/call Request **WT-021**: Agent executes tool via MCP **Sequence**: ```mermaid sequenceDiagram part agent Tool System agent->>mcp: mcp("tools/call", "getWeather", args={"city" => "Bangkok"}, sessionId; eventSink=eventSink) Note over mcp: JSON-RPC 2.0: {"jsonrpc"=>"2.0", method=>"tools/call", "params"=>{name, arguments, sessionId}} MCP_server-->>agent: response = {
"result" => {
"content" => [{"type"=>"text", "text"=>"Sunny, 22°C"}],
"isError" => false
}
} agent->>agent: check haskey(response, "error") → no agent->>agent: result_data = response["result"] agent->>agent: content_text = _extract_text_content(result_data) agent->>agent: return agentToolResult([textContent(content_text)], details={isError=>false}, nothing, false) ``` > **Traceability**: FR-004 (tool execution), PR-004, SD-004, SD-005 ### Receive Error from MCP Server **WT-022**: MCP server returns error **Sequence**: ```mermaid sequenceDiagram MCP_server-->>agent: response = {
"error" => {
"code" => -32601,
"message" => "Method not found"
}
} agent->>agent: haskey(response, "error") → true agent->>agent: return agentToolResult(
[textContent("MCP error: Method not found")],
details={"error" => "Method not found"},
nothing, false
) agent->>AgentLoop: finalOutcome(error=true) agent->>AgentLoop: push!(agentMsgHistory, toolResultMessage(...)) agent->>AgentLoop: next LLM turn receives error result ``` > **Traceability**: FR-004 (error handling), NFR-202 (never throw), PR-004, SD-005 --- ## Error Paths and Edge Cases **WT-030**: LLM times out (network error) > Same participant POV — from caller's perspective, they block on `takeResponse()` until task exits ```mermaid sequenceDiagram participant Dev as Developer part agent AgentLoop Dev->>agent: takeResponse(agent) # blocks agent->>llmCall: llmCall(messages) # calls external API lltimout LLM provider-->>AgentLoop: ERROR (timeout) AgentLoop->>AgentLoop: @error "Agent loop failed" AgentLoop->>AgentLoop: eventSink("error: ...") AgentLoop->>Dev: fetch(_agentLoop) throws TaskFailedException Dev->>agent: stopAgent(agent) # cleanup channels ``` **Traceability**: PR-001, NFR-201 (catch error and exit), SD-001 **WT-031**: Tool execution throws exception ```mermaid sequenceDiagram agent->>agent: executePreparedToolCall agent->>tool: tool.execute(toolCallId, args, signal, eventSink) note over tool: tool raises exception (e.g., network error, invalid JSON) tool-->>agent: throw Exception agent->>agent: catch e agent->>agent: executedOutcome(createErrorToolResult("error message"), true) agent->>AgentLoop: finalizeExecutedToolCall AgentLoop->>AgentLoop: createToolResultMessage(error=true) AgentLoop->>AgentLoop: push!(history, toolResultMessage) AgentLoop->>AgentLoop: next llmCall with error in message history ``` **Traceability**: FR-005 (hook errors caught), NFR-202 (never throw), SD-005 **WT-032**: beforeToolCall hook blocks execution ```mermaid sequenceDiagram agent->>agent: beforeToolCall(beforeCtx, signal, eventSink) agent->>agent: returns beforeToolCallResult(true, "User must approve") agent->>agent: return immediateOutcome(createErrorToolResult("Tool blocked"), true) agent->>AgentLoop: executeToolCalls → immediateOutcome AgentLoop->>AgentLoop: finalizeExecutedToolCall AgentLoop->>AgentLoop: createToolResultMessage(error=true) AgentLoop->>AgentLoop: push! to history ``` **Traceability**: FR-005 (beforeToolCall can block), SD-005