An open-sourced, terminal-based AI coding agent.
Project description
Postal
An open-source AI coding agent that lives in your terminal.
Plans, edits, runs, and reviews code with any model on OpenRouter.
Postal connects to LLMs through OpenRouter, reads and edits your code with a built-in tool set, runs shell commands, delegates to specialized sub-agents, and streams everything through a full-screen TUI. Every mutating action goes through an approval policy you control, so it is as autonomous or as careful as you want it to be.
Quickstart
Two commands and you are talking to an agent in your own repo:
pip install postalcli
postal login # opens your browser to authorize with OpenRouter
postal # start the interactive TUI
More ways to run it:
postal "your prompt" # single-shot mode, great for scripting
postal --cwd /path # run against a different working directory
postal --continue # pick up the last session in this directory
postal --resume 3f2a1c # resume a specific session by id
postal sessions # list saved sessions
Configuration lives in ~/.config/postal/config.toml, with per-project overrides in .postal/config.toml, and AGENTS.md is picked up automatically.
Full CLI reference · configuration reference
Documentation
| Page | What it covers |
|---|---|
| CLI reference | Installing, logging in, every command and flag |
| Configuration | config.toml, per-project overrides, AGENTS.md, every option |
| Tools | The built-in tool set, sub-agents, MCP servers |
| Approvals | The six approval policies and the rules that override them |
| Sessions | Saving, resuming, checkpointing and rewinding conversations |
| Slash commands | Everything you can type after a / inside the TUI |
| Architecture | How the codebase is put together, class by class |
| Technologies | The libraries Postal is built on and what each one does |
Why Postal?
- Bring any model. OpenRouter as the backend means one login gives you Claude, GPT, Gemini, DeepSeek, open-weight models, and whatever ships next. No vendor lock-in.
- Safety is a first-class feature. Six approval policies, dangerous-command rejection, and confirmation for anything outside the working directory. You choose the risk level, not the agent. See Approvals.
- It survives long sessions. Context pruning reclaims tokens from stale tool output, and when the window fills up, Postal compacts history into a continuation brief and keeps going instead of erroring out.
- Nothing is lost when you close the terminal. Sessions are checkpointed after every turn, so
postal --continueputs you back exactly where you were, and/rewindwalks the conversation back to any earlier checkpoint. See Sessions. - Hackable by design. A readable, object-oriented Python codebase built on Rich, Click, and Pydantic. Every tool is one class behind a shared abstract base, so adding a tool or a sub-agent is a small, well-marked change. See Architecture.
- Component-based UI design. Postal uses a modular, component-based UI system with separate components for every visual element (spinners, gutters, markdown rendering, tool displays, confirmations, etc.), making it easy to maintain, customize, and extend the interface.
What it can do
| Files | read, write, edit, apply_patch, grep, glob, and list_directories for working with a codebase. apply_patch batches creates, updates, deletes and renames across several files into one all-or-nothing call. |
| Shell | The shell tool executes commands in the working directory. |
| Planning | A plan tool tracks steps (a todo list) across the agent loop. |
| Network and memory | Web search via DuckDuckGo, URL fetching, and key-value storage that survives across sessions. |
| Sub-agents | Specialized agents the main agent can delegate to: codebase_investigator, code_reviewer, software_architect, test_writer, debugger. |
| MCP | Connects to external MCP servers for additional tools and data sources. |
| Interactive TUI | Full-screen terminal interface built on Rich, with streaming responses, live tool call output, visible model reasoning, and token usage tracking. |
| Single-shot mode | Pass a prompt as an argument for non-interactive runs, suitable for scripting. |
Technologies
Frontend
| Rich | The whole TUI: full-screen live rendering, streaming responses, tool call panels, diffs, markdown, and the color theme. |
| prompt-toolkit | The input line: key bindings, multiline editing, history, and slash command completion. |
| Pygments | Syntax highlighting for code blocks and file previews rendered through Rich. |
Backend
| Python 3.11+ | The agent loop is asyncio-based, so streaming, tool calls, and MCP connections run concurrently. |
| OpenAI SDK | The API client, pointed at OpenRouter's OpenAI-compatible endpoint for streaming and tool calling. |
| OpenRouter | The model gateway. One login, any model, no per-vendor SDKs. |
| Pydantic | Config schemas, tool argument validation, and the JSON Schema sent to the model for each tool definition. |
| FastMCP / MCP | Connecting to external MCP servers and exposing their tools to the agent. |
| tiktoken | Token counting that drives context pruning and compaction. |
| httpx | Async HTTP for URL fetching and the OAuth token exchange. |
| ddgs | DuckDuckGo-backed web search. |
| Click | The postal CLI: flags, single-shot mode, and the login / logout subcommands. |
| OAuth 2.0 + PKCE | Browser login runs on a stdlib http.server loopback redirect, with the key stored locally. |
| platformdirs + TOML | Cross-platform config and credential paths, read with tomllib. |
| Docker | A Dockerfile and Compose file in docker/ for running the agent sandboxed. |
Slash commands
| Command | What it does |
|---|---|
/help |
Show all commands |
/model <name> |
Switch models mid-session |
/approval <mode> |
Switch the approval policy mid-session |
/thinking [on|off|low|medium|high] |
Show, hide, or retune the model's reasoning |
/clear |
Start a fresh conversation (the old one stays saved) |
/config |
Show the active configuration |
/stats |
Session statistics: tokens, elapsed time, tool calls |
/tools |
List available tools |
/mcp |
Show MCP server status |
/sessions [all] |
List saved sessions, newest first |
/sessions rm <n|id> |
Delete a saved session |
/resume <n|id> |
Load a saved session into the current one |
/checkpoint [label] |
Save a checkpoint now, with an optional name |
/checkpoints |
List the checkpoints in this session |
/rewind <n|id> |
Roll the conversation back to a checkpoint |
/exit, /quit |
Leave the agent |
Commands autocomplete as you type: hitting / lists every command, the list narrows as you keep typing, and ↑/↓ select while Enter runs the highlighted one (Tab fills it in if you want to add arguments first).
Sessions
Postal writes the conversation to disk after every turn, so closing the terminal does not kill your conversation. All conversations are resume-able and can be accessed by running a command shown below.
postal --continue # resume the most recent session in this directory
postal --resume 3f2a1c # resume a specific session (a prefix of the id is enough)
postal sessions # what is saved for this directory
postal sessions --all # every directory
postal sessions rm 3f2a1c
Inside the TUI, /sessions lists what is saved and /resume loads one into the running agent, transcript and token totals included. The system prompt is not restored: it is rebuilt from the current config and tool set, so a resumed session picks up any model, approval, or AGENTS.md changes you have made since.
Checkpoints
Each save is a checkpoint, a full snapshot of the conversation at that point. Turns are checkpointed automatically, and /checkpoint <label> marks one by hand before you try something risky:
❯ /checkpoint before the refactor
Saved before the refactor · 24 messages · session 3f2a1c8b
❯ /checkpoints
1 a41f9c02 turn 3 18 msgs 22m ago
2 7d2b1e55 turn 4 24 msgs 4m ago
3 e0c34a91 before the refactor 24 msgs just now
❯ /rewind 1
Rewound to turn 3 · 18 messages · turn 3
Rewinding replaces the conversation the model sees, which makes it the way out of a turn that went sideways: roll back to before the detour and take another run at it. It only rewinds the conversation, not your files — anything already written to disk stays written.
Sessions live in ~/.config/postal/sessions/<id>/, one directory per session, with the transcripts in a JSONL file next to a small meta.json. Old checkpoints are trimmed once a session passes max_checkpoints (autosaves go first, named ones are kept), and the oldest sessions are dropped past max_sessions. Set enabled = false under [session] to keep conversations off disk entirely.
Approvals
Before Postal runs anything that changes state, the approval policy decides whether it goes ahead, asks you, or is refused outright. Read-only tools (read, grep, glob, list_directories, plan) never prompt, so a policy only affects writes, shell commands, network calls, memory writes, MCP tools, and sub-agent runs.
| Value | Badge | Behaviour |
|---|---|---|
on_request (default) |
ask |
Confirm every mutating tool call. Commands matched as known-safe (ls, git status, grep, …) run without asking. |
auto_edit |
auto-edit |
File edits and writes inside the working directory go through unprompted; shell commands still need confirmation unless known-safe. |
auto |
auto |
Everything runs except dangerous commands, which are rejected. |
on_fail |
on fail |
Currently identical to auto. Reserved for prompting after a failed tool call, which is not implemented yet. |
never |
read-only |
Rejects anything that isn't a known-safe command. Nothing gets written, and you are never prompted. |
yolo |
yolo |
Approves everything, including commands matched as dangerous. Only use this in a sandbox or container. |
Two rules apply on top of the policy, and no policy except yolo overrides them:
- Dangerous commands are rejected.
rm -rf /,dd if=,mkfs,shutdown,curl … | bash, fork bombs, and similar patterns are refused before the shell ever sees them (the full list isDANGEROUS_PATTERNSinsafety/approval.py). - Anything touching a path outside the working directory is confirmed, however permissive the policy is (
neverrejects it instead).
The active policy is printed at startup and shown in the prompt badge, color-coded by risk: normal for ask, auto-edit and read-only, amber for auto and on fail, red for yolo.
Details in Tools and Slash commands.
origin/main
Roadmap
Currently being worked on:
- Skill Integration - allows users to import skills and use with their favorite model.
- Git Integration - allows users to use git commands with postal.
- More assets - Logo, banner, etc
Have an idea? Open an issue, feature discussions are very welcome.
Contributing
Contributions of every size are appreciated, from typo fixes to new tools and sub-agents. Read CONTRIBUTING.md to get started, and check the open issues for something to pick up!
If Postal is useful to you, a star on the repo genuinely helps the project reach more developers. ⭐
License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file postalcli-0.0.29.tar.gz.
File metadata
- Download URL: postalcli-0.0.29.tar.gz
- Upload date:
- Size: 123.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f688c2b8af9a2a52437674b39579d1d17fa301ca5ac49e773cec03fa018e02b7
|
|
| MD5 |
91c844f6ad3953f9b26a542f21fcdbcd
|
|
| BLAKE2b-256 |
4f5a991c651a23a2bce6410735eb3cf05d46b6d27b50fff9b7c7d00079a3fdcb
|
Provenance
The following attestation bundles were made for postalcli-0.0.29.tar.gz:
Publisher:
python-publish.yml on andrefetch/postal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postalcli-0.0.29.tar.gz -
Subject digest:
f688c2b8af9a2a52437674b39579d1d17fa301ca5ac49e773cec03fa018e02b7 - Sigstore transparency entry: 2341279337
- Sigstore integration time:
-
Permalink:
andrefetch/postal@6f323e894e93fabdcd2b4169042e6c35e0d8c051 -
Branch / Tag:
refs/tags/v0.0.29 - Owner: https://github.com/andrefetch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@6f323e894e93fabdcd2b4169042e6c35e0d8c051 -
Trigger Event:
release
-
Statement type:
File details
Details for the file postalcli-0.0.29-py3-none-any.whl.
File metadata
- Download URL: postalcli-0.0.29-py3-none-any.whl
- Upload date:
- Size: 139.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b0e6c84e16bcb4d0d111b33fa36d695a74e4b6b94d74c0e8edf57da5ad35cd8
|
|
| MD5 |
1720616eacdfdbe65692d9ce2f0fc08e
|
|
| BLAKE2b-256 |
d45fe7feffa324e9c34558db3e6d93601b8b634109c2a7cb2df641f9e221e2fc
|
Provenance
The following attestation bundles were made for postalcli-0.0.29-py3-none-any.whl:
Publisher:
python-publish.yml on andrefetch/postal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postalcli-0.0.29-py3-none-any.whl -
Subject digest:
1b0e6c84e16bcb4d0d111b33fa36d695a74e4b6b94d74c0e8edf57da5ad35cd8 - Sigstore transparency entry: 2341279341
- Sigstore integration time:
-
Permalink:
andrefetch/postal@6f323e894e93fabdcd2b4169042e6c35e0d8c051 -
Branch / Tag:
refs/tags/v0.0.29 - Owner: https://github.com/andrefetch
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@6f323e894e93fabdcd2b4169042e6c35e0d8c051 -
Trigger Event:
release
-
Statement type: