moonpi
v0.4.5
Published
Opinionated set of extensions for pi
Downloads
499
Maintainers
Readme
moonpi is set of extensions for pi that are actually useful for a coding agent. No fluff. No over-engineered architecture. Just practical guardrails, structured workflows, and pragmatism.
Opinions
moonpi is built around a few strong opinions.
If these resonate with you, you might find this set of extensions useful:
Subagents are a waste of tokens. Subagents are useless and just a conspiracy to make us use more tokens. Tin foil hat firmly on.
Plan mode is actually useful - but only if history is retained. Plan mode becomes pointless when the model outputs a "plan" and then loses all the context that produced it. Planning only works when the planning conversation remains part of the execution context.
Context is king. Why make the model guess which files matter for a task, wasting tokens and time wandering through the project, when you can inject the relevant files directly into the context? Context matters more than models or harnesses, control the context and you control the result.
Rejecting reads and writes outside the working directory is good steering. Yes, we all know the model can write and execute code, and yes, sandboxing is hard. But telling the model "hey, you cannot read that" when it reaches outside the project helps steer behavior. May our benevolent AI overlords accept this humble suggestion.
Read before write is mandatory. If a model tries to overwrite a file without reading it first, that is an error. It should not be allowed. Period.
Prompt cache affinity matters.
Switching system prompts or tool definitions between phases destroys the provider's prompt cache, forcing a full re-read of the system prompt on every turn. The phase transition should be invisible to the cache: same prompt, same tool schemas, only runtime behavior changes.Loops are stupidly simple.
A specification file, a TODO list, and auto-compaction after every phase are more than enough for most use cases.
Installation
First, install the base pi coding agent globally:
npm install -g @mariozechner/pi-coding-agentThen, install the moonpi extension set directly via pi:
pi install npm:moonpiScreenshots
Features
moonpi adds a practical workflow layer on top of pi, focused on:
- explicit work modes
- persistent planning context with prompt cache retention across phase transitions
- TODO-driven execution
- safer file access behavior
- pickable project documentation context and
/contextinspection - sprint-based long-running workflows
- phase-by-phase execution loops
Modes
moonpi provides four modes (Auto Mode has two phases):
| Mode | Available Tools | Description |
| --- | --- | --- |
| Plan Mode | read, grep, find, ls, todo, question | Used to reason about the task before making changes. The model must produce a TODO list. No file modifications or shell commands are allowed. |
| Act Mode | read, grep, find, ls, bash, edit, write, todo, question (+ end_phase in sprint loop) | Used to execute work. The model can read, write, edit, and run commands. TODO and QnA can be used when helpful, but are not mandatory. |
| Auto Mode (Plan phase) | read, grep, find, ls, todo, question, end_conversation | Default mode. The model first plans, then acts while retaining the full planning conversation history. During the planning phase, an end_conversation tool is available for question-only requests that do not need a TODO list. |
| Auto Mode (Act phase) | read, grep, find, ls, bash, edit, write, todo, question (+ end_phase in sprint loop) | After planning, the model executes the TODO list with full planning context intact. |
| Fast Mode | read, grep, find, ls, bash, edit, write (+ end_phase in sprint loop) | Direct execution mode. No planning requirement, no TODO list, no QnA. Useful for quick edits and simple tasks. |
Modes can be cycled using Tab
Each mode has a different textbox color in the UI, making the current workflow state immediately visible.
Auto Mode
Auto Mode is the default moonpi workflow.
It works in two phases:
Plan phase
- editing tools are disabled at runtime (blocked by guards, not removed from the prompt)
- the TODO list tool is enabled
- the model plans the work
- the model either:
- produces a TODO list, then continues to Act phase
- or calls
end_conversationwhen the user is only asking a question
Act phase
- editing tools become available (guards no longer block them)
- the conversation history from the Plan phase is retained
- the model executes the TODO list with full planning context intact
This avoids the "plan then forget everything" problem.
Prompt Cache Retention
Auto Mode is specifically designed to preserve the LLM provider's prompt cache across the Plan→Act transition. This means:
- Same system prompt. Both phases use the identical system prompt text. The phase instructions are embedded in a single
AUTO_MODE_PROMPTthat covers both Plan and Act behavior — the prompt never changes between phases. - Same tool schemas. All moonpi tools (
read,grep,find,ls,bash,edit,write,todo,question,end_conversation,end_phase) are always present in the tool definitions sent to the provider. Tools are never added or removed between phases. - Runtime enforcement only. In Plan phase, editing tools (
bash,write,edit) are blocked by moonpi's runtime guards — thetool_callevent handler rejects them with an error message. The tool definitions remain in the prompt. When the phase switches to Act, the guards simply stop blocking those tools. - Stable tool schemas for cache. Even in Fast mode where
todois disabled, its JSON schema is still advertised to the provider. This keeps the tool definition block identical across all modes and phases, maximizing cache hits.
Why this matters: providers cache the system prompt and tool definitions across turns. When the prompt or tool list changes, the cache is invalidated and the entire prefix must be re-processed — costing tokens, money, and latency. By keeping the prompt and tool schemas stable, Auto Mode ensures the cache is retained throughout the entire conversation, from Plan through Act and beyond.
Project Context Picker
moonpi injects only the files selected with /pick into the model context.
/pick
Opens a file-tree picker for the current working directory:
README.md,SPECS.md, andSPRINT.mdcontext files are selected by default- use
↑/↓to move - use
←/→to close and open folders - use
Spaceto select or deselect files and folders- selecting a folder recursively selects all descendant files that match the pickable extensions filter
- selecting a folder with all descendants already selected will deselect them all
- use
Dto deselect everything - use
Enterto confirm and close the picker
The picker only shows files with pickable extensions (code, text, config, and documentation files). Binary files, images, lock files, and other non-text files are hidden. The extension filter is configurable via pickableExtensions (see Configuration).
/context
Shows which files are currently selected for prompt injection and whether they were auto-discovered at startup or manually selected via /pick.
Example output:
3 file(s) auto-discovered (use /pick to change):
README.md
SPECS.md
SPRINT.md5 file(s) manually selected with /pick:
README.md
src/config.ts
src/context-files.ts
src/types.ts
src/modes.tsAt startup, a notification shows which files are currently selected for injection.
/context:clear
Deselects all currently active context files (both /pick-selected and auto-discovered). After clearing, no files are injected into the prompt until you run /pick again.
Example output:
Cleared context file selection (3 file(s) deselected). Use /pick to select files.Custom Providers
moonpi includes the support from some custom providers, and provides slash commands to manage custom providers in ~/.pi/agent/models.json.
Synthetic Provider
Moonpi registers Synthetic as the synthetic provider using the OpenAI-compatible endpoint.
Configure credentials with either:
export SYNTHETIC_API_KEY=...or run:
/loginUse /model to select a synthetic model. Use /synthetic:quotas to show your weekly token and rolling 5h usage quotas:

