A minimal natively compiled single-binary coding agent with a ~1 MB footprint. For professionals on limited hardware who want results, not bloat.
Startup time ~1ms means Puny is always ready when you are — no spinner, no waiting. The ~1 MB binary and minimal memory footprint mean it runs comfortably on a Raspberry Pi, a remote server over SSH, or a decade-old laptop. Every millisecond and megabyte is deliberate: there is no hidden runtime, no garbage collector, no Node.js dependency, no Electron shell.
Puny is designed around the way I work. It is highly opinionated.
Other coding agents include MCP, subagents, plugins, extensions, animations, and dozens of other features I never asked for. Puny does not. The feature set is limited to what I need to get results: read files, write files, run commands, search code, load skills, fetch web pages. If you want parallel work, run another instance.
Each feature earns its place. Nothing ships because it looks good on a comparison table. This is not a platform — it is a tool.
Puny does not show the model's reasoning or thinking output by default. I do not care about the model's internal monologue. I see it as a wall of text, a huge TL;DR you have to scroll past to get to the actual answer. If you want it, pass --show-thinking. If you think you want it later, pass --chat-log which saves the entire conversation, noise included, to puny_chat.log.
The five supported providers are the ones I use personally:
Puny also supports Ollama, both a local Ollama server and Ollama Cloud.
The five providers I use personally: LM Studio, Unsloth, OpenCode Zen, OpenCode Go, and GitHub Copilot; Ollama and Ollama Cloud are also supported
Read, write, and list files, run shell commands, search code, load skills, and fetch web pages
Load reusable prompt-engineering skills via slash command, keyword triggers, or automatic model invocation
Multi-turn conversations with session management and PRD export
Interactive model selection on startup
One-command installation that automatically detects your platform, downloads the latest release, and installs it to your chosen directory.
curl -fsSL https://christianhelle.com/puny/install | bash
irm https://christianhelle.com/puny/install.ps1 | iex
curl -fsSL https://christianhelle.com/puny/install | bash -s -- --dir "$HOME/.local/bin"
$install = irm https://christianhelle.com/puny/install.ps1
& ([scriptblock]::Create($install)) -InstallDir "$env:USERPROFILE\bin"
Each session is identified by a UUID. A new session is created on every start, /new, or /reset. Use /sessions to list saved sessions and /prune to clean up old ones. In /plan mode, once you confirm the plan is ready, the model saves the final PRD as both plan.md and plan.html to the session folder via the save_prd tool.
Type @ at the start of a prompt or after whitespace to search files under the current directory. Selecting a file inserts an @path mention, and Puny appends that file's contents before sending the prompt.
Explain @src/main.zig and compare it with @src/config/config.zig
Each attachment is limited to 64 KiB. Unreadable files, larger files, and paths containing whitespace are not attached.
Inside a Git repository, Puny loads the first instruction file found at the repository root: AGENTS.md, then .github/copilot-instructions.md, then CLAUDE.md. Use that file for project-specific commands, conventions, and constraints that should apply to every request.
Puny supports reusable prompt-engineering skills stored as markdown files. Skills are scanned from ~/.agents/skills/ (global) and <repo>/.agents/skills/ (per-project).
Each skill directory contains a SKILL.md file with YAML frontmatter:
name | Canonical identifier |
description | Short summary shown in /skills |
triggers | Comma-separated phrases that auto-load the skill |
disable-model-invocation | Set to true to restrict to manual loading |
Skills load three ways: /<name> slash command, mentioning a trigger phrase in your message, or the model calling load_skill automatically when it finds a relevant skill.
Start LM Studio, load a model with tool-calling support, then:
puny
Set PUNY_API_KEY in the environment or use --api-key-file, then:
puny --provider opencode_zen
Set PUNY_API_KEY in the environment or use --api-key-file, then:
puny --provider opencode_go
puny --provider copilot
Run puny once to complete setup before using it in a non-interactive job. One-shot mode requires --prompt or --prompt-file; pass --model (or set PUNY_MODEL) so the run never needs the model picker.
puny --model model-id --prompt "List all source files" --oneshot
Load the first prompt from a local file or an http:///https:// URL via --prompt-file at startup or /file in a session:
puny --model model-id --prompt-file spec.md --oneshot
Run the autonomous loop in a single process: implement the task, commit anything left in the worktree, review the branch against origin/main, and feed review-results.md back for fixes until MERGE WORTHY: YES (exit 0) or the iteration budget runs out. Every phase starts from a fresh conversation and hands off through artifacts, so the loop stays inside a small model's context window. Run it on a feature branch with origin/main available. Use /orchestrate --plan "task" inside a chat session to run the interactive planning interview first — the loop starts by itself once the model saves the PRD, reading it straight from the session folder.
puny --orchestrate --prompt "Add CSV export"
puny --orchestrate --prompt-file spec.md --max-iterations 3
/orchestrate Add CSV export
/orchestrate --plan Add CSV export
/orchestrate
Exit codes mirror puny --review: 0 merge worthy, 1 still rejected after the iteration budget, 2 operational failure. The model is told to commit as it works in small one-line commits with no prefixes and no trailers; whatever it leaves behind is swept into one backstop commit before the review runs, so a review never passes judgement on a partial diff. Override the instructions with the prompts.orchestrate entry in config.json.
Puny is published as a container image to both Docker Hub and GitHub Container Registry.
docker pull christianhelle/puny:latest
docker volume create puny-home
docker run --rm -it --mount "type=bind,source=${PWD},target=/workspace" --mount "type=volume,source=puny-home,target=/app" --workdir /workspace christianhelle/puny:latest
The project is mounted at /workspace, while puny-home persists configuration, sessions, and encrypted credentials under /app.
For detailed Docker documentation, including LM Studio integration, one-shot prompts, and building locally, see the Docker guide.