Tiny, open, embeddable, native coding agent. The command is pf.
⚠ Status: Experimental. Use at your own risk.
Paneflow Agent is a coding agent CLI written in Zig: a small native binary that is open source (Apache-2.0), model-agnostic, and embeddable as a harness in larger systems. Its interface stays closer to a Unix shell than an IDE in the terminal. It derives from fx by Vercel; see NOTICE.
- Any model: Vercel AI Gateway, ChatGPT or Grok subscriptions, or your own OpenAI-compatible endpoint such as Ollama or OpenRouter
- Any interface: interactive shell, one-shot
pf askfor scripts, or embedded through ACP - Shell-like output: inline rendering that preserves your terminal scrollback
- Extensible: skills, MCP servers, and subagents
Paneflow Agent does not publish releases yet. Build it from source, then put zig-out/bin/pf on your PATH. No install script or package manager (Homebrew, winget, Scoop) is available.
Sign in with one of:
pf login: Vercel AI Gatewaypf login codex: ChatGPT subscription (OpenAI Codex OAuth)pf login grok: Grok subscription (xAI OAuth)pf setup: AI Gateway API key
Run pf doctor to check your sign-in, configuration, and workspace before the first request.
pf loads Grok models from your subscription's live catalog, so new supported models appear without a static model list. Public xAI metadata enriches image support but does not filter subscription models.
Then start the interactive shell from a project:
cd your_project
pfOr make a one-shot request:
pf ask "explain the changes in this repository"Inside the shell, run /help to browse interactive commands.
pf saves each conversation under ~/.pf/sessions/. Run pf -c in the same project to continue where you left off.
In tmux, use your usual prefix bindings to switch sessions or enter copy mode. pf preserves those tmux views while resizing, including when the switcher zooms a split pane.
The interactive shell reports its state with the Program Status Protocol (OSC 7501), so a terminal that supports it can show when pf is working, waiting for your approval or answer, done, or stopped by an error. Approval and question reports include the request or question text. When pf exits or is suspended, it clears the terminal's status records. Terminals without support ignore the reports.
Paste an image, attach one with pf ask --image PATH, or ask pf to read_file a PNG, JPEG, GIF, or WebP. File-backed attachments retain the original image. Before each model request, it checks the complete image count and sends only images that fit: at most 8000 pixels per side with 20 or fewer images, or 2000 pixels per side with more than 20. The encoded per-image limit is 5 MiB.
When a file-backed image cannot be sent, the model receives its source path and the reason. It can use an image tool already on your system, such as sips on macOS, ffmpeg on Linux, or PowerShell on Windows, to save a smaller new file and read that copy. pf does not automatically install image tools or overwrite the original. If no usable file or tool is available, the model should ask you for a smaller copy or permission before installing software.
Commands run in your login shell, zsh or bash, with your startup files applied, so your aliases, functions, and PATH work as they do in your terminal. pf runs the startup files once and restores their result for each command, so a slow .zshrc does not slow down every call.
- pf reloads automatically when your zsh or bash startup files change, such as
.zshrc,.zprofile,.bash_profile, or.bashrc. After changing a file they source, run/shell reload. - Each command starts fresh:
cd,export, and alias changes do not carry over to the next one. - Tools that switch the environment by directory, such as direnv or mise hooks, apply the environment of the directory where pf captured the startup files, usually your workspace.
- If the startup files cannot be captured, pf says so once and runs them for every command instead.
- Terminals opened with
tty: truerun your full login shell and end when pf exits. - Every command pf runs, including terminals and PowerShell on Windows, receives
AI_AGENT=pfin its environment, so development tools can detect pf.
Visit paneflow.dev/agent/docs for the full manual: sessions, models, custom model connections, permissions, configuration, skills, MCP, subagents, embedding, and the complete CLI and slash command references. Agents can read any page as Markdown by appending .md to its URL, or fetch llms-full.txt for everything in one file.
Add named connections for any OpenAI Chat Completions endpoint, including local servers such as Ollama and gateways such as OpenRouter, in ~/.pf/settings.json, then select one for the profile or a single invocation:
pf provider local
PF_PROVIDER=openrouter PF_MODEL=openai/gpt-4.1 pf ask "review this change"See Custom model connections for connection JSON, model metadata, and behavior details.
Ultrafast mode is off by default. It requests OpenAI's higher-cost Gateway service tier with openai.serviceTier: "ultrafast" for models whose Gateway metadata advertises Ultra eligibility. ultrafast_requested in pf status --json and /status reports the request, not a guarantee that a provider served the tier.
Set a profile default in ~/.pf/settings.json:
Use it explicitly in an interactive session, a one-shot request, or ACP:
pf --ultrafast
pf ask --ultrafast "review this change"
pf acp --ultrafastUse /ultrafast on, /ultrafast off, or /ultrafast status in the shell. The Settings menu includes an Ultra mode row. PF_ULTRAFAST=1 and --ultrafast are process-local opt-ins and are not persisted. PF_ULTRAFAST=0, --no-ultrafast, and /ultrafast off explicitly disable it. A resumed session keeps its saved request unless a higher-precedence explicit disable applies.
Ultra mode is available only through the Vercel AI Gateway's OpenAI service tier. Gateway metadata currently marks Astra eligible. pf does not select Ultra automatically, and switching models clears an existing Ultra request. Subagents inherit the parent turn's request; an explicit parent disable and capability checks override an existing child preference. Background side calls, including titles, reviews, and compaction, do not use Ultra mode.
ACP clients get the same per-session choice as the shell. The config options from session/new, session/load, session/resume, and session/set_config_option include fast only for models with a Fast lane and ultrafast only for Ultra-eligible models, each with the values false and true. Turning one on turns the other off, enabling a lane the model lacks returns an error, and switching models turns off a lane the new model lacks. The choice is saved with the session. An unknown config option id returns an error. To show these choices before a session exists, pf models --json describes each Gateway model by the same rules: its display name, the efforts it accepts besides auto, and whether it has fast and ultrafast lanes. pf status --json run in the workspace reports the model and effort a new session there starts with.
When the active model goes through the Vercel AI Gateway, one model is often served by several providers (for example Anthropic directly, AWS Bedrock, or Google Vertex). pf can tell the gateway which providers to use, in what order:
// ~/.pf/settings.json
{
"provider_order": ["bedrock", "anthropic"], // try Bedrock first, then Anthropic
"provider_strict": false // true restricts requests to only these providers
}Both keys also work in a committed project .pf.json, and per launch:
pf --provider-order azure,openai --provider-strict
pf ask --provider-order bedrock "review this change"
PF_PROVIDER_ORDER=vertex PF_PROVIDER_STRICT=1 pfSlugs are the gateway's provider identifiers (letters, digits, dashes, for example anthropic, bedrock, vertexAnthropic), listed on the models page. An empty provider_order in a higher-precedence layer clears a list set by a lower one. Routing applies to gateway requests only; custom model connections ignore it.
pf ships with pf-dark and pf-light and follows your terminal's light or dark mode. Pin a variant with PF_THEME=light or PF_THEME=dark, or drop a VS Code format theme at ~/.pf/themes/<name>.json and select it with the theme setting or PF_THEME=<name> per launch. Without an explicitly selected theme, diff markers and edit counts stay monochrome; selecting any theme adds its diff marker colors. See Configuration for all environment variables.
When a conversation fills the model's context, pf compacts it so the work can continue. The newest few turns stay unchanged. Every compacted turn keeps your messages and the assistant's final reply word for word. The conversation's own model adds a short note on what the assistant did in between, and a line for each tool call: pf writes what the call was from the call itself, like shell zig build test (failed, exit 1, 3120 bytes), and the model adds why it was used and what it showed. The model also keeps numbered entries for your rules, quoted word for word, and for facts, decisions, status and open questions, plus a list of the skills and MCP tools used. Entries are never rewritten: a later entry can say it replaces an earlier one, and a status entry can close an open entry it answers or finishes. At the next compaction, the one before it is saved whole with an ID like L2, and in its place the agent sees a short summary the model writes of all earlier compactions, plus their rules, status and open entries still in force, word for word. The turns of earlier compactions leave the agent's view however many compactions a session has; only those kept entries grow with it. In a session that is not saved, nothing can be stored, so earlier compactions stay in view. pf checks every new note and entry, and marks without removing one that names no source, quotes words you did not write, states a path, long number, version or quoted text found in none of the compacted turns and tool calls or the turns that stay after them, names an ID that does not exist, or calls a failed tool call a success; turns the model skipped, or a missing summary of earlier compactions, are asked for once more. Only when the compacted conversation would leave too little room to continue are its longest texts shortened to their start and end, each naming the saved turn that keeps it whole. Every compacted turn is saved word for word with an ID like M3, every tool call with its input and output as the model saw them, plus the handle of any full output saved separately, with an ID like T12, and every earlier compaction with an ID like L2. The agent can search them by text or open one by ID with read_tool_result; a search also says how many saved records hold all of its words, and which came first and last.
Automatic compaction asks the model right after the conversation, exactly as the agent was about to send it and with the same settings, so the provider can reuse what it has cached. When that request does not fit or fails, and when you run /compact to compact now, pf writes the turns out in a separate request at the model's lowest reasoning; turns too large for one such request go oldest first, in as many requests as it takes.
Automatic compaction starts when a request reaches 80 percent of the model's usable input. Set auto_compact_percent in ~/.pf/settings.json to any value from 10 to 80, or PF_AUTO_COMPACT_PERCENT for a single launch:
// ~/.pf/settings.json
{ "auto_compact_percent": 60 }pf builds as a native binary or WebAssembly. Applications embedding pf can provide network transport, session storage, configuration, permission handling, and terminal I/O.
| Surface | Use |
|---|---|
pf acp |
Connect the native agent to editors and other Agent Client Protocol clients. |
createPfAgent() |
Embed the agent core in a JavaScript host with pf-core.wasm. |
createPfTerminal() |
Embed the interactive terminal with pf-term.wasm. |
ACP clients can keep their MCP tools loaded on every turn, steer a running turn, supply a session system prompt, serve MCP servers over the ACP connection, and choose each session's workspace. See ACP embedding.
ACP sessions offer the CLI's permission modes, auto (the default), ask, and full-access, as the mode config option and in modes. A session starts in the saved permission_mode, and choosing a mode with session/set_config_option or session/set_mode saves it, like /permissions in the shell. Any other mode returns an error. To show the choice before a session exists, pf status --json run in the workspace reports the mode a new session there starts in and lists the modes, each with its id, name, and description.
pf does not distribute a JavaScript SDK. The SDK sources build the libpf package from this repository for pf's own tests and benchmarks, and the WebAssembly SDK is experimental. To embed an agent in a JavaScript application from npm, use the SDK that the upstream fx project publishes; it embeds that project's agent, not pf.
To run pf from your own tools, use pf ask. It takes the prompt as arguments or on stdin, writes its result to stdout and progress and diagnostics to stderr, and exits with a nonzero status when the request fails. With --json, the final answer is in final_output. For the flags of the built version, run pf <command> --help.
To inspect a user's setup, run pf status --json for configuration and runtime information, or pf doctor --json for local health checks. Neither changes the setup.
pf ask uses the user's saved permission mode, which is auto unless they changed it. Pass --full-access only when the user asks for it, because it turns off pf's permission checks.
To change pf itself, read AGENTS.md first, then build and verify the change as described in Build from source.
pf slack install and personal Slack MCP authorization still use the fx Slack app and its bridge on fx.sh, which Vercel operates. Paneflow Agent does not run its own Slack app yet. Run pf slack --help for the commands; credentials stay in the owner-only file ~/.pf/slack/installation.json.
Building pf requires Zig 0.17.0. From a clone of this repository:
zig build -Doptimize=ReleaseSafe
./zig-out/bin/pfRun the test suite with zig build test. See CONTRIBUTING.md for development and contribution guidelines.
pf runs natively on Windows 10 version 1809 or later and Windows 11, on x86_64. The interactive shell needs a console with virtual terminal support: Windows Terminal and the VS Code integrated terminal are supported, and pf exits with an error in a console that cannot process escape sequences. Headless commands such as pf ask and pf status run from any console.
Build from source as above, from PowerShell or Git Bash, with Zig 0.17.0 for Windows x86_64 on PATH. The build produces zig-out\bin\pf.exe; put that directory on PATH. Your profile lives in %USERPROFILE%\.pf, so pf finds the same settings, credentials, and sessions from PowerShell and from Git Bash.
The shell tool runs commands in Git Bash when it is available and in PowerShell otherwise. It never uses cmd.exe. pf selects the shell once per process, in this order:
PF_GIT_BASH_PATH, the absolute path of a Git Bashbash.exe, when it is set- the
bash.exeinstalled with thegit.exefound onPATH %ProgramFiles%\Git\bin\bash.exepwsh.exe(PowerShell 7) onPATHpowershell.exe(Windows PowerShell 5.1) onPATH
Set PF_WINDOWS_SHELL to bash to require Git Bash, to powershell to skip it, or to auto (the default) for the order above. With bash, pf reports an error instead of falling back when Git Bash is missing. pf doctor shows the selected shell and why it was chosen, and the model is told which shell and path conventions to use.
pf does not cache shell startup files on Windows: Git Bash reads its login files for every command, PowerShell runs without a profile, and /shell reload says so.
Under Git Bash, permission analysis works as on Linux and macOS. pf does not parse PowerShell, so under PowerShell every command needs an exact approval of that command or a configured rule that names it exactly; in auto mode, each one goes through the security review. Wildcard allow rules and the automatic approval of routine read-only commands never apply to PowerShell commands.
Workspace file listing for @-completion, glob_files, and grep_files reads git's rules as data on Windows too. It reads your global git config and excludes file, but not the system config of Git for Windows (<install>\etc\gitconfig), so an ignore or exclude rule set only there is not applied. The git that pf runs itself, for pf pr, pf issue, and direct git status, git diff, and git log, is the git.exe of the selected Git Bash; when PowerShell is the selected shell, pf does not run git itself.
Interactive programs that the shell tool runs with a terminal, such as REPLs, run in a Windows pseudo console (ConPTY) inside the pf process, under the selected Git Bash or PowerShell. Each session runs inside a Job Object that pf holds, so a session and every process it started end when pf exits or crashes. A later pf run, such as pf ask --resume last, reports those sessions as ended. Each session keeps its control files in a private directory under %USERPROFILE%\.pf.
Stored credentials, such as sign-in sessions, API keys, and MCP OAuth tokens, are encrypted with DPAPI to your Windows account, so another account or a copy of the disk cannot read them. A file that cannot be decrypted on the current logon, for example over an OpenSSH key-based logon or after a profile move, is kept as a .unreadable backup next to the original, and pf asks you to sign in again.
Turn notifications ring the terminal bell, as on Linux. In Windows Terminal, pf also requests a desktop notification, which Windows Terminal shows while its window is unfocused when compatibility.allowOSC777 is enabled in its settings.
- Image paste from the clipboard
pf slack install- The full output of a truncated command in a session that is not saved, such as
pf ask --no-save: saved sessions keep it - Release downloads: build from source
- Sessions v2 (
--sessions-v2,PF_SESSIONS_V2): pf exits with an error and keeps saving sessions in the default store
Windows Terminal handles some key chords itself before pf receives them (default actions). Use these alternatives, or remove the Windows Terminal binding in its settings:
| Key | Windows Terminal action | Use instead |
|---|---|---|
| Alt+Enter | Toggle full screen | Shift+Enter, Ctrl+J, or Esc then Enter for a new line |
| Ctrl+V | Paste text | Text paste works; image paste is not available on Windows |
| Ctrl+Tab, Ctrl+Shift+Tab | Switch tabs | Tab, Shift+Tab |
| Alt+Left, Alt+Right | Move pane focus | Ctrl+Left, Ctrl+Right, or Alt+B, Alt+F to move by word |
| Alt+Up, Alt+Down | Move pane focus | Ctrl+Up, Ctrl+Down to move by row |
| Alt+Shift+Arrow | Resize pane | Ctrl+Shift+Left, Ctrl+Shift+Right, or Alt+Shift+B, Alt+Shift+F to select by word; Shift+Up, Shift+Down to select by row |
| Ctrl+Shift+Up, Ctrl+Shift+Down, Ctrl+Shift+PgUp, Ctrl+Shift+PgDn, Ctrl+Shift+Home, Ctrl+Shift+End | Scroll the buffer | Shift+Up, Shift+Down, Shift+PgUp, Shift+PgDn to extend the selection |
| Ctrl+Shift+A, Ctrl+Shift+F | Select all, find | Shift+Home, Shift+Right |
| Ctrl+Shift+C, Ctrl+Shift+V, Ctrl+Shift+P, and other Ctrl+Shift+letter chords | Copy, paste, command palette, tabs | The same chord without Shift |
Ctrl+C reaches pf unless text is selected in Windows Terminal, in which case it copies the selection.
Report security vulnerabilities privately through GitHub's private vulnerability reporting on this repository instead of a public issue.
Apache-2.0. Third-party licenses and attributions are listed in THIRD_PARTY_NOTICES.md.
Interface sounds by cuelume.
{ "provider": "gateway", "models": { "gateway": "openai/gpt-6-astra" }, "ultrafast_mode": true }