Back to blog
Jul 18, 2026
9 min read

Understanding the Pi Coding Agent: Its Components and How They Fit Together

A breakdown of the Pi coding agent's architecture, with GitHub Copilot as the work provider and a custom Ollama Cloud provider extension for personal use.

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 / JSONpi -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:

  1. Run /login in Pi.
  2. Select GitHub Copilot.
  3. 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:

  1. Read your message.
  2. Combine it with the current context: system prompt, previous messages, loaded skills, and available tool definitions.
  3. Send the context to the active Copilot model.
  4. Stream back the response.
  5. If the model asks to run a tool, execute it and feed the result back in.
  6. 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.md with 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 files
  • write — write files
  • edit — edit files
  • bash — 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 fetches https://ollama.com/v1/models, calls https://ollama.com/api/show for capabilities, and registers an ollama-cloud provider. Models are reached through the local Ollama daemon at http://127.0.0.1:11434/v1 using a :cloud suffix 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 /login and 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.

Further reading

Related posts