Watch the agent loop call a tool
Run the real Pi 1.0.4 agent core with a scripted provider, a word-count tool and two assistant turns.
You can observe the loop without credentials. The provider scripts the assistant’s responses; the real Agent executes the tool and emits the events. This proves orchestration, not a real model’s choice of tool or interpretation of its result.
Run it
Use Node.js 22.18 or later with built-in TypeScript stripping. In an empty folder:
npm init -y
npm pkg set type=module
npm install @earendil-works/pi-agent-core@1.0.4 @earendil-works/pi-ai@1.0.4Save the complete file below as agent-core.ts, then run node agent-core.ts.
It is an exact copy of the book’s canonical example, not a replacement harness.
import { Agent, type AgentTool } from "@earendil-works/pi-agent-core";
import {
Type,
createModels,
fauxAssistantMessage,
fauxProvider,
fauxText,
fauxToolCall,
} from "@earendil-works/pi-ai";
// The provider layer: a scripted stand-in for a model.
const faux = fauxProvider();
const models = createModels();
models.setProvider(faux.provider);
// A tool is a name, a schema and an execute(). Nothing here knows about Pi.
const WordCountParams = Type.Object({ text: Type.String() });
const wordCount: AgentTool<typeof WordCountParams> = {
name: "word_count",
label: "Word Count",
description: "Count the words in a string.",
parameters: WordCountParams,
async execute(_id, params) {
const n = params.text.trim().split(/\s+/).filter(Boolean).length;
return { content: [{ type: "text", text: String(n) }], details: { words: n } };
},
};
// Turn 1 asks for the tool. Turn 2 answers from its result.
faux.setResponses([
fauxAssistantMessage([fauxToolCall("word_count", { text: "the quick brown fox" })], { stopReason: "toolUse" }),
fauxAssistantMessage([fauxText("Four words.")]),
]);
// The agent core: loop, messages, state, tool execution, events.
const agent = new Agent({
initialState: { systemPrompt: "You count words.", model: faux.getModel(), tools: [wordCount] },
streamFn: models.streamSimple.bind(models),
});
agent.subscribe((e) => {
if (e.type === "tool_execution_end") console.log("tool:", e.toolName, JSON.stringify(e.result.details));
if (e.type === "message_end" && e.message.role === "assistant") console.log("stop:", e.message.stopReason);
});
await agent.prompt("How many words in 'the quick brown fox'?");Expected outcome
The existing Chapter 1 test asserts this exact stdout at Pi 1.0.4:
stop: toolUse
tool: word_count {"words":4}
stop: stop| Event | What you learn |
|---|---|
First assistant stops with toolUse |
The scripted request names word_count |
| Tool execution ends | The real tool returned { "words": 4 } |
| Second assistant stops | A subsequent assistant turn completed |
Mechanism and limitations
Documented: the pinned agent-core exported declarations describe Agent,
AgentTool, event subscriptions and tool execution. Observed: the canonical
agent-core.test.ts runs this file and checks the three lines above. The second
response is scripted as “Four words.”; it is not evidence of model reasoning.
Full source and test are maintained in the Pi repository. The site provenance check catches source drift.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.