404 lines
11 KiB
Markdown
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.
|