42 KiB
42 KiB
AgentCore.jl - Architecture Overview
Top-Down Architecture
┌─────────────────────────────────────────────────────────────────────────┐
│ AgentCore.jl Layers │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Level 1: AgentHarness (Session Management & Persistence) │
│ - Session persistence with JSONL storage │
│ - Resource management (skills, prompt templates) │
│ - Extension hooks system │
│ - Branch navigation and compaction │
└─────────────────────────────────────────────────────────────────────────┘
│
│ orchestrates
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Level 2: Agent (State Management & Event Streaming) │
│ - Conversation state (messages, tools, system prompt) │
│ - Event streaming and lifecycle management │
│ - Steering and follow-up message queues │
│ - Abort handling │
└─────────────────────────────────────────────────────────────────────────┘
│
│ delegates to
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Level 3: AgentLoop (Core LLM Interaction Loop) │
│ - Stateful LLM interactions │
│ - Tool execution (parallel or sequential) │
│ - Event emission lifecycle │
│ - Steering/follow-up message handling │
└─────────────────────────────────────────────────────────────────────────┘
│
│ transforms to
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Level 4: Session (Conversation History Management) │
│ - Tree-based conversation history │
│ - Branch support with compaction │
│ - Message and metadata persistence │
└─────────────────────────────────────────────────────────────────────────┘
Process Flow
1. Agent Lifecycle
┌─────────────────────────────────────────────────────────────────────────┐
│ Agent Lifecycle │
└─────────────────────────────────────────────────────────────────────────┘
User Code
│
│ 1. Create Agent
▼
┌──────────────┐
│ Agent() │ ──► Initialize state, queues, listeners
└──────────────┘
│
│ 2. Subscribe to events
▼
┌──────────────────┐
│ subscribe() │ ──► Register event handlers
└──────────────────┘
│
│ 3. Run prompt
▼
┌──────────────────┐
│ prompt() │ ──► Validate input, normalize messages
└──────────────────┘
│
│ 4. Start AgentLoop
▼
┌──────────────────┐
│ runPromptMessages│ ──► Create ActiveRun, spawn loop
└──────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ AgentLoop (runs in separate thread) │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ 1. Emit AgentStartEvent │ │
│ │ 2. Emit TurnStartEvent │ │
│ │ 3. Process prompts (emit MessageStart/End) │ │
│ │ 4. ┌──────────────────────────────────────────────┐ │ │
│ │ │ while true: │ │ │
│ │ │ │ Process steering/follow-up messages │ │ │
│ │ │ │ Stream assistant response (LLM call) │ │ │
│ │ │ │ Execute tool calls (parallel/sequential) │ │ │
│ │ │ │ Emit TurnEndEvent │ │ │
│ │ │ │ Check if should stop │ │ │
│ │ │ │ Get next steering messages │ │ │
│ │ └───┴────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
│ 5. Event streaming
▼
┌──────────────────┐
│ Event Handlers │ ──► User-defined listeners receive events
└──────────────────┘
│
│ 6. Wait for completion
▼
┌──────────────────┐
│ waitForIdle() │ ──► Resolve when all events processed
└──────────────────┘
2. AgentLoop Flow Diagram
┌─────────────────────────────────────────────────────────────────────────┐
│ AgentLoop Process Flow │
└─────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ AgentLoop Entrypoint │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ agentLoop(prompts, context, config, signal, stream_fn) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ runAgentLoop(prompts, context, config, emit, signal) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ runLoop() - Main Event Loop │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────┼───────────────────────────────────────┘
│
│ Loop Iteration
▼
┌────────────────────────────────────────────────────────────────────┐
│ Main Processing Loop │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. Get Steering/Follow-up Messages │ │
│ │ ┌────────────────────┐ ┌──────────────────────┐ │ │
│ │ │ steering_queue │ │ follow_up_queue │ │ │
│ │ │ (after assistant) │ │ (after stop) │ │ │
│ │ └────────────────────┘ └──────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 2. Stream Assistant Response │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ transform_context() │ │ │
│ │ │ convert_to_llm(messages) -> Message[] │ │ │
│ │ │ stream_fn(model, context, config) -> Response │ │ │
│ │ │ - Text deltas │ │ │
│ │ │ - Tool call deltas │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ Emit: MessageStartEvent, MessageUpdateEvent, │ │ │
│ │ │ MessageEndEvent │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 3. Execute Tool Calls │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ extract ToolCall from assistant content │ │ │
│ │ │ │ │ │
│ │ │ if EXECUTION_SEQUENTIAL || has_sequential_tool: │ │ │
│ │ │ executeToolCallsSequential() │ │ │
│ │ │ else: │ │ │
│ │ │ executeToolCallsParallel() │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ For each tool call: │ │ │
│ │ │ 1. before_tool_call hook │ │ │
│ │ │ 2. prepareToolCall() │ │ │
│ │ │ 3. execute() │ │ │
│ │ │ 4. after_tool_call hook │ │ │
│ │ │ 5. Emit ToolExecutionStart/Update/EndEvent │ │ │
│ │ │ 6. Emit ToolResultMessage │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 4. Prepare Next Turn │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ prepare_next_turn(context) -> next_turn_snapshot │ │ │
│ │ │ - Optional: Update model/thinking_level │ │ │
│ │ │ - Optional: Update context │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 5. Check Termination Conditions │ │
│ │ ┌────────────────────────────────────────────────────┐ │ │
│ │ │ should_stop_after_turn(context) -> bool │ │ │
│ │ │ - Max turns reached? │ │ │
│ │ │ - Tool returned terminate=true? │ │ │
│ │ │ - Steering queue empty and follow-up empty? │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 6. Emit TurnEndEvent (message, tool_results) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Loop continues until termination condition met │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────┼───────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────┐
│ AgentEndEvent with final messages │
└────────────────────────────────────────────────────────────────────┘
3. Tool Execution Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ Tool Execution Flow │
└─────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────┐
│ Assistant Message with Tool Calls │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ AssistantMessage: │ │
│ │ content: [ │ │
│ │ TextContent("I'll help you"), │ │
│ │ ToolCall(id="tc1", name="bash", args={...}), │ │
│ │ ToolCall(id="tc2", name="read", args={...}) │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
└───────────────────────────────────────────────────────────────────┘
│
│ executeToolCalls()
▼
┌───────────────────────────────────────────────────────────────────┐
│ Determine Execution Mode │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ config.tool_execution == EXECUTION_SEQUENTIAL? │ │
│ │ OR any tool has execution_mode == EXECUTION_SEQUENTIAL? │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌────────────────────────┐ ┌────────────────────────┐ │
│ │ executeSequential() │ │ executeParallel() │ │
│ └────────────────────────┘ └────────────────────────┘ │
│ │ │ │
└──────────────┼───────────────────────────────┼────────────────────┘
│ │
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Sequential Execution │ │ Parallel Execution │
│ │ │ │
│ for tool_call in: │ │ for tool_call in: │
│ prepareToolCall() │ │ prepareToolCall() │
│ execute() │ │ execute() (async) │
│ finalize() │ │ │
│ │ │ wait all results │
│ │ └──────────────────────┘
└──────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────┐
│ For Each Tool Call │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 1. before_tool_call hook (optional) │ │
│ │ - Can block execution │ │
│ │ 2. prepareToolCall() │ │
│ │ - validateToolArguments() │ │
│ │ - prepareToolCallArguments() (optional) │ │
│ │ 3. Execute Tool: │ │
│ │ tool.execute(tool_call_id, args, signal, on_update) │ │
│ │ 4. after_tool_call hook (optional) │ │
│ │ - Can modify result content │ │
│ │ 5. Emit events: │ │
│ │ - ToolExecutionStartEvent │ │
│ │ - ToolExecutionUpdateEvent (optional) │ │
│ │ - ToolExecutionEndEvent │ │
│ │ 6. Create ToolResultMessage │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────┐
│ Tool Result Messages │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ToolResultMessage: │ │
│ │ role: "toolResult" │ │
│ │ tool_call_id: "tc1" │ │
│ │ tool_name: "bash" │ │
│ │ content: [TextContent("command output")] │ │
│ │ is_error: false │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
4. Session & Tree Structure
┌─────────────────────────────────────────────────────────────────────────┐
│ Session Tree Structure │
└─────────────────────────────────────────────────────────────────────────┘
Session = Linked List of Entries (tree structure)
┌───────────────────────────────────────────────────────────────────┐
│ Branch Navigation │
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ E1 │────▶│ E2 │────▶│ E3 │────▶│ E4 │────▶│ E5 │ (leaf) │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ │
│ Message Message Compaction Message BranchSummary │
│ │
│ E3 is a Compaction Entry: │
│ - Summary of E1, E2 │
│ - first_kept_entry_id: reference to first retained message │
│ - tokens_before: context size before compaction │
│ │
│ E5 is a BranchSummary Entry: │
│ - Summary of branch from from_id │
│ - Represents a fork point in conversation history │
│ │
└───────────────────────────────────────────────────────────────────┘
│
│ Session.moveTo()
▼
┌───────────────────────────────────────────────────────────────────┐
│ Forking & Branching │
│ │
│ Current branch: │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ E1 │────▶│ E2 │────▶│ E3 │ │
│ └─────┘ └─────┘ └─────┘ │
│ │ │
│ │ moveTo(E2) │
│ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ E1 │────▶│ E2 │────▶│ E3' │────▶│ E4' │ (new branch) │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │ │
│ │ create BranchSummary │
│ ▼ │
│ ┌─────┐ │
│ │ E5 │ (branch summary) │
│ └─────┘ │
│ │
└───────────────────────────────────────────────────────────────────┘
Component Relationships
┌─────────────────────────────────────────────────────────────────────────────┐
│ Component Relationships │
└─────────────────────────────────────────────────────────────────────────────┘
User Code
│
├── Creates ──► Agent
│ │
│ ├── Uses ──► AgentLoop
│ │ │
│ │ ├── Uses ──► StreamFn (LLM API)
│ │ │
│ │ └── Uses ──► Session
│ │
│ ├── Manages ──► AgentState
│ │
│ ├── Queues ──► SteeringQueue
│ │
│ └── Queues ──► FollowUpQueue
│
└── Interacts With ──► AgentHarness (optional, higher level)
│
├── Manages ──► SessionRepo
│
├── Manages ──► Skills
│
└── Manages ──► PromptTemplates
Data Flow
┌─────────────────────────────────────────────────────────────────────────────┐
│ Data Flow Between Layers │
└─────────────────────────────────────────────────────────────────────────────┘
User Input (String/Message)
│
▼
┌──────────────────────┐
│ Agent.prompt() │
│ - normalizeInput() │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ AgentState.messages │ ──► AgentMessage[]
└──────────────────────┘
│
▼
┌──────────────────────┐
│ AgentLoop │
│ - transform_context │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ convertToLlm() │ ──► Transforms AgentMessage[] to Message[]
└──────────────────────┘
│
▼
┌──────────────────────┐
│ LLM API (StreamFn) │
│ - Context: Message[] │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Response (Streaming) │
│ - Text deltas │
│ - Tool call deltas │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ AssistantMessage │
│ - content: Message[] │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ AgentState.messages │ ──► Appended to conversation
└──────────────────────┘
│
▼
┌──────────────────────┐
│ Tool Execution │
│ - Extract ToolCalls │
│ - Execute tools │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ ToolResultMessage[] │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ AgentState.messages │ ──► Tool results appended
└──────────────────────┘
│
│ (Loop back to LLM or end)
▼
┌──────────────────────┐
│ Session Storage │
│ - JSONL format │
│ - Tree entries │
└──────────────────────┘
Summary
The AgentCore.jl architecture follows a clean separation of concerns:
- AgentHarness - Highest level, handles persistence and resources
- Agent - State management and event streaming
- AgentLoop - Core LLM interaction loop
- Session - Conversation history management
Each layer transforms data and passes it to the next layer, with clear interfaces and event hooks for customization.