Web Search
When authenticated with Synthetic, moonpi makes a web_search tool available to the agent. This tool uses the Synthetic search API to perform zero-data-retention web searches and return results with title, URL, published date, and text excerpt.
The tool is only visible to the LLM when logged in with Synthetic — it is not registered at all when no API key is configured, so the model never sees it or knows it exists.
To disable the search tool even when logged in, set synthetic.search.enabled to false in your config:
{
"synthetic": {
"search": {
"enabled": false
}
}
}Managing Custom Providers
moonpi provides five slash commands to manage custom providers in ~/.pi/agent/models.json:
/custom-provider:add-provider
Interactive wizard that adds a new custom provider. Prompts for:
- Provider name — a unique identifier (e.g.
my-vllm) - API type — select from all supported APIs (
openai-completions,anthropic-messages,google-generative-ai, etc.) - Base URL — the API endpoint (sensible defaults per API type)
- API key — your API key or an environment variable name
Example models.json result:
{
"providers": {
"my-vllm": {
"baseUrl": "http://127.0.0.1:8000/v1",
"api": "openai-completions",
"apiKey": "none",
"models": []
}
}
}/custom-provider:add-model
Adds a model to an existing custom provider. Prompts for:
- Provider — select from existing custom providers
- Model ID — the model identifier (e.g.
Qwen/Qwen3-27B) - Display name — optional human-readable name
- Advanced options — optional configuration for reasoning, context window, max tokens, image input, and API type override
/custom-provider:scan-models
Auto-detects models from an OpenAI-compatible provider endpoint. Works with providers using openai-completions or openai-responses API.
- Select provider — choose from OpenAI-compatible custom providers
- Scan — fetches
/v1/modelsfrom the provider's base URL - Select models — checkbox UI shows all discovered models; already-added models are greyed out
Spaceto toggle individual modelsa/Ato select/deselect allEnterto confirm (adds all new models if none selected)
- Auto-fills
contextWindowfrommax_model_lenwhen available
/custom-provider:remove-provider
Removes a custom provider and all its models from models.json. Asks for confirmation before deleting.
/custom-provider:remove-model
Removes a single model from a custom provider. Prompts for the provider, then the model to remove, with confirmation.
After any change, run /reload to refresh pi's model registry and make new models available in /model.
Moonpi loop
moonpi includes sprint-oriented loop for larger projects.
/sprint:init
Creates a new sprint for a larger project.
This command asks one question: the sprint objective.
It then delegates SPRINT.md and TASKS.md creation to the agent, which writes:
./sprints/<sprint_number>/SPRINT.md
./sprints/<sprint_number>/TASKS.mdThe sprint is divided into phases.
Each phase includes tasks and verification steps that define when the phase is complete.
The goal is to turn a vague big project into a concrete, phased execution plan.
/sprint:loop
Runs the latest sprint phase-by-phase. Automatically picks the most recent sprint.
The loop works like this:
- complete one phase
- mark completed tasks in:
./sprints/<sprint_number>/TASKS.md- compact the conversation/context
- proceed to the next phase
- repeat until the sprint is complete
The model signals the end of a phase by calling a special end_phase tool.
This keeps long-running projects simple, resumable, and grounded in actual files.
Does it work?
Watch a drastically sped-up video of Qwen/Qwen3.6-27B working unattended for over an hour on this sprint prompt:
create WebOS a fully functional web-based operating system with apps, games and everythinghttps://github.com/user-attachments/assets/92670a55-a3c4-4c31-a4a2-0afc449f0137
And judge the result yourself here.
Configuration
Configure .pi/moonpi.json (project) or ~/.pi/agent/moonpi.json (global):
{
"defaultMode": "auto",
"preserveExternalTools": false,
"customEditor": true,
"synthetic": {
"search": {
"enabled": true
}
},
"contextFiles": {
"enabled": true,
"fileNames": ["README.md", "SPECS.md", "SPRINT.md"],
"maxTotalBytes": 120000,
"maxDepth": 4,
"maxScannedEntries": 10000,
"maxDefaultFiles": 25,
"pickableExtensions": [
".ts", ".js", ".py", ".rs", ".go", ".md", ".json", ".yaml",
".toml", ".sql", ".html", ".css", "Dockerfile", "Makefile"
],
"ignoreDirs": [
".git", ".svn", ".hg",
"node_modules", ".next", ".turbo",
".venv", "venv", "__pycache__",
"target", "dist", "build", "coverage",
".env", ".gradle", ".idea", ".vscode"
]
},
"guards": {
"cwdOnly": true,
"allowedPaths": ["~/.pi/agent"],
"readBeforeWrite": true
},
"keybindings": {
"cycleNext": "tab",
"cyclePrevious": ""
}
}General
| Field | Default | Description |
| --- | --- | --- |
| defaultMode | "auto" | Mode used at session start. One of "plan", "act", "auto", "fast" |
| preserveExternalTools | false | When true, tools registered by other extensions are kept alongside moonpi tools when applying mode tool restrictions |
| customEditor | true | When false, moonpi skips installing its mode-colored editor, preserving editor customizations from other extensions |
Synthetic
| Field | Default | Description |
| --- | --- | --- |
| synthetic.search.enabled | true | When false, the web_search tool is not registered even if logged in with Synthetic |
Keybindings
| Field | Default | Description |
| --- | --- | --- |
| cycleNext | "tab" | Keybinding to cycle to the next mode when the editor is empty |
| cyclePrevious | "" (disabled) | Keybinding to cycle to the previous mode when the editor is empty |
Context files
| Field | Default | Description |
| --- | --- | --- |
| enabled | true | Enable or disable context file injection entirely |
| fileNames | ["README.md", "SPECS.md", "SPRINT.md"] | Filenames auto-discovered at startup (before any /pick selection) |
| maxTotalBytes | 120000 | Maximum total bytes injected into the prompt. Files are truncated beyond this limit |
| maxDepth | 4 | Maximum directory depth for startup auto-discovery only. The /pick picker has no depth limit |
| maxScannedEntries | 10000 | Maximum filesystem entries inspected during any scan (startup or /pick). Prevents walking huge trees |
| maxDefaultFiles | 25 | Maximum files selected by auto-discovery. Does not affect /pick selections |
| pickableExtensions | (80+ entries, see defaults) | File extensions (.ts, .py) and exact filenames (Dockerfile, Makefile) shown in /pick. Non-matching files are hidden from the picker |
| ignoreDirs | (40+ entries, see defaults) | Directory names skipped during both auto-discovery and /pick. Covers VCS, language-specific caches, build output, IDE folders, and more |
Guards
| Field | Default | Description |
| --- | --- | --- |
| cwdOnly | true | Reject file access outside the current working directory |
| allowedPaths | [] | Directories permitted for read access even when cwdOnly is enabled. Supports ~ expansion and relative paths (resolved from cwd) |
| readBeforeWrite | true | Require the model to read a file before writing or editing it |
Recommended tuning for huge monorepos:
- lower
maxDepthto2or3if READMEs are mostly near the root - lower
maxScannedEntriesto cap worst-case startup and per-picker-session scanning latency - add generated/vendor folders to
ignoreDirs - narrow
pickableExtensionsto only the file types you care about - lower
maxDefaultFilesif too many READMEs are injected by default - set
enabled: falseand use manual@filereferences if you do not want automatic context injection
If a scan hits a limit, moonpi shows a warning in startup notifications or /pick.
The system prompt also instructs the model to keep selected project documents up to date, making README.md and SPECS.md living project documents instead of abandoned archaeology.
TODO List Tool
moonpi includes a TODO list tool that can be updated at any moment.
Whenever an item is modified, the current full state of the TODO list is returned to the model.
This keeps the agent grounded in the actual progress of the task instead of relying on vibes, memory, or whatever it hallucinated three messages ago.
Filesystem Guardrails
moonpi adds practical file access rules.
Stay inside the working directory
Reads, writes, and edits outside the current working directory are rejected.
This is not presented as magical security theater. It is a behavioral constraint that helps steer the model toward project-scoped work.
Allowed paths
When cwdOnly is enabled, you can grant the agent read access to specific directories outside the project via guards.allowedPaths. This is useful when the agent needs to read its own documentation, extension files, or shared configs that live outside the working directory.
{
"guards": {
"allowedPaths": [
"~/.pi/agent",
"/home/user/Projects"
]
}
}Paths support ~ expansion (resolved to home directory) and relative paths (resolved from cwd). Only read access is granted for allowed paths - writes and edits are still restricted to the working directory.
The setting works in both global (~/.pi/agent/moonpi.json) and project-level (.pi/moonpi.json) configs.
Read before write
moonpi requires the model to read a file before writing to it.
If the model tries to write to a file without reading it first, the write tool returns an error.
This prevents careless overwrites and forces the agent to inspect the current state of a file before modifying it.
Why moonpi?
moonpi is not trying to be a giant agent framework.
It does not try to invent a new architecture for every task.
It does not summon a committee of subagents to discuss whether a file should be edited.
It gives the coding agent a simple structure:
- plan when needed
- act when needed
- keep the plan in context
- inject the right project files up front
- track work explicitly
- stay inside the project
- read before writing
- compact after meaningful phases
- keep project docs alive
That is usually enough.
And when it is not enough, it is still better than burning tokens on fake organizational charts for imaginary agents.
Fun Fact: I used
moonpito buildmoonpi, making it a bootstrapped coding agent.
License
MIT
