Files
pi_harness/packages/agent/learning/08-LEARNING-PATH.md
T
2026-07-29 10:59:18 +07:00

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

  1. 01-ARCHITECTURE-OVERVIEW.md - Read this first
  2. 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

  1. Draw the architecture diagram from memory
  2. List 3 use cases for each hook type
  3. Trace a message from input to LLM to output

Phase 2: Core Agent (2-3 hours)

Goal: Understand the low-level agent loop

Resources

  1. 02-AGENT-LOOP-DETAILED.md - Study the agent loop
  2. Read src/agent-loop.ts (skim, focus on comments)
  3. 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

  1. Trace through a conversation with 1 prompt + 2 tool calls
  2. Draw the outer/inner loop flow
  3. Explain how abort signals propagate
  4. Explain queue draining (steering/follow-up)

Phase 3: Hooks System (2-3 hours)

Goal: Understand how to customize agent behavior

Resources

  1. 03-HOOK-SYSTEM.md - Study all hooks
  2. Read src/types.ts - Hook types and contexts

Hook Categories

Message Transformation:

  • convertToLlm - Convert messages to LLM format
  • transformContext - Manipulate context before LLM

Lifecycle Hooks:

  • beforeToolCall - Block or modify tool execution
  • afterToolCall - Override tool results
  • shouldStopAfterTurn - Request early termination
  • prepareNextTurn - Update context/model/thinking

Queue Draining:

  • getSteeringMessages - Interrupt agent mid-work
  • getFollowUpMessages - 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

  1. Implement a hook that logs all tool calls
  2. Implement a hook that blocks dangerous commands
  3. Implement a hook that summarizes conversation every 5 turns
  4. 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

  1. 06-AGENTHARNESS-REFERENCE.md - Study the harness API
  2. 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 conversation
  • skill() - Execute skill
  • promptFromTemplate() - Run template

Queues:

  • steer() - Interrupt agent
  • followUp() - Queue message
  • nextTurn() - Queue for next turn

Session:

  • compact() - Compress context
  • navigateTree() - Branch conversation

State:

  • setModel() - Change model
  • setThinkingLevel() - Change reasoning level
  • setTools() / 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

  1. Create a session, add messages, and build context
  2. Implement branching and navigate between branches
  3. Implement compaction and verify it works
  4. Set up hooks for tool call logging

Phase 5: Session Architecture (2-3 hours)

Goal: Understand persistence and tree structure

Resources

  1. 04-SESSION-ARCHITECTURE.md - Study session system
  2. 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

  1. Create a session and trace its tree
  2. Add custom entries and verify they don't appear in context
  3. Fork a session and compare contexts
  4. Compact a session and verify size reduction

Phase 6: Tool Execution (2-3 hours)

Goal: Understand how tools work

Resources

  1. 05-TOOL-EXECUTION.md - Study tool system
  2. 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

  1. Implement a custom tool (e.g., weather API)
  2. Implement streaming updates for long operation
  3. Implement tool with error handling
  4. Test sequential vs parallel execution

Phase 7: Data Flow (2-3 hours)

Goal: Understand how data flows through the system

Resources

  1. 07-DATA-FLOW-STATE.md - Study data flow
  2. 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

  1. Trace a message through the entire flow
  2. Trace a tool call through all hooks
  3. Trace an abort through the system
  4. Draw the complete data flow diagram

Phase 8: Implementation (4-6 hours)

Goal: Implement your own version

Steps

  1. Design your data structures (in Julia)

    • AgentMessage equivalent
    • AgentEvent equivalent
    • AgentTool equivalent
  2. Implement core agent loop

    • Message streaming
    • Tool execution
    • Event emission
  3. Add hooks system

    • Hook registration
    • Hook execution
    • Return value handling
  4. Implement session persistence

    • Tree structure
    • Entry types
    • Context building
  5. Add harness layer

    • High-level API
    • Queue management
    • Branching
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:

  1. Implement in Julia

    • Start with data types
    • Implement core loop
    • Add hooks
    • Implement session
  2. Extend Functionality

    • Add new tool types
    • Implement custom hooks
    • Add new entry types
  3. Optimize

    • Improve token estimation
    • Optimize context pruning
    • Parallelize operations
  4. Production

    • Error handling
    • Logging
    • Monitoring

Questions to Test Understanding

  1. How would you implement a tool that requires user approval?
  2. How would you implement conversation summarization every 10 turns?
  3. How would you implement context pruning based on importance?
  4. How would you implement branching with automatic summaries?
  5. How would you implement tool execution rate limiting?

Summary

Top-down learning:

  1. Big picture (layers, components)
  2. Core agent (loop, streaming)
  3. Hooks (customization)
  4. Harness (session, persistence)
  5. Data flow (how everything connects)

Key insight: The system is built on messages and events with hooks for customization.