# Implementation Plan: YiemAgent Agent Framework ## 1. Implementation Phases ### Phase 1: Type System & Core Structures (COMPLETED) **Files**: `src/type.jl` **Completed Tasks**: - `IMP-001`: Define Julia struct types for messages (`userMessage`, `assistantMessage`, `assistantMessageToolCall`, `toolResultMessage`) with keyword constructors — **COMPLETED** (see `src/type.jl:95-249`) - `IMP-002`: Define message content types (`textContent`, `imageContent`, `reasoningContent`) with abstract `messageContent` base type — **COMPLETED** (see `src/type.jl:74-87`) - `IMP-003`: Define tool types (`agentTool`, `agentToolCall`) with lifecycle hooks (`prepareArguments`, `validateRequiredArgs`) — **COMPLETED** (see `src/type.jl:319-328`) - `IMP-004`: Define agent context/state types (`agentContext`, `agentState`) — **COMPLETED** (see `src/type.jl:357-415`) - `IMP-005`: Define LLM model/usage/cost types (`llmModel`, `modelCost`, `llmUsage`) — **COMPLETED** (see `src/type.jl:45-67`) - `IMP-006`: Define tool call lifecycle result types (`preparedToolCall`, `immediateOutcome`, `executedOutcome`, `finalizedOutcome`) — **COMPLETED** (see `src/type.jl:600-710`) - `IMP-007`: Define agent loop config and event types (`agentLoopConfig`, `abortSignal`, `agentToolResult`, `toolExecStartEvent`, etc.) — **COMPLETED** (see `src/type.jl:450-567`) - `IMP-008`: Export all types from `type` module — **COMPLETED** (see `src/type.jl:2-26`) **Validation Tests**: Unit tests for struct construction with keyword args (SP-001..SP-008, SP-019..SP-022) **Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005, FR-006 --- ### Phase 2: Utility & Formatting Functions (COMPLETED) **Files**: `src/utils.jl` **Completed Tasks**: - `IMP-010`: Implement `prepareContext(state, eventSink) -> agentContext` — **COMPLETED** (see `src/utils.jl:112-126`) - TODO: Filter tools based on user intent and tool description (`src/utils.jl:114`) - TODO: Inject system prompt context (e.g., time, session state) (`src/utils.jl:118`) - TODO: Prune/reorder messages (`src/utils.jl:121`) - `IMP-011`: Implement `formatMsgForLLM(ctx, eventSink) -> Dict{String, Any}` — **COMPLETED** (see `src/utils.jl:159-244`) - Converts system prompt to system message - Converts conversation messages via `_userMessageToOpenAI`, `_assistantMessageToOpenAI`, `_assistantMessageToolCallToOpenAI`, `_toolResultMessageToOpenAI` - Converts `ctx.tools` to OpenAI function tool format via `_toolsToOpenAI` - `IMP-012`: Implement message converters (`_userMessageToOpenAI`, `_assistantMessageToOpenAI`, `_assistantMessageToolCallToOpenAI`, `_toolResultMessageToOpenAI`) — **COMPLETED** (see `src/utils.jl:321-387`) - `IMP-013`: Implement `_messageContentToBlocks` — converts `textContent`/`imageContent` to OpenAI content blocks — **COMPLETED** (see `src/utils.jl:396-413`) - `IMP-014`: Implement `_toolsToOpenAI` — converts `OrderedDict{String, agentTool}` to OpenAI tools array — **COMPLETED** (see `src/utils.jl:429-447`) - `IMP-015`: Implement `beforeToolCall` hook default (returns `beforeToolCallResult(false, "N/A")`) — **COMPLETED** (see `src/utils.jl:278-284`) - `IMP-016`: Implement `afterToolCall` hook default (returns `nothing`) — **COMPLETED** (see `src/utils.jl:310-316`) - `IMP-017`: Implement `validateRequiredArgs(args, inputSchema)` — validates required fields in tool arguments — **COMPLETED** (see `src/utils.jl:479-497`) - `IMP-018`: Implement `validateToolArguments(tool, prepared)` — wrapper that calls tool-specific or default hook — **COMPLETED** (see `src/utils.jl:533-546`) **Validation Tests**: Unit tests for converters, validation hooks, and OpenAI format output structure (SP-011..SP-016, SP-023, SP-024) **Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005, FR-006 --- ### Phase 3: Tool Registry & MCP Integration (COMPLETED) **Files**: `src/toolRegistry.jl` **Completed Tasks**: - `IMP-020`: Implement `_extract_text_content(result)` — extracts text from MCP tool result content array — **COMPLETED** (see `src/toolRegistry.jl:19-33`) - `IMP-021`: Implement `_wrap_mcp_tool(mcpserver, tool_def)` — wraps MCP tool as `agentTool` with `execute` calling `mcpserver("tools/call", ...)` — **COMPLETED** (see `src/toolRegistry.jl:41-106`) - Handles MCP error responses (`haskey(response, "error")`) - Extracts text and `isError` from result - `IMP-022`: Implement `register_mcp_tools(tools, mcpserver)` — queries `tools/list` with pagination (`nextCursor`) — **COMPLETED** (see `src/toolRegistry.jl:118-186`) - Handles both `result.tools` and `result.error` response shapes - Skips already registered tools (idempotent) - `IMP-023`: Implement `registerTool(tools, tool)` — adds tool to OrderedDict — **COMPLETED** (see `src/toolRegistry.jl:200-205`) - `IMP-024`: Implement `listTool(tools, mcpserver)` — builtin tool that triggers runtime discovery and lists all tools — **COMPLETED** (see `src/toolRegistry.jl:235-271`) - `IMP-025`: Implement `registerAllTools(tools, mcpserver)` — discovers existing tools + registers `listTools` — **COMPLETED** (see `src/toolRegistry.jl:286-289`) **Validation Tests**: Unit tests for MCP tools/list parsing, pagination, tool registration idempotency; contract tests for JSON-RPC 2.0 format (SP-009, SP-010) **Traceability**: FR-002, FR-004 --- ### Phase 4: Agent Core & Loop (COMPLETED) **Files**: `src/agentCore.jl` **Completed Tasks**: - `IMP-030`: Define `yiemAgent` struct with channels, callbacks, MCP server — **COMPLETED** (see `src/agentCore.jl:17-95`) - `IMP-031`: Implement `yiemAgent` constructor — creates channels, discovers MCP tools, spawns `_agentLoop` task — **COMPLETED** (see `src/agentCore.jl:126-199`) - `IMP-032`: Implement `_agentLoop(agent)` — background `@spawn` task managing `inputChannel`/`followUpChannel` priority dispatch — **COMPLETED** (see `src/agentCore.jl:225-364`) - Handles shutdown signal: drains all channels, breaks loop - On error: catches, calls `eventSink(errMsg)`, exits loop - `IMP-033`: Implement `_processMessage(...)` — drains input, calls `llmCall`, extracts tool calls, executes, loops until no tool calls — **COMPLETED** (see `src/agentCore.jl:394-544`) - `while true` loop: `llmCall()` → `_extractToolCalls()` → if `hasToolCalls && length > 0` execute → append results → repeat - On `toolResultBatch.terminate == true`: build final response and break - Catches all errors and calls `eventSink(err_msg)` - `IMP-034`: Implement `_extractToolCalls(response)` — handles **both** formats: - Format 1: `response["message"]["tool_calls"]` (LMStudio/vLLM style) — **COMPLETED** (see `src/agentCore.jl:671-683`) - Format 2: `response.content` blocks with `type => "tool_calls"` or `type => "tool_call"` (OpenAI API style) — **COMPLETED** (see `src/agentCore.jl:685-719`) - Extracts `reasoning_content`, `content` text blocks, `api`, `provider`, `model`, `usage`, `finish_reason` — **COMPLETED** (see `src/agentCore.jl:723-797`) - Returns `(hasToolCalls, toolCallList, assistant_message)` — **COMPLETED** (see `src/agentCore.jl:799-827`) - `IMP-035`: Implement `prepareToolCall(context, assistantMsg, toolCall, config, signal, eventSink)` — resolves tool, validates args, runs `beforeToolCall` hook, never throws — **COMPLETED** (see `src/agentCore.jl:974-1035`) - `IMP-036`: Implement `executePreparedToolCall(prep, signal, eventSink)` — calls `tool.execute(toolCallId, args, ...)` — **COMPLETED** (see `src/agentCore.jl:1078-1100`) - `IMP-037`: Implement `finalizeExecutedToolCall(context, msg, prep, executed, config, signal, eventSink)` — runs `afterToolCall` hook, catches errors — **COMPLETED** (see `src/agentCore.jl:1159-1198`) - `IMP-038`: Implement `prepareToolCallArguments(tool, toolCall)` — calls `tool.prepareArguments` if present — **COMPLETED** (see `src/agentCore.jl:908-917`) - `IMP-039`: Implement `createErrorToolResult(msg)` — builds `agentToolResult` with error content — **COMPLETED** (see `src/agentCore.jl:572-574`) - `IMP-040`: Implement `createToolResultMessage(f)` — builds `toolResultMessage` from `finalizedOutcome` — **COMPLETED** (see `src/agentCore.jl:606-612`) **Note**: Sequential and parallel execution implementations continue beyond line 1207 of `src/agentCore.jl` — implementation incomplete, requires review to ensure execution methods properly call hooks and emit lifecycle events. See IMP-041. **Validation Tests**: Integration tests for full agent loop with mocked LLM and MCP; contract tests for response format extraction (WT-001..WT-032, SP-017, SP-018) **Traceability**: FR-001, FR-002, FR-003, FR-004, FR-005 --- ### Phase 5: Public API (COMPLETED) **Files**: `src/api.jl` **Completed Tasks**: - `IMP-050`: Implement `promptAgent(agent, msg::AbstractDict)` — put to `inputChannel`, returns agent for chaining — **COMPLETED** (see `src/api.jl:36-39`) - `IMP-051`: Implement `promptAgent(agent, text::String)` — wraps text as OpenAI `Dict` and put to `inputChannel` — **COMPLETED** (see `src/api.jl:41-44`) - `IMP-052`: Implement `takeResponse(agent)` — blocks on `outputChannel` `take!`, returns OpenAI dict response — **COMPLETED** (see `src/api.jl:66-68`) - `IMP-053`: Implement `followUp(agent, msg::AbstractDict)` — put to `followUpChannel`, returns agent — **COMPLETED** (see `src/api.jl:94-97`) - `IMP-054`: Implement `followUp(agent, text::String)` — wraps text as OpenAI `Dict` and put to `followUpChannel` — **COMPLETED** (see `src/api.jl:99-102`) - `IMP-055`: Implement `stopAgent(agent)` — puts `:shutdown`, `fetch(_agentLoop)`, closes all channels — **COMPLETED** (see `src/api.jl:126-139`) **Validation Tests**: Integration tests for send/receive/stop lifecycle (WT-001..WT-005) **Traceability**: FR-001, FR-006 --- ## 2. Module Structure ``` src/ ├── YiemAgent.jl # Parent module, includes submodules in dependency order ├── type.jl # All Julia struct types (exports 30+ types) ├── utils.jl # Formatting, validation, hooks (default implementations) ├── toolRegistry.jl # MCP tool discovery, registration, listTools builtin ├── agentCore.jl # yiemAgent struct, _agentLoop, _processMessage, extractToolCalls └── api.jl # Public: promptAgent, takeResponse, followUp, stopAgent ``` **Include order** (per `src/YiemAgent.jl` lines 6-19): 1. `type.jl` (no dependencies) 2. `utils.jl` (depends on `type`) 3. `toolRegistry.jl` (depends on `type`) 4. `agentCore.jl` (depends on `type`, `utils`, `toolRegistry`) 5. `api.jl` (depends on `type`, `utils`, `agentCore`, `toolRegistry`) --- ## 3. Task Dependencies | Task ID | Title | Depends On | Status | |---------|-------|------------|--------| | IMP-001..IMP-008 | Type system | — | **COMPLETED** | | IMP-010..IMP-018 | Utils (formatMsgForLLM, converters, hooks) | IMP-001..IMP-008 | **COMPLETED** | | IMP-020..IMP-025 | Tool registry (MCP discovery) | IMP-003, IMP-017 | **COMPLETED** | | IMP-030..IMP-040 | Agent core (agent loop, tool call execution) | IMP-001..IMP-008, IMP-010..IMP-018, IMP-020..IMP-025 | **INCOMPLETE*** (see note) | | IMP-050..IMP-055 | Public API | IMP-030..IMP-040 | **COMPLETED** | **NOTE on IMP-030..IMP-040**: The sequential and parallel execution functions (`executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls`) begin after line 1207 of `src/agentCore.jl` but the full implementation is truncated in file reads and requires review. The exported functions (`executePreparedToolCall`, `prepareToolCall`, `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls`) are declared (`src/agentCore.jl:4-5`) but their full bodies may be incomplete. **Requires manual validation.** **Traceability**: IMP-001..IMP-008 → SP-001..SP-008, IMP-020..IMP-025 → SP-009, SP-010; IMP-010..IMP-018 → SP-011..SP-016, SP-023, SP-024; IMP-030..IMP-040 → SP-017, SP-018, SP-019..SP-022 --- ## 4. Remaining Work (TODOs) | File | Line | Description | Priority | |------|------|-------------|----------| | `src/utils.jl` | 114 | Filter tools from `state.tools` based on user intent and tool description | High | | `src/utils.jl` | 118 | Add system prompt context: time, session state, user preferences | Medium | | `src/utils.jl` | 121 | Prune/reorder/conversation messages before sending to LLM | Medium | | `src/agentCore.jl` | post-1207 | Complete and validate `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls` implementations (currently exported but body may be incomplete) | **Critical** | | `tests/unit/...` | — | Write unit tests for all struct types (IMP-001..IMP-008) and converters (SP-011..SP-016) | High | | `tests/unit/...` | — | Write unit tests for tool validation hooks (SP-023, SP-024) | High | | `tests/integration/...` | — | Write integration tests for agent loop with mocked LLM/MCP (WT-001..WT-032) | High | | `tests/contract/...` | — | Write contract tests for response formats (SP-017, SP-018) | Medium | | `tests/perf/...` | — | Write performance tests (NFR-101, NFR-102) | Medium | --- ## 5. Build & Deployment ### Build ```bash cd /home/ton/docker-apps/sommpanion/YiemAgent julia --project=. -e 'using Pkg; Pkg.instantiate(); using Pkg; Pkg.precompile()' ``` ### Test ```bash julia --project=./tests -e 'using Pkg; Pkg.instantiate()' julia --project=./tests -e 'using Test; Test.@testset "Unit" begin include("tests/unit/run_tests.jl") end' julia --project=./tests -e 'using Test; Test.@testset "Integration" begin include("tests/integration/run_tests.jl") end' ``` ### CI/CD Preparation - Add `.github/workflows/julia.yaml` referencing `ci/julia-pipeline.yaml` steps - Add `Project.toml` dependencies: ```toml [deps] JSON = "682c8566-3430-5439-8f4e-b64832e8aaba" DataStructures = "892a3cad-33e6-51e5-808c-f3d90072be70" HTTP = "cd6eee30-4dd5-5329-8bad-3259ec48d0f4" # plus: UUIDs, Dates, PrettyPrinting, Serialization, DataFrames, NATS, LibPQ ``` --- ## 6. Risk Mitigation | Risk | Mitigation | |------|------------| | Line 1207+ of `agentCore.jl` (sequential/parallel execution) is incomplete or broken | Manually review and validate `executeToolCallsSequential`, `executeToolCallsParallel`, `executeToolCalls` against spec SP-021 (agentLoopConfig) and trace to test `test_tool_call_execution_sequence` | | `GeneralUtils` internal package unavailable in CI | Ensure `GeneralUtils` is installed as dependency (via `Pkg.develop` or registry) | | NATS `mcpServer` struct is abstract — no concrete implementation | Provide at least one mock `mcpServer` for tests that returns canned JSON responses | | Message history grows unbounded | Implement `prepareContext` pruning (TODO in code); add NFR-104 for max history tokens |