Write Your First Agent
Last updated: 2026-07-13
In this tutorial we build a minimal agent that can call a tool, using pi-agent-core. By the end you should understand what Pi's core loop looks like, what AgentSession and Agent each own, and how to watch the agent step by step.
Skim Architecture and Core concepts first for speed; the boundaries between packages are explained in Core packages.
The core idea
Pi's runtime separates the "unstable outside world" from a "stable core loop":
Wrap business capability as tools and state the goal in a prompt.
Agent holds tools and model; Session holds conversation context; Loop drives "call model → run tool → write back".
Hides vendor differences; AgentLoop calls the model through it.
Step by step
- Define a tool: declare a callable function with a decorator, giving its name, purpose and parameters. Here is a "read file" tool.
- Build the Agent: hand a model instance and the tool list to
Agentto get a runtime that can call tools. - Build an AgentSession: create a session from the Agent and write the system prompt and a user message.
- Run and observe events: subscribe to the
AgentEventstream to see model replies andtoolCallinvocation and results in real time. - Understand the core loop: the loop repeats "call model → run tool → write back" until the model returns a final answer with no tool call.
Minimal code example (TypeScript style)
import { Agent, tool, AgentSession } from '@earendil-works/pi-agent-core';
import { createModel } from '@earendil-works/pi-ai';
// 1) define a tool: read a file's contents
const readFile = tool({
name: 'read_file',
description: 'Read the contents of a file at the given path',
parameters: { path: { type: 'string', description: 'Absolute or relative path' } },
async run({ path }) {
return await fs.readFile(path, 'utf8');
},
});
// 2) build the Agent (model + tools)
const model = createModel('openai/gpt-4o');
const agent = new Agent({ model, tools: [readFile] });
// 3) build a session and write messages
const session = new AgentSession(agent);
session.pushSystem('You are a careful coding assistant; read first, then answer.');
session.pushUser('Read ./package.json and tell me which scripts it defines.');
// 4) run and observe events
for await (const event of session.run()) {
if (event.type === 'text') console.log('model:', event.text);
if (event.type === 'toolCall') console.log('call:', event.name, event.args);
if (event.type === 'toolResult') console.log('result:', event.result);
}
What the core loop actually is
The session.run() above drives Pi's stable kernel:
- Pack the system prompt, history and tool definitions into a context;
- Call the model through
pi-ai; - If the model returns a
toolCall, run the matching tool and write the result back into the context; - Call the model again with the new result, until it stops calling tools and gives a final answer.
Try extending it
- Add a
run_shelltool so the agent can execute commands; - Switch the model to
MockModeland run the whole example offline; - Attach the session to the JSONL session tree from Architecture to watch every toolCall and branch.
Where to go next
You now have the smallest possible agent. From here, most people add more tools, stream events to a UI, or load extensions at runtime to "self-extend" the agent without a rebuild. The Core concepts page explains messages, the tool-calling mechanism and the session tree in depth, and the Chinese first-agent tutorial shows the same example with more commentary. When you are ready to ship, read Running & debugging for the permission model and sandbox options.
Frequently asked questions
What is the difference between Agent and AgentSession?
Agent is the runtime itself — it defines behavior and capability (tools + model). AgentSession is the context container for one conversation, holding message history, state and branches. One Agent can create many Sessions.
What should a tool function return?
A tool is just an async function annotated with name, description and a parameter schema; its return value is serialized and sent back to the model as the toolCall result, feeding the next loop iteration.
Can I debug without a real model?
Yes. pi-ai provides a MockModel that simulates model replies offline, so you can focus on verifying tool definitions and core-loop logic without spending tokens.
What information is in an AgentEvent?
A typical AgentEvent carries streamed model text, toolCall invocations and their results, and session state changes. Subscribing to it lets a UI or log show the agent's thinking and actions in real time.