Welcome to the pi-mono project tutorial! We are kicking things off with the most fundamental building block for creating a persistent, interactive AI experience: the Agent Session.
Imagine you have a brilliant AI "brain" (the Large Language Model). It can answer questions and write code. However, if you turn it off, it forgets everything. If the conversation gets too long, it gets confused. If it crashes, you lose your progress.
The Agent Session acts as the "body" and "operating system" for that AI brain. It solves three critical problems:
In this chapter, we will learn how AgentSession wraps the raw AI runtime to create a robust, usable application.
Before looking at code, let's understand the machinery.
The AgentSession is a wrapper class. It holds an instance of an Agent (the core logic) and a SessionManager (the storage logic). When you want to talk to the AI, you talk to the Session, not the Agent directly.
Instead of manually saving after every line of code, the Session "subscribes" to the Agent. When the Agent finishes speaking, the Session automatically catches that event and writes it to disk.
Because the Session manages the history file, it allows for "branching." You can go back to a previous message, edit it, and the Session creates a new timeline (fork) without deleting the old one.
Let's look at how AgentSession is used to create a chat loop.
First, we create the session. We need to give it an Agent (the brain) and a SessionManager (the notebook).
const session = new AgentSession({
agent: myAgent,
sessionManager: mySessionManager,
settingsManager: mySettings,
cwd: process.cwd(), // Current working directory
resourceLoader: myLoader,
modelRegistry: myRegistry
});
Explanation: We instantiate the class with dependencies. This sets up the environment where the AI will "live."
We want to see what's happening, so we subscribe to the session.
// Listen to everything the session does
session.subscribe((event) => {
if (event.type === "message_start") {
console.log("AI is starting to type...");
}
if (event.type === "auto_compaction_start") {
console.log("Context is full! Summarizing...");
}
});
Explanation: The subscribe method lets the UI (like a terminal or web app) know what's happening without knowing the complex logic inside.
Finally, we send a user message.
try {
// Send a message to the AI
await session.prompt("Write a Hello World program");
} catch (error) {
console.error("Something went wrong:", error);
}
Explanation: The prompt method does a lot of heavy lifting: it checks your API keys, handles template expansion (like filling in variables), and then passes the message to the AI.
What happens when you call session.prompt()? It's not just a pass-through. The Session acts as a guardian.
/skill:coding)? If so, expand them into full text.Agent.Agent work. If it emits a message_end event, save it to the session file.
Let's look at how AgentSession implements these features internally.
The constructor sets up a listener immediately. This ensures that no matter how the agent is run, data is always saved.
constructor(config: AgentSessionConfig) {
// ... setup config ...
// Always subscribe to agent events for internal handling
// (session persistence, extensions, auto-compaction)
this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
}
Explanation: this.agent.subscribe connects the Session to the Agent's nervous system. _handleAgentEvent is the central brain for processing these signals.
Here is a simplified view of _handleAgentEvent. This is where the magic of "auto-saving" happens.
private _handleAgentEvent = async (event: AgentEvent): Promise<void> => {
// 1. Notify external listeners (like the UI)
this._emit(event);
// 2. Handle Persistence
if (event.type === "message_end") {
// Save the message to the history file
this.sessionManager.appendMessage(event.message);
}
};
Explanation: When the agent finishes a message (message_end), the Session grabs that message and tells sessionManager to write it down. This separation ensures the Agent doesn't need to know about file systems.
The prompt method ensures everything is ready before bothering the AI.
async prompt(text: string, options?: PromptOptions): Promise<void> {
// 1. Check for Model and API Key
if (!this.model) throw new Error("No model selected.");
const apiKey = await this._modelRegistry.getApiKey(this.model);
if (!apiKey) throw new Error("No API key found.");
// 2. Expand Templates (e.g. loading file contents)
let expandedText = expandPromptTemplate(text, [...this.promptTemplates]);
// 3. Send to Agent
await this.agent.prompt([{ role: "user", content: expandedText }]);
}
Explanation: This logic prevents the Agent from crashing due to missing configuration. It also handles "Prompt Templates," which we will discuss in the Standard Tools chapter.
One of the most advanced features of AgentSession is keeping the memory clean.
private async _checkCompaction(assistantMessage: AssistantMessage): Promise<void> {
const settings = this.settingsManager.getCompactionSettings();
// Check if we have exceeded the context window threshold
if (shouldCompact(currentTokens, contextWindow, settings)) {
// Trigger the compaction process
await this._runAutoCompaction("threshold", false);
}
}
Explanation: After every turn, the Session checks the token count. If it's too high, it pauses and summarizes older messages. You can learn more about this in the Context Compaction chapter.
The Agent Session is the sturdy container that holds your AI application together. It turns a raw "text-in, text-out" engine into a persistent, robust assistant that remembers your history and manages its own lifecycle.
In this chapter, we learned:
In the next chapter, we will peel back the wrapper and look at the "brain" itself: the Agent Runtime.
Generated by Code IQ