🤏 Puny

A minimal natively compiled single-binary coding agent with a ~1 MB footprint. For professionals on limited hardware who want results, not bloat.

puny
Welcome to Puny 0.3.5 - Your tiny AI coding assistant
AI makes mistakes - read the fucking code

  Provider: OpenCode Zen (https://opencode.ai/zen)
  Model: deepseek-v4-flash-free
  Session: 550e8400-e29b-41d4-a716-446655440000

Available commands:
  /quit, /exit       Exit Puny
  /new, /reset       New session
  /stats             Show session statistics
  /config            Reconfigure URL and API key
  /plan [task]       Enter planning mode
  /build [task]      Switch to build mode
  /review            Review the current branch
  /orchestrate [task] Implement, review, and fix until merge worthy
  /model [id]        Switch to another model
  /provider            Open the provider picker
  /thinking [level]  Change reasoning effort
  /sessions          List saved sessions
  /resume [id]       Resume a saved session
  /prune             Remove old sessions
  /skills            List global and repository skills
  /file [path|url]   Load a prompt from a file or URL
  /help              Show this help message
  @path              Attach a file to the prompt
Tip: Type @ to search and attach files to your prompt.

>

Why fast and small matters

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.

Why the feature set is intentionally limited

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:

  • LM Studio — local inference on my own hardware
  • Unsloth — local inference through Unsloth Studio
  • OpenCode Zen — wide selection of reliable optimized models
  • OpenCode Go — cheaper hosted models when I need them
  • GitHub Copilot — comes with my GitHub subscription

Puny also supports Ollama, both a local Ollama server and Ollama Cloud.

Features

🧠

Multiple Providers

The five providers I use personally: LM Studio, Unsloth, OpenCode Zen, OpenCode Go, and GitHub Copilot; Ollama and Ollama Cloud are also supported

🔧

Tool Calling

Read, write, and list files, run shell commands, search code, load skills, and fetch web pages

📦

Skills System

Load reusable prompt-engineering skills via slash command, keyword triggers, or automatic model invocation

💬

Interactive Chat

Multi-turn conversations with session management and PRD export

🎯

Model Picker

Interactive model selection on startup

Installation

Build from Source

Dev

Requires Zig 0.16.0 or later.

git clone https://github.com/christianhelle/puny.git
cd puny
zig build

The compiled binary is written to zig-out/bin/puny.

To install a release build to ~/.local/bin (the same directory used by the install scripts):

zig build install-release

Other optimization modes are available:

zig build install-release-safe
zig build install-release-fast
zig build install-debug

Override the install directory with INSTALL_DIR or --prefix.

Docker

Container

Puny is published as a container image.

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

Usage

Sessions

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.

Attach files

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.

Repository instructions

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.

Skills

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:

nameCanonical identifier
descriptionShort summary shown in /skills
triggersComma-separated phrases that auto-load the skill
disable-model-invocationSet 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.

Quick Start with LM Studio

Start LM Studio, load a model with tool-calling support, then:

puny

OpenCode Zen

Set PUNY_API_KEY in the environment or use --api-key-file, then:

puny --provider opencode_zen

OpenCode Go

Set PUNY_API_KEY in the environment or use --api-key-file, then:

puny --provider opencode_go

GitHub Copilot

puny --provider copilot

One-shot automation

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

Prompt Files

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

Orchestrate: implement → review → fix loop

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.

Docker

Run with Docker

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.