13 KiB
Learning Path and Study Guide
Overview
This guide helps you learn the Pi Agent architecture top-down, starting from high-level concepts to implementation details.
Phase 1: Big Picture (1-2 hours)
Goal: Understand how components fit together
Resources
- 01-ARCHITECTURE-OVERVIEW.md - Read this first
- Diagrams - Study the architecture diagrams
Key Questions to Answer
✅ What are the two main layers?
✅ What does each layer do?
✅ How do messages flow through the system?
✅ What is the relationship between Agent and AgentHarness?
✅ What are the main event types?
✅ How do tools integrate with the agent?
✅ What is the purpose of hooks?
Exercises
- Draw the architecture diagram from memory
- List 3 use cases for each hook type
- Trace a message from input to LLM to output
Phase 2: Core Agent (2-3 hours)
Goal: Understand the low-level agent loop
Resources
- 02-AGENT-LOOP-DETAILED.md - Study the agent loop
- Read
src/agent-loop.ts(skim, focus on comments) - Read
src/types.ts- Understand AgentEvent, AgentMessage, AgentTool
Key Concepts
- runAgentLoop() - Starts a new conversation
- runAgentLoopContinue() - Continues existing conversation
- runLoop() - Main iteration (outer and inner loops)
- streamAssistantResponse() - Streams LLM response
- executeToolCalls() - Executes tool calls
- prepareToolCall() - Validates and prepares tools
- executePreparedToolCall() - Executes tool with updates
- finalizeExecutedToolCall() - Finalizes with hooks
Key Questions to Answer
✅ What's the difference between outer and inner loop?
✅ How does streaming work?
✅ How are tool calls executed (sequential vs parallel)?
✅ What happens when a tool is blocked?
✅ How are errors handled?
✅ What are the four phases of tool execution?
✅ How does the loop know when to stop?
Exercises
- Trace through a conversation with 1 prompt + 2 tool calls
- Draw the outer/inner loop flow
- Explain how abort signals propagate
- Explain queue draining (steering/follow-up)
Phase 3: Hooks System (2-3 hours)
Goal: Understand how to customize agent behavior
Resources
- 03-HOOK-SYSTEM.md - Study all hooks
- Read
src/types.ts- Hook types and contexts
Hook Categories
Message Transformation:
convertToLlm- Convert messages to LLM formattransformContext- Manipulate context before LLM
Lifecycle Hooks:
beforeToolCall- Block or modify tool executionafterToolCall- Override tool resultsshouldStopAfterTurn- Request early terminationprepareNextTurn- Update context/model/thinking
Queue Draining:
getSteeringMessages- Interrupt agent mid-workgetFollowUpMessages- Queue messages for later
Key Questions to Answer
✅ What hooks receive abort signals?
✅ What hooks can block execution?
✅ What is the execution order of hooks?
✅ What's the difference between beforeToolCall and afterToolCall?
✅ How do you implement context window management?
✅ How do you implement permission checks?
✅ What's the difference between steering and follow-up?
Exercises
- Implement a hook that logs all tool calls
- Implement a hook that blocks dangerous commands
- Implement a hook that summarizes conversation every 5 turns
- Implement a hook that switches to high thinking for complex tasks
Phase 4: AgentHarness (3-4 hours)
Goal: Understand high-level API and session management
Resources
- 06-AGENTHARNESS-REFERENCE.md - Study the harness API
- Read
src/harness/agent-harness.ts(focus on public methods)
Key Concepts
- Session - Persistent conversation history
- SessionTreeEntry - Individual entries in conversation
- Context Building - Derive LLM context from session
- Branching - Create new conversation paths
- Compaction - Summarize old history
API Methods
Core:
prompt()- Run new conversationskill()- Execute skillpromptFromTemplate()- Run template
Queues:
steer()- Interrupt agentfollowUp()- Queue messagenextTurn()- Queue for next turn
Session:
compact()- Compress contextnavigateTree()- Branch conversation
State:
setModel()- Change modelsetThinkingLevel()- Change reasoning levelsetTools()/setActiveTools()- Manage tools
Key Questions to Answer
✅ What's the difference between steer() and followUp()?
✅ How does branching work?
✅ How does compaction work?
✅ What's the relationship between Session and SessionStorage?
✅ What's the difference between MessageEntry and CustomEntry?
✅ How are pending writes handled during active turns?
✅ What hooks does AgentHarness provide?
Exercises
- Create a session, add messages, and build context
- Implement branching and navigate between branches
- Implement compaction and verify it works
- Set up hooks for tool call logging
Phase 5: Session Architecture (2-3 hours)
Goal: Understand persistence and tree structure
Resources
- 04-SESSION-ARCHITECTURE.md - Study session system
- Read
src/harness/session/session.ts
Key Concepts
- SessionTreeEntry - Tree nodes
- Path Tracing - From leaf to root
- Context Building - Projection to messages
- Default Transform - Compaction logic
- Forking - Create branches
Key Questions to Answer
✅ How is conversation history stored?
✅ What's the difference between ID and parentId?
✅ How does the session know the current head?
✅ What entries appear in the LLM context?
✅ How does compaction work at the session level?
✅ What's the difference between fork and navigateTree()?
✅ How are custom entries different from messages?
Exercises
- Create a session and trace its tree
- Add custom entries and verify they don't appear in context
- Fork a session and compare contexts
- Compact a session and verify size reduction
Phase 6: Tool Execution (2-3 hours)
Goal: Understand how tools work
Resources
- 05-TOOL-EXECUTION.md - Study tool system
- Read
src/harness/tools/- Built-in tools
Key Concepts
- AgentTool - Tool definition
- Tool Execution Flow - Prepare → Execute → Finalize
- Sequential vs Parallel - Execution modes
- Streaming Updates - Progress updates
- Error Handling - Throw vs return error
Tool Execution Flow
prepareToolCall()
├─ Find tool
├─ Validate args
└─ beforeToolCall()
executePreparedToolCall()
└─ tool.execute()
finalizeExecutedToolCall()
└─ afterToolCall()
emitToolResult()
Key Questions to Answer
✅ What's the difference between prepareArguments and execute?
✅ How do streaming updates work?
✅ When do you throw vs return an error?
✅ How are sequential vs parallel tools different?
✅ What's in the ToolContext passed to execute()?
✅ How do you handle long-running operations?
✅ What's the terminate flag for?
Exercises
- Implement a custom tool (e.g., weather API)
- Implement streaming updates for long operation
- Implement tool with error handling
- Test sequential vs parallel execution
Phase 7: Data Flow (2-3 hours)
Goal: Understand how data flows through the system
Resources
- 07-DATA-FLOW-STATE.md - Study data flow
- Read
src/agent.ts- State management
Key Concepts
- AgentMessage - Unified message type
- AgentEvent - Event stream
- AgentContext - Snapshot for LLM
- State Mutation - How state changes on events
- Queue Flow - Steering and follow-up
Key Questions to Answer
✅ How do messages flow from input to LLM?
✅ How is state mutated on events?
✅ What's the difference between AgentContext and AgentState?
✅ How do hooks transform data?
✅ How are abort signals propagated?
✅ What's the relationship between queue mode and draining?
✅ How are pending writes handled?
Exercises
- Trace a message through the entire flow
- Trace a tool call through all hooks
- Trace an abort through the system
- Draw the complete data flow diagram
Phase 8: Implementation (4-6 hours)
Goal: Implement your own version
Steps
-
Design your data structures (in Julia)
- AgentMessage equivalent
- AgentEvent equivalent
- AgentTool equivalent
-
Implement core agent loop
- Message streaming
- Tool execution
- Event emission
-
Add hooks system
- Hook registration
- Hook execution
- Return value handling
-
Implement session persistence
- Tree structure
- Entry types
- Context building
-
Add harness layer
- High-level API
- Queue management
- Branching
Recommended Order
1. Data Types (2h)
├─ AgentMessage
├─ AgentEvent
└─ AgentTool
2. Core Loop (4h)
├─ streamAssistantResponse
├─ executeToolCalls
└─ runLoop
3. State Management (2h)
├─ AgentState
└─ Event handlers
4. Hooks (3h)
├─ Hook system
└─ Implement hooks
5. Session (4h)
├─ Tree structure
├─ Persistence
└─ Context building
6. Harness (4h)
├─ Public API
├─ Queue management
└─ Branching
Tips
- Start simple, iterate
- Test each component
- Follow TypeScript patterns
- Use your language's idioms
Quick Reference
Agent Layer
| Function | Purpose |
|---|---|
runAgentLoop() |
Start new conversation |
runAgentLoopContinue() |
Continue existing |
runLoop() |
Main iteration |
streamAssistantResponse() |
Stream LLM |
executeToolCalls() |
Execute tools |
AgentHarness Layer
| Method | Purpose |
|---|---|
prompt() |
Run conversation |
steer() |
Interrupt agent |
followUp() |
Queue message |
compact() |
Compress context |
navigateTree() |
Branch conversation |
Hooks
| Hook | Purpose |
|---|---|
convertToLlm |
Convert messages |
transformContext |
Manipulate context |
beforeToolCall |
Block tools |
afterToolCall |
Override results |
shouldStopAfterTurn |
Request stop |
prepareNextTurn |
Update config |
getSteeringMessages |
Interrupt |
getFollowUpMessages |
Queue for later |
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 marker |
custom |
App data |
custom_message |
Custom message |
label |
User label |
leaf |
Current head |
Common Patterns
1. Context Window Management
transformContext: async (messages, signal) => {
if (estimateTokens(messages) > MAX_TOKENS) {
return pruneOldMessages(messages);
}
return messages;
}
2. Tool Permission Checks
beforeToolCall: async ({ toolCall, args }, signal) => {
if (toolCall.name === "bash" && !await canExecute(args)) {
return { block: true, reason: "Permission denied" };
}
return undefined;
}
3. Streaming Updates
execute: async (id, params, signal, onUpdate) => {
for await (const chunk of process()) {
onUpdate({ content: [{ type: "text", text: `Progress: ${chunk}%` }] });
}
return finalResult;
}
4. Branching
const branchSession = await session.fork(leafId);
const branchHarness = new AgentHarness({ session: branchSession });
Study Schedule
| Week | Focus | Hours |
|---|---|---|
| 1 | Phases 1-2 | 6-8 |
| 2 | Phases 3-4 | 8-10 |
| 3 | Phases 5-6 | 6-8 |
| 4 | Phase 7-8 | 8-10 |
Total: 28-36 hours
Next Steps
After understanding the architecture:
-
Implement in Julia
- Start with data types
- Implement core loop
- Add hooks
- Implement session
-
Extend Functionality
- Add new tool types
- Implement custom hooks
- Add new entry types
-
Optimize
- Improve token estimation
- Optimize context pruning
- Parallelize operations
-
Production
- Error handling
- Logging
- Monitoring
Questions to Test Understanding
- How would you implement a tool that requires user approval?
- How would you implement conversation summarization every 10 turns?
- How would you implement context pruning based on importance?
- How would you implement branching with automatic summaries?
- How would you implement tool execution rate limiting?
Summary
Top-down learning:
- Big picture (layers, components)
- Core agent (loop, streaming)
- Hooks (customization)
- Harness (session, persistence)
- Data flow (how everything connects)
Key insight: The system is built on messages and events with hooks for customization.