At work we use GitHub Copilot, and I’ve started using Pi as a lightweight terminal harness around it. Pi is a minimal agent framework that exposes the LLM loop as something you can customize — providers, tools, skills, extensions, context files, and sessions. Recently I shipped a small extension called pi-ollama-cloud-models that auto-registers Ollama Cloud models at home. Building it forced me to understand how Pi is actually put together.
This post explains Pi’s architecture in plain terms, with a focus on using GitHub Copilot as the provider and extending Pi with custom providers when you need something outside your work subscription.
What is Pi?
Pi is a minimal terminal-based coding agent harness written in TypeScript. Instead of a sealed product, it gives you a core loop — read input, call the LLM, run tools, stream results — and lets you customize almost everything around it.
Pi runs in four modes:
- Interactive — the TUI you run in a terminal.
- Print / JSON —
pi -p "query"for scripts and automation. - RPC — JSON over stdin/stdout for non-Node integrations.
- SDK — embed Pi directly in your own application.
For day-to-day coding I use interactive mode. The same primitives apply to all four modes.
Connecting Pi to GitHub Copilot
Pi supports Copilot as a subscription-based provider. To use it:
- Run
/loginin Pi. - Select GitHub Copilot.
- Press Enter for
github.com, or type your GitHub Enterprise Server domain.
If you see a “model not supported” error, enable the model in VS Code first: open Copilot Chat, click the model selector, choose the model you want, and click Enable. After that it should be selectable in Pi with /model or Ctrl+L.
Credentials are stored in ~/.pi/agent/auth.json and auto-refresh. Pi’s provider list includes built-in Copilot-capable models, and the catalog refreshes automatically. Run pi update --models to force a refresh.
That single /login step is enough to turn Pi into a Copilot-powered terminal agent. From there, the rest of Pi’s architecture becomes interesting because you can shape the harness around Copilot instead of adapting to Copilot Chat’s UI.
The agent loop
Once a provider is configured, Pi’s core loop is simple:
- Read your message.
- Combine it with the current context: system prompt, previous messages, loaded skills, and available tool definitions.
- Send the context to the active Copilot model.
- Stream back the response.
- If the model asks to run a tool, execute it and feed the result back in.
- Repeat until the model produces a final answer.
What makes Pi useful with Copilot is that you control the context and tooling around that loop.
Core components
1. Providers and models
A provider is how Pi talks to an LLM backend. Pi ships with support for many providers, but for work I care mainly about Copilot. Other built-in options include Anthropic, OpenAI, Google, Azure, Bedrock, Groq, OpenRouter, Ollama, and more — useful for side projects or when you want to compare outputs.
Each provider exposes one or more models, with metadata like:
- context window size
- max output tokens
- supported inputs (text, image)
- reasoning support
- cost per million tokens
- compatibility settings
You can switch models mid-session with /model or Ctrl+L, and cycle favorites with Ctrl+P. That means you can bounce between Copilot models, or jump to a local Ollama model for quick offline checks.
Pi also lets you register custom providers through extensions or ~/.pi/agent/models.json. That’s the hook I used for Ollama Cloud: the extension fetches the public model list, decorates each model with capabilities, and registers them under an ollama-cloud provider.
2. Extensions
Extensions are TypeScript modules that extend Pi’s behavior. They can:
- register custom tools (
pi.registerTool) - register slash commands (
pi.registerCommand) - register custom model providers (
pi.registerProvider) - subscribe to lifecycle events (
agent_start,tool_call,session_start, etc.) - manipulate the TUI: widgets, status bars, dialogs, overlays
- run bash commands via
pi.exec - inject dynamic context before each turn
An extension receives an ExtensionAPI object at init. That API is the main door into Pi. For example, here’s the shape of registering a provider:
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.registerProvider("my-provider", {
baseUrl: "https://api.example.com",
apiKey: "MY_API_KEY",
api: "openai-completions",
models: [
{
id: "my-model",
name: "My Model",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096
}
]
});
}
My Ollama Cloud extension does essentially this, but dynamically: it fetches the model catalog, builds the model configs, and re-registers the provider on startup or when /ollama-cloud-refresh is called. I wrote about the extensions I actually run in a follow-up post.
3. Skills
Skills are capability packages that give the agent focused instructions and tools for a specific task. They’re loaded on-demand, which keeps the system prompt small and avoids prompt-cache busting.
A skill might include:
- a
README.mdwith instructions for the LLM - tool definitions the skill provides
- prompt templates or examples
Skills are useful when you want Copilot to know how to do something specialized — like interact with an internal API, follow a company-specific review checklist, or work with a particular framework — without permanently adding that context to every conversation.
4. Prompt templates
Prompt templates are reusable Markdown prompts stored as files. You invoke them by typing /template-name. They’re a quick way to drop a pre-written prompt into the conversation, useful for recurring tasks like code review, refactoring, or writing tests.
5. Context files
Pi loads context from a few standard files:
- AGENTS.md — project instructions loaded from
~/.pi/agent/, parent directories, and the current working directory. - SYSTEM.md — per-project override or append to the default system prompt.
- session files — the actual conversation history.
This is where context engineering happens. You control what Copilot sees, how much history is kept, and how it’s summarized. For work projects, an AGENTS.md in the repo can encode team conventions, tech stack notes, or links to internal docs.
6. Sessions and compaction
Pi stores conversations as tree-structured JSONL files in ~/.pi/agent/sessions/. The tree structure matters: you can branch at any point, navigate back with /tree, and continue from an earlier message without losing the rest of the session.
When a session approaches the model’s context limit, Pi can compact older messages into a summary. This is customizable through extensions, so you can implement topic-based compaction, code-aware summaries, or use a different model for summarization.
You can export sessions to HTML or JSONL, or share them as a private GitHub gist via /share.
7. Tools
By default, Pi gives the model four tools:
read— read fileswrite— write filesedit— edit filesbash— run shell commands
Extensions can register additional tools with TypeBox schemas, so the model can call them like any other function. The model decides when to call a tool based on its description; Pi executes it and returns the result.
This is the main reason Pi plus Copilot feels different from Copilot Chat: the model can actually read your repo, run tests, grep logs, and edit files in place.
8. Commands and keybindings
Pi has a slash-command system (/command) and customizable keyboard shortcuts. Built-in commands include /model, /tree, /compact, /export, /share, /reload, and /quit. Extensions can add their own commands and shortcuts.
/reload is especially useful during extension development: it re-reads extensions, skills, prompts, themes, and context files without restarting the agent.
Copilot at work, custom providers at home
My setup looks like this:
- At work: Pi talks to GitHub Copilot via
/login. I use Pi for the terminal workflow, context control, and tool use that Copilot Chat doesn’t give me. - At home: I sometimes want to try models that aren’t in the Copilot catalog, so I wrote
pi-ollama-cloud-models. It fetcheshttps://ollama.com/v1/models, callshttps://ollama.com/api/showfor capabilities, and registers anollama-cloudprovider. Models are reached through the local Ollama daemon athttp://127.0.0.1:11434/v1using a:cloudsuffix so they don’t conflict with local Ollama models.
The extension also caches the list for an hour and adds /ollama-cloud-refresh for manual refresh. Install it with:
pi install npm:pi-ollama-cloud-models
The key insight is that Pi treats the model list as runtime data, not a static config file. By registering a provider through an extension, you can make new models appear automatically without touching ~/.pi/agent/models.json.
Why this matters for Copilot users
Pi’s design makes a few things unusually pleasant when your work provider is Copilot:
- You keep the subscription you already have. No extra API keys. Just
/loginand go. - You control the context. AGENTS.md and SYSTEM.md let you inject project conventions without polluting every prompt.
- The model can act on your repo. Read, edit, and bash tools mean Copilot goes from chat assistant to coding agent.
- Sessions are durable and branchable. The tree structure is closer to git history than a linear chat log.
- Extensions are first-class. If Copilot doesn’t expose a model or workflow you want, you can add custom providers, tools, or commands yourself.
If you’re already paying for Copilot and want more control over how you use it, Pi is worth a look.