Files
pi_harness/packages/agent/learning/10-QUICK-START.md
T
2026-07-29 10:59:18 +07:00

11 KiB

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

// 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<Result>;
}

// 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

// 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

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

// 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:

transformContext: async (messages) => {
  if (estimateTokens(messages) > MAX_TOKENS) {
    return pruneOldMessages(messages);
  }
  return messages;
}

Tool permission checks:

beforeToolCall: async ({ toolCall }) => {
  if (toolCall.name === "bash" && !await canExecute()) {
    return { block: true, reason: "Permission denied" };
  }
  return undefined;
}

Streaming updates:

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.