# ๐ฅง Setting Up Pi Coding Agent: Self-Hosted AI Coding Assistant on a VM
**Date:** July 16, 2026
**Tags:** AI, Pi, Coding Agent, Self-Hosted, 9Router, MCP, AI Agents
---
## ๐ฏ Overview
[Pi](https://github.com/earendil-works/pi-coding-agent) (the **Pi Coding Agent**) is a fast, terminal-native **AI coding assistant** with read, bash, edit, and write tools. It's the *hands-on coder* of the stack: where [Hermes](/blog/hermes-setup/) orchestrates and [OpenClaw](/blog/openclaw-setup/) gates chat traffic, Pi sits in your shell and actually edits files, runs commands, and drives a coding session.
Pi is model-agnostic โ it talks to any OpenAI-compatible endpoint. On this VM we point it at **9Router** (our LAN free-model router) so every coding call is free and auto-rotated. It also speaks **MCP**, so we wire it into **GBrain** (shared memory) the same way the other agents do.
### Why Pi?
| Feature | Benefit |
|---------|---------|
| **โก Fast CLI** | Runs inline in your terminal, no separate UI |
| **๐ ๏ธ Real Tools** | read / bash / edit / write โ actual file + shell access |
| **๐ MCP Ready** | Connect GBrain, databases, or any MCP server |
| **๐ 9Router Native** | Free, auto-rotating models via one endpoint |
| **๐พ Sessions** | Resume, fork, and name coding sessions |
| **๐ฆ Extensible** | Extension packages (e.g. `pi-9router-ext`) |
---
## ๐๏ธ Architecture Overview
```
Your terminal (VM)
โ
โผ
pi (coding agent CLI)
โ --provider 9router
โผ
9Router http://192.168.51.115:20128/v1
โ (auto-rotates free models)
โผ
OpenAI-compatible free models
โ
pi โโ MCP โโโบ GBrain (shared memory / Postgres)
```
Unlike the gateway agents, Pi is **session-scoped**: you launch it per task. We still show an optional systemd wrapper for a persistent background coding session.
---
## ๐ Prerequisites
- Linux VM with a `node`/`npm` toolchain (Pi ships as a Node package).
- A model backend โ we use **9Router** (see the [9Router guide](/blog/9router-setup/)).
- (Optional) GBrain MCP server for shared memory (see below).
- A non-root user (`ryan` in examples).
> โ ๏ธ **Security note:** Every secret below is a ``. Never commit real keys.
---
## ๐ฆ Step 1 โ Install Pi
Pi installs as a global Node package:
```bash
# Install Pi globally
npm install -g @earendil-works/pi-coding-agent
# Symlink lands here on this VM:
# /home/ryan/.hermes/node/bin/pi ->
# ../lib/node_modules/@earendil-works/pi-coding-agent/dist/cli.js
# Verify
which pi
pi --help
```
If you already have it, update:
```bash
pi update self
# or update pi + extensions
pi update --all
```
---
## โ๏ธ Step 2 โ Configure Providers (models.json)
Pi reads provider + model definitions from `~/.pi/agent/models.json`. Below is a **sanitized** example pointing at 9Router with a fallback chain.
```json
{
"providers": {
"9router": {
"name": "9Router",
"baseUrl": "http://192.168.51.115:20128/v1",
"apiKey": "",
"api": "openai-completions",
"models": [
{
"id": "openrouter/nvidia/nemotron-3-ultra-550b-a55b:free",
"name": "Nemotron 3 Ultra 550B (via 9Router) โ
Primary",
"contextWindow": 1048576,
"maxTokens": 65536,
"reasoning": true,
"input": ["text"]
},
{
"id": "openrouter/poolside/laguna-m.1:free",
"name": "Laguna M.1 (via 9Router) โ
Fallback 1",
"contextWindow": 204800,
"maxTokens": 131072,
"reasoning": true,
"input": ["text"]
},
{
"id": "cerebras/zai-glm-4.7",
"name": "Z.ai GLM-4.7 (via 9Router) โ
Fallback 2",
"contextWindow": 262144,
"maxTokens": 32768,
"reasoning": true,
"input": ["text"]
}
]
}
}
}
```
> ๐ก The `apiKey` can be omitted if you export `NINEROUTER_API_KEY` in your shell / service env โ Pi reads env vars by default via `--api-key` fallback.
---
## ๐ง Step 3 โ Default Settings (settings.json)
`~/.pi/agent/settings.json` sets the default provider/model and any extensions:
```json
{
"lastChangelogVersion": "0.80.3",
"packages": [
"npm:pi-9router-ext"
],
"defaultProvider": "9router",
"defaultModel": "Free"
}
```
Install the 9Router extension so Pi knows how to talk to your router:
```bash
pi install npm:pi-9router-ext
# List installed extensions
pi list
# Open the resource TUI to toggle what's enabled
pi config
```
---
## ๐งฉ Step 4 โ Wire GBrain via MCP (mcp.json)
Pi supports MCP servers. We connect **GBrain** (the shared memory brain all agents use) via `~/.pi/mcp.json`:
```json
{
"mcpServers": {
"gbrain": {
"command": "/home/ryan/.bun/bin/gbrain",
"args": ["serve"],
"env": {
"GBRAIN_DATABASE_URL": "",
"GBRAIN_DIRECT_DATABASE_URL": ""
}
}
}
}
```
> ๐ The GBrain URLs are Postgres connection strings โ store them as `` and inject via env, never commit. Pi will then have `gbrain` tools (search, save pages, query) available inside a coding session.
---
## ๐ Step 5 โ Run Pi (Interactive & Non-Interactive)
**Interactive session** (edits files in the current repo):
```bash
# From inside a project directory
pi
# Or pin a provider/model explicitly
pi --provider 9router --model Free
```
**One-shot (non-interactive)** โ great for scripts/CI:
```bash
pi --provider 9router --model Free --print --no-session \
"Add a README with build instructions to this repo"
```
**Resume / fork sessions:**
```bash
# Continue the previous session
pi --continue
# Pick a session to resume
pi --resume
# Use a specific session by ID
pi --session
# Fork a session into a new branch of work
pi --fork
# Name a session for later lookup
pi --name "refactor-auth-module"
```
**Use a system prompt / context file:**
```bash
pi --system-prompt "You are a security-focused reviewer." \
--append-system-prompt ./CONTRIBUTING.md \
"Review this PR diff for injection risks"
```
---
## ๐ ๏ธ Step 6 โ Optional: Persistent systemd Session
Pi is usually launched per-task, but if you want a long-running background coding session, wrap it as a user service:
```ini
# ~/.config/systemd/user/pi-coding-agent.service
[Unit]
Description=Pi Coding Agent (persistent background session)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/ryan/PersonalWebsite
ExecStart=/home/ryan/.hermes/node/bin/pi \
--provider 9router --model Free --session-id pi-background
Environment=NINEROUTER_API_KEY=
Environment=GBRAIN_DATABASE_URL=
Environment=GBRAIN_DIRECT_DATABASE_URL=
Restart=on-failure
RestartSec=10
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now pi-coding-agent.service
systemctl --user is-active pi-coding-agent.service
# -> active
```
> ๐ Most users skip this and just run `pi` in a terminal when needed. The wrapper is only for an always-on coding bot.
---
## ๐งช Step 7 โ Smoke Test
```bash
# 1. Pi binary resolves
which pi
# 2. 9Router is reachable with the key (placeholder)
curl -s --max-time 8 -H "Authorization: Bearer " \
http://192.168.51.115:20128/v1/models | head -c 200
# 3. Pi answers a trivial prompt non-interactively
pi --provider 9router --model Free --print --no-session "Say: pi online"
# 4. MCP server (gbrain) is reachable
/home/ryan/.bun/bin/gbrain get_health
```
Expected: a JSON `data` array from `/v1/models`, Pi prints `pi online`, and GBrain returns healthy.
---
## ๐ฉบ Troubleshooting
| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| `pi: command not found` | Not on PATH | Use full path `/home/ryan/.hermes/node/bin/pi` or `npm link` |
| Model calls 401 | Wrong/expired 9Router key | Re-export ``, re-test `/v1/models` |
| `provider not found: 9router` | models.json missing provider | Check `~/.pi/agent/models.json` |
| MCP tools missing | gbrain server down / bad env | Run `gbrain get_health`, fix `` |
| Extension not loading | Not enabled in `pi config` | `pi list`, then `pi config` and toggle it on |
---
## ๐ Security Checklist
- [ ] `NINEROUTER_API_KEY` is a ``, injected via env, never committed.
- [ ] GBrain Postgres URLs are placeholders; restrict DB user to least privilege.
- [ ] Pi runs as non-root (`ryan`).
- [ ] Background systemd session uses `Restart=on-failure`, not `always`, for a coding bot.
- [ ] MCP env vars live in the unit file / env, not in world-readable config.
---
## โ
Summary
You now have the **Pi Coding Agent** wired into your AI stack:
- Installed as a global Node CLI (`~/.hermes/node/bin/pi`).
- Routed through **9Router** for free, auto-rotating models (Nemotron 3 Ultra primary, Laguna M.1 + GLM-4.7 fallbacks).
- Default provider/model pinned in `settings.json` (`9router` / `Free`).
- Connected to **GBrain** via MCP for shared memory across all agents.
- Usable interactively (`pi`), one-shot (`--print --no-session`), or as an optional persistent `systemd` session.
Pi completes the trio alongside [Hermes](/blog/hermes-setup/) (orchestration) and [OpenClaw](/blog/openclaw-setup/) (gateway) โ all three leaning on [9Router](/blog/9router-setup/) for inference. ๐
---
*No API keys, tokens, or secrets are included in this guide โ every value is a `` you supply locally. Built with Astro, GitHub, and Cloudflare Pages.*