Files
pi_harness/packages/agent/learning/10-QUICK-START.md
T
2026-07-29 13:20:58 +07:00

404 lines
11 KiB
Markdown

# 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<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
```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.