# Quick Start Guide ## Learning the Pi Agent Architecture This guide helps you quickly understand the agent system and prepare for reimplementation in Julia. --- ## Phase 1: Core Concepts (30 minutes) ### 1. Two-Layer Architecture ``` ┌────────────────────────────────────────────────────────────────────┐ │ Application Layer │ │ ┌──────────────┐ ┌─────────────────┐ ┌───────────────────────┐ │ │ │ Agent (Core) │ │ AgentHarness │ │ Your Custom App │ │ │ └───────┬──────┘ └────────┬────────┘ └─────────┬────────────┘ │ └──────────┼───────────────────┼─────────────────────┼────────────────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │ agent-loop.ts│ │ agent-harness.ts│ │ Session Repo │ │ types.ts │ │ │ │ │ └──────────────┘ └─────────────────┘ └────────────────┘ ``` **Key Insight**: - **Agent Core** = Low-level async iteration (messages in, messages out) - **AgentHarness** = High-level session management with persistence --- ### 2. Core Data Types ```typescript // Message: Basic unit of conversation interface Message { role: "user" | "assistant" | "toolResult"; content: (TextContent | ImageContent)[]; timestamp: number; } // Tool: Function the agent can call interface AgentTool { name: string; label: string; description: string; parameters: Schema; execute(toolCallId, params, signal, onUpdate): Promise; } // Event: Notification of state changes type AgentEvent = | { type: "agent_start" } | { type: "agent_end"; messages: Message[] } | { type: "turn_start" } | { type: "turn_end"; message: Message; toolResults: Message[] } | { type: "message_start"; message: Message } | { type: "message_update"; message: Message } | { type: "message_end"; message: Message } | { type: "tool_execution_start"; ... } | { type: "tool_execution_end"; ... }; ``` --- ## Phase 2: Message Flow (45 minutes) ### The Agent Loop ``` 1. User Input └─► Agent.prompt("Hello") 2. Agent Start └─► emit: agent_start, turn_start, message_start/end (user) 3. LLM Streaming └─► streamAssistantResponse() └─► transformContext() → convertToLlm() → streamFn() 4. Tool Execution └─► executeToolCalls() └─► prepare → execute → finalize (for each tool) 5. Turn End └─► emit: turn_end └─► Check hooks, drain queues, decide next turn 6. Repeat or End └─► Loop continues until no more work ``` ### Key Insight **Everything is a message**: User input, assistant response, tool calls, tool results, steering messages. **Everything is an event**: State changes are emitted as events for UI updates. --- ## Phase 3: Hooks System (30 minutes) ### Hook Categories | Category | Purpose | When Called | |----------|---------|-------------| | `convertToLlm` | Filter/transform messages | Before LLM call | | `transformContext` | Manipulate context | Before LLM call | | `beforeToolCall` | Block tool execution | Before tool runs | | `afterToolCall` | Override tool results | After tool runs | | `shouldStopAfterTurn` | Request early stop | After turn ends | | `prepareNextTurn` | Update config | Before next turn | | `getSteeringMessages` | Interrupt agent | After turn ends | | `getFollowUpMessages` | Queue messages | When agent stops | ### Hook Flow ``` Agent.prompt("Build app") │ ├─► transformContext() [hook] │ ├─► convertToLlm() [hook] │ ├─► LLM call │ ├─► executeToolCalls() │ ├─► beforeToolCall() [hook] │ ├─► tool.execute() │ └─► afterToolCall() [hook] │ └─► turn_end ├─► shouldStopAfterTurn() [hook] ├─► prepareNextTurn() [hook] ├─► getSteeringMessages() [hook] └─► getFollowUpMessages() [hook] ``` --- ## Phase 4: AgentHarness (45 minutes) ### High-Level API ```typescript // Create harness const harness = new AgentHarness({ session: session, models: models, tools: [weatherTool, gitTool], activeToolNames: ["weather", "git"], model: gpt4Model, thinkingLevel: "medium" }); // Main operations await harness.prompt("What's the weather in London?"); // Queue management await harness.steer("Wait, check this first"); // Interrupt await harness.followUp("Now summarize"); // After agent stops await harness.nextTurn("Also deploy"); // Next turn // Session management await harness.compact(); // Compress context await harness.navigateTree(entryId); // Branch conversation ``` ### Session Tree ``` Session = Conversation History as a Tree root ├─ message [user prompt #1] │ └─ message [assistant #1] │ └─ tool_result [result] │ └─ message [user prompt #2] │ └─ compaction [summary] │ ├─ retained: [recent messages] │ └─ message [assistant continues] │ └─ leaf [current head] │ └─ branch_summary [point where branch created] └─ message [new branch] └─ leaf [new head] ``` **Key Operations**: - `buildContext()` → Get LLM context from tree - `fork()` → Create branch at point - `compact()` → Summarize history --- ## Phase 5: Tool Execution (30 minutes) ### Tool Lifecycle ``` 1. LLM sends tool call └─► AssistantMessage with toolCall block 2. prepareToolCall() ├─► Find tool by name ├─► Validate arguments └─► beforeToolCall() hook 3. executePreparedToolCall() └─► tool.execute() with onUpdate callback 4. finalizeExecutedToolCall() └─► afterToolCall() hook 5. Emit events ├─► tool_execution_start ├─► tool_execution_update (streaming) └─► tool_execution_end ``` ### Tool Definition ```typescript const weatherTool: AgentTool = { name: "get_weather", label: "Get Weather", description: "Get current weather for a city", parameters: Type.Object({ city: Type.String() }), execute: async (toolCallId, params, signal, onUpdate) => { // Check for abort if (signal?.aborted) throw new Error("Aborted"); // Long operation with streaming const result = await fetchWeather(params.city); onUpdate({ content: [{ type: "text", text: "Fetching..." }] }); return { content: [{ type: "text", text: result }], details: { city: params.city, temp: result.temp }, usage: { input: 0, output: 0, ... } }; } }; ``` --- ## Phase 6: Session Persistence (30 minutes) ### Entry Types | Type | Purpose | |------|---------| | `message` | User/assistant/toolResult | | `model_change` | Model switch | | `thinking_level_change` | Reasoning level | | `active_tools_change` | Tools change | | `compaction` | History summary | | `branch_summary` | Branch point | | `custom` | App data (not visible to model) | | `custom_message` | Custom message | | `label` | User label | | `leaf` | Current head | ### Context Building ```typescript // 1. Get path from leaf to root const pathEntries = await session.getBranch(); // 2. Apply transforms (compaction) const contextEntries = defaultContextEntryTransform(pathEntries); // 3. Project entries to messages const messages = contextEntries.flatMap(sessionEntryToContextMessages); // 4. Derive state (model, thinking level, active tools) const state = deriveSessionContextState(pathEntries); // 5. Return context return { ...state, messages }; ``` --- ## Summary ### What to Remember 1. **Two layers**: Agent (core) + AgentHarness (high-level) 2. **Messages everywhere**: Input, output, tools, events 3. **Hooks for customization**: Transform messages, block tools, override results 4. **Session = Tree**: Persistent conversation history with branching 5. **Events for UI**: All state changes emitted as events 6. **Tool lifecycle**: Prepare → Execute → Finalize → Emit ### Next Steps 1. **Read the detailed docs**: - `01-ARCHITECTURE-OVERVIEW.md` - Big picture - `02-AGENT-LOOP-DETAILED.md` - Core loop - `03-HOOK-SYSTEM.md` - Hooks reference - `04-SESSION-ARCHITECTURE.md` - Session system - `05-TOOL-EXECUTION.md` - Tool system - `06-AGENTHARNESS-REFERENCE.md` - API reference - `07-DATA-FLOW-STATE.md` - Data flow - `08-LEARNING-PATH.md` - Study guide - `09-DIAGRAMS.md` - Visual diagrams 2. **Design your Julia implementation**: - Data types - Core agent loop - Hook system - Session persistence - Tool execution 3. **Start coding**: - Implement basic types - Implement core loop - Add hooks - Add session - Add harness ### Common Patterns **Context window management**: ```typescript transformContext: async (messages) => { if (estimateTokens(messages) > MAX_TOKENS) { return pruneOldMessages(messages); } return messages; } ``` **Tool permission checks**: ```typescript beforeToolCall: async ({ toolCall }) => { if (toolCall.name === "bash" && !await canExecute()) { return { block: true, reason: "Permission denied" }; } return undefined; } ``` **Streaming updates**: ```typescript execute: async (id, params, signal, onUpdate) => { for await (const chunk of process()) { onUpdate({ content: [{ type: "text", text: `Progress: ${chunk}%` }] }); } return finalResult; } ``` --- ## Quick Reference ### Agent Core (agent.ts, agent-loop.ts) | Function | Purpose | |----------|---------| | `runAgentLoop()` | Start new conversation | | `runAgentLoopContinue()` | Continue existing | | `runLoop()` | Main iteration | | `streamAssistantResponse()` | Stream LLM | | `executeToolCalls()` | Execute tools | ### AgentHarness API | Method | Purpose | |--------|---------| | `prompt()` | Run conversation | | `steer()` | Interrupt agent | | `followUp()` | Queue message | | `compact()` | Compress context | | `navigateTree()` | Branch conversation | ### Hook Types | Hook | Purpose | |------|---------| | `convertToLlm` | Convert messages | | `beforeToolCall` | Block tools | | `afterToolCall` | Override results | | `shouldStopAfterTurn` | Request stop | ### Entry Types | Type | Purpose | |------|---------| | `message` | Conversation messages | | `compaction` | History summary | | `branch_summary` | Branch point | --- **You now have the foundation to reimplement the agent in Julia!** Start with data types and the core loop, then add hooks, session, and harness layers incrementally.