Guide
What Pendant is, how it works with Pi, and what to expect from it. Open the guide on pendant.sh.
01
At a glance
Pendant is a companion for the Pi coding agent. It gives Pi a home next to your code. You chat with the agent, follow its work and review what it changed. Pi itself does the thinking, runs the tools and talks to your model provider.
Pendant gives you
- An interface built for following an agent at work
- Context from your workspace without copy and paste
- Your existing Pi setup, supported as best as possible
- Session history you can search, pin and branch
- Local voice input, and offline use with a local model
Pendant does not
- Provide models, subscriptions or credits
- Sandbox the agent or ask before it runs a tool. Pendant will support sandboxing soon; follow issue #367.
- Route anything through a Pendant server, need an account or collect telemetry
02
Pi and its Pendant
Pi is a coding agent. You give it a task in plain language, and it works through it on your computer. It reads files, edits them, runs commands and reports back. It uses a language model from a provider you choose. Without Pendant, you use Pi in a terminal.
Pendant is a companion to Pi, not a replacement for it. It puts a graphical interface around Pi, next to your code. The agent you talk to is still Pi, configured the way you configured it.
Pendant comes in two forms. Pendant in VS Code is an extension for your editor. Pendant for Desktop is a standalone app with everything built in.
Who looks after what
| Who | Looks after |
|---|---|
| Pi | The agent itself and its tools, the connection to model providers and your credentials, extensions, skills, prompt templates, and sessions. |
| Pendant | The chat interface, how the agent's work is shown, the context it can take from your workspace, voice input, and how your sessions are listed and organised. |
| You | Which providers and models you use, what the agent is allowed to touch, and keeping agents that work at the same time out of each other's way. |
If you already use Pi, little should change. Pendant reads the same configuration and keeps sessions where Pi keeps them. If Pi is new to you, How it works explains how the pieces fit together.
An independent project
Pendant is built independently. It is not affiliated with or endorsed by the Pi project, whose team develops and maintains Pi itself, including its providers and its agent runtime.
03
How it works
Pendant carries its own copy of Pi and runs it on your computer. You don't install Pi or any other runtime separately, and nothing sits between Pi and the model provider you chose.
- Your workspace your code and files
- Pendant the interface
- Pi built into Pendant
- Your model provider or a local model
From your message to the model and back
The chat is only the interface. When you send a message, Pendant adds the context you allowed and hands the message to Pi. Context and what the agent sees shows exactly what that looks like.
Pi runs inside Pendant, in a background process of its own, apart from the interface. It keeps the session, talks to your model provider directly and runs its tools on your computer. While it works, it reports every step, such as text as it is written, its thinking, tool calls and their results. Pendant turns these reports into the live transcript you watch.
Nothing on this path passes through a Pendant server.
Your existing Pi setup
Pi keeps its configuration in a folder on your computer. It holds your settings, the providers and credentials you set up, extensions, skills, prompt templates and sessions. The Pi inside Pendant reads that same folder and aims to use it the way Pi in a terminal does. There is no second configuration to keep in sync.
A session you start in Pendant is an ordinary Pi session, stored where Pi stores its sessions.
One Pi version, chosen by Pendant
Each Pendant release includes one specific version of Pi, tested together with it. Pendant updates Pi in its own releases. So after Pi publishes a release, Pendant may include an earlier one until its own next release. You can't point Pendant at a different Pi installation.
Where your data goes
Pi talks to your model provider directly, with no Pendant server or account in between. Privacy and your data has the details.
Working without a network
With a local model, everything above runs offline. Pendant itself needs no connection to work.
04
What you can do
Pendant is built around one activity. You work with an agent on your code. Everything it offers serves that.
Follow the agent's work
Replies stream in as the agent writes them. Tool calls, file edits and command output appear as they happen, with changes shown as diffs. Diagrams and mathematical notation render in the chat, and so do the plans, to-do lists and subagents that Pi extensions add. While the agent is working, you can steer it with a new message or queue the next one. Everyday conveniences covers this and other small helpers.
Give it context from your workspace
Attach files, folders, selections, problems reported in your code, the state of your Git repository, and images, without copying and pasting. You decide which kinds of context Pendant may include. Context and what the agent sees shows exactly what reaches the agent.
Keep and organise sessions
Every conversation is a Pi session you can come back to. Search through them, pin the ones you need, and branch a session to try a different direction. See Sessions.
Review changes and write commit messages
Follow every edit as a diff, and take recorded edits back when that is still safe. Pendant can also write commit messages and pull request descriptions from your changes. See Reviewing changes and Git.
Choose how the agent works
Pick the model, and how much it thinks before it answers, from whatever your Pi setup provides. Start a chat with a narrower set of tools, for example a read-only set for a review. You can also add to the prompts Pi uses, or turn off its context files. Models, tools and prompts covers all of this.
Use Pi extensions and packages
Pi extensions work in Pendant. When an extension asks a question, shows a dialog or draws its own interface, Pendant displays it in the chat. You can also browse, install and remove Pi packages from Pendant, as Models, tools and prompts describes. Pi extensions in Pendant explains in detail how extensions behave here.
Hear when the agent needs you
Pendant can tell you when a run has finished or an extension is waiting for an answer, with a system notification or a sound. You can also run a command of your own for these moments, for example to reach your phone while Pendant works on a remote machine. Hooks explains how, and Settings covers the notification settings.
Talk instead of typing
Dictate your messages with voice input that runs entirely on your computer. Your audio never leaves it. Voice dictation explains how it works and which speech models it uses.
Keep work apart, and take edits back
Start a session in its own Git worktree, so its changes stay separate from your main checkout. And when the agent has made a text edit you don't want, Pendant can often take it back. That works as long as the file hasn't changed since and no session in that workspace is running.
Follow along from your phone
Remote access lets you follow a session, answer an extension's questions and send prompts from your phone, while everything keeps running on your computer. It is still in internal development and not in the published versions yet. See Remote access.
05
Models, tools and prompts
Pi decides how the agent works. Pendant gives you a place to choose the model, how much it thinks, which tools it may use and which prompts it starts from. Some of these choices are Pi's own settings, so Pi in a terminal sees them too. This chapter says which ones.
Connecting a provider
To chat, Pi needs at least one model provider. Pendant helps you connect one, and offers to when none is set up yet.
- Sign in with your plan. For providers that offer it, you sign in with your existing subscription in the browser, or with a code on another device. Pi runs the sign-in.
- Use an API key. Paste a key for a provider you have an account with.
- Add a custom provider. Point Pi at any server that speaks one of the common model APIs, for example a local model server. Pendant can ask the server which models it offers.
Pi stores what you connect in its own configuration folder, where Pi in a terminal finds it too. Pi keeps one credential per provider, so a new key or sign-in replaces the old one. A key that comes from an environment variable is shown as such. While a run is going, credentials can't be changed.
Choosing a model
The model list shows every model Pi can use right now. That includes Pi's built-in providers, your custom providers and providers that Pi extensions add. Search matches the model's name, its identifier and its provider. Each model shows the size of its context window. Models that accept images are marked, and searching for images or vision finds them.
When you edit Pi's list of custom models, the model list updates on its own. If Pi can't load the file after a change, the list stays as it was until the file loads again.
If you limited Pi to a set of models in its settings, the list also offers that set on its own, and opens on it.
When you pick a model or a thinking level in a chat, it applies to that session only, as it does with Pi's own commands in a terminal. Pi's defaults for new sessions stay as they are. A new chat starts with Pi's default model, and you can pick another before you send the first message.
Thinking
Many models can think before they answer. The thinking levels on offer are the ones the current model supports. If you switch to a model that doesn't support your level, Pendant moves to the nearest one it does. For a model that doesn't think at all, the control disappears.
Thinking is hidden in the transcript unless you choose to show it. It is part of the session either way.
Presets
A preset is a model and a thinking level together, as a button next to the message box. Use presets to switch between, for example, a quick model for small questions and a strong one for hard problems. You can add the current choice as a preset, rename presets and reorder them. A preset whose model is not available is shown but can't be used.
Tools
You choose which of Pi's tools the agent may use in a chat.
- Pi's defaults. The tools your Pi setup names as defaults, otherwise reading, running commands, editing and writing files, plus the tools of your extensions.
- Read-only. Only reading and searching files, with no extension tools. Good for questions and reviews.
- None. No tools at all.
- Your own set. Any built-in tools you pick.
The choice belongs to the chat. It is saved with its session and never changes Pi's own settings. A new chat starts with the set you chose in Pendant's settings, and you can change it before you send the first message. After that, you can change a chat's tools whenever it is idle. An extension may still adjust them for a turn.
Skills
Skills are instructions Pi can pick up on its own when they fit the task. For each session, you can switch individual skills off, so the agent no longer picks them up by itself. You can still use such a skill by calling it with a slash. The switch is saved with the session, which reloads in place and keeps its conversation.
Prompt templates
A prompt template is a prompt you saved as a file. You start it with a slash and its name. Pi finds templates in its own configuration folder and in your project.
Text you type after the name fills the template's placeholders, the same way Pi fills them in a terminal. When a template takes arguments, Pendant can show a small form with one field for each argument. It also shows a preview of the prompt as Pi will receive it.
You can create a template from Pendant, or open one to edit it. A template you add, change or remove outside Pendant appears in the slash list on its own. If Pi can't load a template, for example because the details at its top don't parse, Pendant tells you once. It also lists the template with Pi's reason, so you can open the file and fix it.
Prompts and context files
Pendant can change what Pi starts from.
- Replace Pi's system prompt with your own text or a file.
- Append prompts of your own to it.
- Stop reading context files such as AGENTS.md and CLAUDE.md.
- Load extra extensions, skills or prompt templates from paths you name, or stop Pi from discovering them on its own.
Pendant hands these settings to Pi as they are. Pi's context files and skills are still added after a system prompt you set. When you change one of these settings, Pi restarts with it once no session is running, so a running turn is never cut off.
Pi packages
You can browse, install, update and remove Pi packages from Pendant.
- Browsing searches the npm registry for packages marked as Pi packages. Installing needs npm on your computer.
- Scope. A package goes into your user scope, or into the project you have open. The project scope needs a trusted workspace.
- Switching off. You can turn off a whole package, or single extensions, skills or prompts inside it, without removing it.
- Updates. Pendant checks for updates and offers them, unless you work offline.
- Recommendations. Your list of installed packages also recommends a few packages that Pendant supports especially well. A package leaves the recommendations once you install it. You can dismiss them. The package manager's menu brings them back.
Extensions run with the same access as the agent, in every new session. After a change, idle sessions reload right away. A session that is working reloads after its current turn.
Cache warming
Many providers keep a recent prompt in a cache for a while, which makes the next request faster and often cheaper. Pi can keep that cache warm while the agent runs, or also while the session is idle, or not at all. This is one of Pi's own settings, so it applies to Pi in a terminal too.
06
Context and what the agent sees
Pi only knows what reaches it in your message and what it reads with its own tools. This chapter shows how you give it context, and exactly what Pendant adds to your message.
Ways to add context
- Mention a file or folder by typing @ in your message.
- Drop files or folders from your editor or your file manager onto the message box.
- Pick files from a list.
- Send a reference to the lines you selected in your editor.
- Pick lines by their line numbers and add them to the chat.
- Quote a change from a diff.
- Pin the file you have open, with the lines you selected in it.
- Select lines in your editor, then click into the message box. The selection is attached with its line range. If you remove it, it stays off until you select something else.
- Keep the file you have open attached. This is off until you turn it on in the settings. The file follows you when you switch files. If you remove it, it stays off until you switch to another file.
- Select text in the transcript and quote it. Once sent, the quote's badge on your message takes you back to the passage.
- Paste an image.
When you paste a long text, Pendant folds it into a small badge, so your message stays readable. The whole text is still sent.
Files travel as references
An attached file is sent as its path, relative to your workspace. When you attached a selection, the path carries its line range. The agent then reads the file itself with its tools, so it always works with the current content. A reference to selected lines works the same way. It names the lines and does not copy them.
Some lines can't be read from disk. Examples are a change in a diff, an older version of a file, or a file you haven't saved. Pendant quotes those lines instead. It copies them into your message, with the file and the lines they come from.
This keeps messages small, and it means the agent sees a file exactly as it is on disk. It also means a file outside the agent's reach, for example on another machine, does not help it.
Images
An image you paste travels with your message as an image, for models that accept images. Common formats work, and a message can carry a few of them. Before the image goes to the model, Pi may scale it down or convert it, and it tells the model when it did. An image file you drop or attach is a file reference like any other file.
Automatic context
Pendant can also add a snapshot of your editor to every message. This is off until you turn it on in the settings. When it is on, each part can be switched on or off.
| Part | What it adds | Starts |
|---|---|---|
| Open file | The file you are looking at, its language and length, whether it has unsaved changes, where your cursor is, which lines are visible, and your other open tabs. | On |
| Selection | The text you selected in that file, in full. | On |
| Problems | The errors and warnings reported for that file. | Off |
| Git | Your branch, how far it is ahead of or behind its upstream, how many changes are staged or unstaged, and merge conflicts. | Off |
Unlike a selection reference, the automatic selection copies the text itself. If you have text selected in the transcript when you send, Pendant leaves the editor selection out of that message.
What Pi receives
Pendant puts all context in one marked block in front of your message. Inside it, each kind of context has its own tag, always in the same order. Editor context comes first, then Git, then attached files, then quotes. A part with nothing to say is left out. Images travel next to the text, not inside it.
Here is a message with two attached files, one of them with a line range, and a quote from the transcript. Automatic context is off. The quote names the session entry of the response it came from, which is how its badge finds the passage again.
--- pi-extension-context:start ---
<file-context>
@src/cart.ts
@src/cart.test.ts:12-30
</file-context>
<quote-context>
<quote source="8154cd9d">
Expected 3, received 2
</quote>
</quote-context>
--- pi-extension-context:end ---
Why does this test fail?
And here is a message with automatic context on and every part switched on. One line is selected, and the file has a warning.
--- pi-extension-context:start ---
<editor-context>
Active file: src/cart.ts (typescript, 84 lines, modified)
Cursor: line 41, col 5
Visible range: lines 20-60
Open tabs: src/cart.ts, src/cart.test.ts
Selection: lines 41-41
```typescript
return items.length;
```
Diagnostics:
src/cart.ts:41 - warning (ts): Unused variable total.
</editor-context>
<git-context>
Branch: fix-cart (ahead 2)
Changes: 1 unstaged
</git-context>
--- pi-extension-context:end ---
Count quantities, not items.
In the chat, Pendant shows only your text, with the context as small chips above it. The block above is what is stored in the session and what the model reads.
Commands and skills
When you pick a prompt template or a skill with a slash, Pendant expands it into your message before sending. The expanded text follows the context block. Text you type after a prompt template's name fills its placeholders, the way Pi fills them. Commands that Pi or an extension handles itself are sent exactly as you typed them, without a context block. A shell command you run from the message box runs directly, without an agent turn. Its output is recorded in the session, so the agent sees it with your next message.
Changing what the model sees later
Sometimes a long tool output or a wrong turn keeps distracting the agent. You can change what the model sees from then on, without rewriting the conversation.
- Omit a tool call, one of your messages or one of the agent's replies. The model no longer sees it. Omitting a tool call also omits its results. When a reply made several tool calls at once, they are omitted together.
- Edit it instead, to keep a shorter or corrected version. For a tool call, only its result changes.
- Restore it later to bring back the original.
The transcript keeps showing the original, marked as omitted or edited. The change is saved in the session as one of Pi's own entries, so it holds when you open the session again. It isn't possible while the agent is working, for a reply that failed or was stopped, or for anything compaction has already summarized.
Compaction
When a session grows close to the model's context window, older turns can be summarized to make room. Pi keeps the recent part of the conversation as it is and replaces the rest with a summary.
Pi does this on its own when the context gets close to full, unless you turn automatic compaction off. That switch is one of Pi's own settings, so it applies to Pi in a terminal too. You can also compact a session yourself. Pendant asks first, because compaction can't be undone, and it suggests compacting when the context meter shows the session nearly full.
What Pendant does not add
Pendant adds no system prompt of its own and no hidden instructions. Only your own setup changes Pi's system prompt. You can replace Pi's system prompt, append prompts of your own to it, and choose whether Pi reads context files such as AGENTS.md. These settings are handed to Pi as they are.
07
Sessions
Every conversation in Pendant is an ordinary Pi session. Pi stores each one as a file in its own folder for sessions, grouped by workspace. Pendant reads and writes those same files. It keeps only a few small things of its own, such as which sessions you pinned and the colours you gave them.
Finding a session
The session list shows the sessions of the workspace you have open. A branch, a clone or a worktree session appears under the session it came from.
A session that is working shows the step it is on, for example running a test command or reading a file. You can also have working sessions move to the top of the list while they run. Turn on Group running sessions in Pendant's settings, under Appearance. When the run ends, the session goes back to its place.
Search looks through your messages, the agent's replies, its thinking and its tool calls. You can search this workspace or all of them. A session from another workspace opens read-only, and Pendant offers to open its folder in a new window. The search index is built in memory while Pendant runs and is never saved.
Inside an open chat, you can also search the conversation itself and step through the matches.
Pins, colours and names
Pin the sessions you return to often, so they stay at the top of the list. Give a session a colour to find it at a glance, and rename it whenever you like. You can reorder the list or sort it, for example by when the agent last replied.
Pendant can also name new sessions for you. It then sends your first message to your model provider, in a separate temporary session, and asks for a short title. This is off until you turn it on.
Branching
You can take a conversation in a different direction without losing the original.
- Fork from one of your messages to continue from that point in a new session. It opens in the same view or in a new tab.
- Edit one of your messages, and the edited version is sent as a new branch from that point.
- Clone a session to copy the conversation as it stands.
Worktree sessions
A worktree session works in its own Git worktree, on a new branch, in a hidden folder inside your repository. Its changes stay out of your main checkout until you merge them, so two agents can work on the same repository without editing the same files. Pendant can copy selected ignored files into the new worktree, for example local settings the project needs.
When you remove a worktree, Pendant first shows which files have uncommitted changes and whether the branch is removed too. The worktree's sessions stay in your history.
A worktree session needs a trusted workspace and a Git repository with at least one commit.
Several sessions at once
Each open chat has its own session, and they run side by side. Pendant does not keep them out of each other's way. If two of them work in the same folder, that is up to you, and worktree sessions are the easy way to separate them. See What Pendant does not do.
Export and delete
You can export a chat as a web page to read or share. You can also export a session as a bug report. Personal details are redacted, and you decide whether diagnostic logs go with it.
Deleting a session removes its file for good. It does not go to a trash folder.
08
Reviewing changes and Git
The agent edits files on your computer. Pendant helps you see what it changed, take edits back when that is possible, and describe your work for Git.
Following changes
Edits and new files appear in the chat as diffs while the agent works. A long diff can open in a full editor tab instead. After a turn that changed files, a summary lists them, each with its diff.
Taking edits back
From that summary, Pendant can undo the text edits the agent recorded. This works as long as the file has not changed since, and no session in that workspace is running. Pendant refuses rather than overwrite work it cannot account for.
Git stays the full record of your work. Commit often, and worktree sessions keep an agent's changes apart. See Sessions.
Commit messages
Pendant can write a commit message for you.
It reads your staged changes. If nothing is staged, it reads the changes in files Git already tracks. New files that Git does not track yet appear by name only. It also sends the branch name and the list of changed files. If you already typed a message, it goes along as a hint.
Some things are held back. Files that look sensitive, for example keys or environment files, keep their name but not their content. A very large diff is cut short, and the model is told that the list of files is still complete.
The message is written in a separate temporary session, apart from your chats. It uses the model and thinking level of the chat you are working in, unless you choose others in the settings. You can add instructions of your own, as text or from a file in your repository, and choose the language of the message.
The result replaces the text in the commit message box. To see exactly what was sent, you can open the prompt and the model's reply from the last generation. Pendant keeps that in memory only.
The diff and file names go to your model provider, like any other prompt. With a local model, they stay on your computer.
Pull request descriptions
Pendant can also describe a branch for a pull request. It reads the branch's commits and its diff against the base branch, and it has its own setting for instructions. The result opens as a new document, with the title on the first line and the description below it.
09
Everyday conveniences
Smaller things that make working with an agent smoother.
Queue and steer
You don't have to wait for the agent to finish. A message you send while it works waits in a queue. You can reorder, edit or remove queued messages. When something can't wait, send a message as a steer instead. It reaches the running turn right away.
Each session keeps its own queue. A queued message goes out when its session's turn ends, even while you look at another session. A queued shell command waits until you come back to its session.
Shell commands
Start a message with an exclamation mark to run a shell command directly, without an agent turn. It runs in your workspace and its output appears in the chat. The output is also recorded in the session, so the agent sees it with your next message. That makes it a quick way to hand the agent a test run or a log.
Prompt history
The message box remembers what you sent in each session, shell commands included. Step back through earlier messages to send one again or change it first.
Drafts
A message you started but haven't sent stays in its session. It is still there after a reload or a restart. A draft on the start page carries over into the chat it starts. Sending or clearing a message removes its draft.
Stopping
Stop ends the agent's work as soon as possible. Pi cancels the model's answer and the tool that is running, and any question an extension was waiting on is closed. What the agent already did stays done. Reviewing changes and Git shows how to take edits back.
Search in a chat
Find text in the open conversation and step through the matches, including in long tool output.
Links to your code
When a finished answer mentions a function or another symbol from your code, the name can become a link to its definition.
Diagrams and math
Mermaid diagrams render in the chat. You can switch a diagram to its source, or open it in its own tab. Mathematical notation renders too.
Context meter
A small meter shows how much of the model's context window the session uses. It warns you as the window fills up, so you can start a new session or compact this one in time.
Prompt suggestions
Pendant can suggest your next message as grey text in the message box. It uses a model you choose, and it sends that model your last message and the agent's last reply. This is off until you choose a model for it.
Notifications
Pendant can tell you when a run has finished or the agent is waiting for an answer. See What you can do, and Hooks for running a command of your own.
10
Settings
Pendant works without any setup, and its defaults suit most people. When you want it to behave differently, most of what you see and how Pi runs can be adjusted. This chapter explains where settings live, when changes take effect, and what you can adjust beyond the features covered in other chapters.
Where settings live
Pendant has a settings screen of its own, inside the chat. It groups the settings by topic and can be searched. Some choices also sit right where you use them, for example showing thinking or picking a tool set.
Pendant's settings are separate from Pi's. Pi keeps its own settings, providers and credentials in its configuration folder, and some of your choices in Pendant change those, as What Pendant writes explains. Pendant can open Pi's own files for you, for example its settings and its list of custom models.
When changes take effect
- Most settings apply right away, including to chats that are open.
- Settings that change how Pi starts apply once Pi restarts. These are the prompt and resource settings, the default tool set, working offline, the extension UI mode and Pi's environment variables. Pendant restarts Pi on its own once no session is running, so a running turn is never cut off.
- The location of Pi's folder applies after Pendant itself restarts.
The transcript
- Detailed or focused. The focused view folds runs of the agent's routine tool activity into quiet rows, so the conversation stays readable. The detailed view shows every tool call on its own. You can switch at any time.
- How much of a tool shows. Tool calls can start folded, normal or fully open with their output. You can also set this for single tools by name, for example to keep a noisy tool folded or to hide it. Errors, and tools you opened yourself, stay open.
- Grouping file exploration. Consecutive reads, searches and listings are grouped into one row you can open.
- Hiding noise. Tool calls the model wrote incorrectly, which it almost always retries right away, are hidden by default. Failed read-only calls, such as reading a file that doesn't exist, can be shown quietly instead of as errors.
- Command labels. Common commands, for example Git or test runs, get a readable label. You can have the raw command shown instead.
- Thinking can start open or folded, or fold once it is done.
- Long sessions load their most recent messages first. You can change how many.
Diffs and changes
- Layout. Show removed and added lines side by side, or one after the other.
- Markdown files. Show a change to a Markdown file as formatted text or as a raw diff. Each edit also has a switch.
- Opening in a tab. Choose how large an edit must be before it opens in its own editor tab. Small finished edits open by themselves in the chat, and you choose up to which size.
- The changed-files summary can be turned off, list files as a tree or with full paths, and show every file or only the first few.
The message box
- Sending and steering. Choose which key sends a message and which one sends it as a steer. If the two collide, sending wins.
- Long pastes. Choose how long a pasted text must be before it folds into a badge, or turn that off.
- Prompt history can be turned off.
- The text cursor can be a line, an underline or a block.
Appearance
- Your messages sit on the right with a light tint of the accent colour. Both can be turned off.
- Stronger contrast gives tool calls, thinking and replies clearer borders and backgrounds.
- Fonts. For accessibility, you can change the interface font, the code font and the base font size.
- Scrolling. The transcript scrolls smoothly as new output arrives, or jumps if you prefer.
- Running sessions can move to a Running group at the top of the session list. This is off by default. Turn on Group running sessions to use it.
Pendant's colours follow your light, dark or high-contrast theme.
Extensions
- Widgets from Pi extensions appear beside the message box, or pinned at the top of the chat so they stay visible while you scroll.
- Checklist cards can be turned off.
- Status values that extensions publish are hidden unless you choose where to show them. You can also limit them to the values you care about.
- The extension UI mode decides whether extensions show dialogs or terminal cards. Pi extensions in Pendant explains both.
Pi
- Pi's folder. Point Pendant at a different folder for Pi's configuration and sessions. By default it uses the same folder as Pi in a terminal.
- Environment variables. Give the Pi that Pendant runs extra environment variables, for example a proxy setting a provider needs.
- Retrying. Pi retries on its own when a provider reports a passing error, such as a rate limit or a timeout. You can turn that off.
- Working offline switches off Pi's and Pendant's own network use, apart from talking to your model provider. Privacy and your data lists what that covers.
Notifications
- System notifications tell you when a run has finished or the agent is waiting for you. They appear only while Pendant is not in front, so they never interrupt you while you watch. Where your system supports it, clicking one takes you to the session.
- A sound can play at the same moments, and you can choose your own sound file.
To reach you somewhere else, for example on your phone, use a hook. See Hooks.
Providers
For subscriptions that report it, the footer shows how much of your usage limit is left. For providers with a credit balance, the model's tooltip shows it. Both can be turned off.
Exports and diagnostics
- Exported chats start with the display choices you set, for example whether thinking, timestamps or raw tool data show. The exported page can still change them.
- Diagnostic logs are off by default. When you turn them on, Pendant writes logs to files on your computer, to help with a bug report. They never leave your computer unless you send them. See Where to go next.
Settings in other chapters
Settings for a single feature are described with that feature.
- Automatic context, in Context and what the agent sees.
- Models, thinking, tools, prompts and packages, in Models, tools and prompts.
- Session names and the session list, in Sessions.
- Commit messages and pull request descriptions, in Reviewing changes and Git.
- Prompt suggestions, in Everyday conveniences.
- Hooks, in Hooks.
- Voice dictation, in Voice dictation.
- Where settings are stored in your editor, in Pendant in VS Code.
11
Pi extensions in Pendant
Most Pi extensions run in Pendant without changes. Pendant does not load them itself. The Pi inside Pendant loads them, the same way it does in a terminal, and every session runs them. What changes is the screen around them. Pi draws extensions in a terminal. Pendant draws them in its chat, with its own controls wherever it can.
This chapter is a reference for extension authors. It explains what an extension sees in Pendant, what the user sees for each part of Pi's extension API, and how to write an extension that works well in both places. It uses the names from Pi's extension API, so Pi's extension documentation is the best companion to it.
How Pendant loads extensions
Pendant's Pi finds extensions where Pi always looks. That is the extensions folder in Pi's configuration folder, the one in your project's Pi folder, and the packages and extension paths your Pi settings list. Pendant's own settings add to this. You can name additional extension paths to load, and you can switch off automatic discovery, so that only those explicit paths load.
You can also browse, install and remove Pi packages from Pendant. After a change, sessions that are idle reload their extensions straight away. A session that is working reloads once it finishes.
A project's own extensions load only after you trust its folder. Pi never loads
them before that, and a project you don't trust does not stop your own extensions.
ctx.isProjectTrusted()
reports your answer.
project_trust
handlers never run, because your answer is settled before any extension loads.
Only trust folders whose extensions you trust.
Extensions reload when you ask Pendant to reload Pi's configuration, when a
package changes, and when an extension calls
ctx.reload(). A reload keeps the conversation. It clears the widgets, statuses, title, working
message and thinking label the extension set. Dialogs that were still open return as
if dismissed, and custom components close. Text in the composer and the tool expansion
stay as they were. A change to Pendant's extension settings applies when Pendant
next starts Pi.
Two presentation modes
Pendant's extension UI mode setting decides which mode Pi reports to extensions as
ctx.mode. It does not switch any feature on or off.
| Extension UI mode |
ctx.mode
|
What it is for |
|---|---|---|
| Dialogs, the default | "rpc" |
Extensions that offer both dialogs and a terminal interface choose their dialogs, and Pendant shows them as its own controls. |
| Terminal | "tui" |
Extensions that only run in Pi's terminal work as they do there. Their custom components appear as terminal cards. |
In both modes every member of
ctx.ui
works, and
ctx.hasUI
is true. An extension that reads "rpc" can still call
ctx.ui.custom, and Pendant still shows it. The mode is only a hint about which presentation the
user prefers.
A terminal card is a small terminal inside the chat. Pendant runs the extension's Pi TUI component there, drawn with Pi's theme. Keyboard and mouse input reach the card while it has focus. Pendant uses terminal cards for custom components, component widgets and custom renderers. Headers and footers are the exception. Pendant draws their lines as styled text, because they never take input.
What each ctx.ui member does
-
ctx.ui.selectshows a card at the end of the chat that lists the options. Dismissing it returnsundefined. -
ctx.ui.confirmshows a card with the message and a yes and a no answer. Dismissing it returnsfalse. -
ctx.ui.inputshows a card with a text field and its placeholder. Dismissing it returnsundefined. -
ctx.ui.editorshows a card with a multi-line text field, filled with the prefill text. Dismissing it returnsundefined. -
ctx.ui.notifyshows a notice in the chat, styled as information, a warning or an error. Nothing is returned. -
ctx.ui.setStatusshows status text in the chat's footer. It stays hidden until the user chooses to show statuses, in the chat's footer, the status bar or both. The user can also pick which keys to show. Passundefinedto clear a status. -
ctx.ui.setWorkingMessagereplaces the text of Pendant's working indicator while the agent works. Terminal escape codes are removed. Call it without an argument to restore the default. -
ctx.ui.setWorkingVisiblehides or shows Pendant's working indicator. -
ctx.ui.setWorkingIndicatoranimates Pendant's working indicator with the frames and interval you give. Escape codes in the frames are removed, so colors do not carry over. -
ctx.ui.setHiddenThinkingLabelsets the title of the thinking blocks in the chat. Call it without an argument to restore the default. -
ctx.ui.setTitlesets a title. Pendant shows it as the title of its view or of the chat's tab. The session name stays unchanged. An empty title removes it. -
ctx.ui.setWidgetwith lines of text shows a card above the composer, or below it with the"belowEditor"placement. Pendant draws it as text, not as a terminal, and keeps its colors. When the first line is short, a widget of several lines becomes a card that folds under that line. Lines in checklist form become a checklist card. The user can pin widgets under the chat header instead.undefinedor blank lines remove the widget. -
ctx.ui.setWidgetwith a component shows a terminal card in the same place. It takes keyboard and mouse input while it has focus. -
ctx.ui.setHeadershows a band below the chat header, drawn as styled text. It starts folded, and the user can open it. It takes no input. -
ctx.ui.setFootershows a band below the composer and its controls, drawn as styled text. It takes no input.footerDatareports the workspace's Git branch and the extension statuses. -
ctx.ui.customshows a terminal card at the end of the chat, which takes keyboard focus when it appears. It closes when the component callsdone. Overlay options are accepted, but the card stays in the chat instead of floating above it. The promise rejects if the card is taken down beforedone, for example by a stop or a reload. -
ctx.ui.setEditorComponentis accepted and ignored. Pendant's composer always stays in place. An extension cannot replace it. -
ctx.ui.getEditorComponentalways returnsundefined. -
ctx.ui.setEditorTextandctx.ui.pasteToEditorreplace or add to the text in Pendant's composer. -
ctx.ui.getEditorTextreturns the text in Pendant's composer. -
ctx.ui.addAutocompleteProvideradds the provider's suggestions to the composer's suggestion list. See the notes on autocomplete below. -
ctx.ui.onTerminalInputreceives what the user types into a focused terminal card, before the component does. It never sees what the user types into the composer. -
ctx.ui.theme,ctx.ui.getAllThemes,ctx.ui.getThemeandctx.ui.setThemework with Pi's themes, which Pendant uses for terminal cards, headers, footers and renderers. Pendant's own appearance never changes.setThemerecolors the terminal content of every session in the same window. -
ctx.ui.getToolsExpandedandctx.ui.setToolsExpandedread and set whether tool cards are open. Setting it opens or closes every tool card in the chat and decides how later ones start. When the user opens or closes a card,getToolsExpandedfollows.
A few of these need more detail.
Themes. Terminal content starts in Pendant's own colors. It uses
a Pi theme once Pi's configured theme is one loaded from a theme file, or once an
extension selects one with
setTheme. Until then,
ctx.ui.theme
returns text unchanged from its color and style functions.
Autocomplete. Pendant asks the providers while the user types a slash
command at the start of the message, or a word that starts with @,
# or one of the characters a provider lists in
triggerCharacters. The provider handed to your factory as current returns no suggestions,
because Pendant completes files and commands itself. A provider that adds to its
results therefore starts from an empty list.
Headers. A header starts folded, so the user may never open it. Don't put anything there that the user must see.
Dialogs in detail
Every dialog appears as a card at the end of the conversation, where the user answers
it or dismisses it. Pendant treats every way a dialog can end without an answer the
same way. It returns what dismissing returns, which is undefined, or
false for
ctx.ui.confirm.
-
Timeouts. With a positive
timeout, the card shows a countdown and closes when it runs out. -
Abort signals. Aborting the
signalcloses the card. A signal that is already aborted returns at once, without showing a card. - Several at once. Tools that run in parallel can each ask something. Every dialog gets its own card and its own answer.
-
Stopping the agent. When the user stops a run, open dialogs return
as dismissed. Until the stop has finished, any further dialog returns as dismissed
straight away, and
ctx.ui.customrejects. - Another session in view. A dialog waits while the user looks at a different session. The session list marks the session as waiting for an answer, and the card comes back, with its countdown still running, when the user returns.
Pi's
ui_prompt_start
and
ui_prompt_end
events fire around dialogs and custom components, as they do in the terminal.
Commands
Commands registered with
pi.registerCommand
appear in the composer's slash command list, with their description and the name
of the extension they come from. When the user sends one, Pendant hands the text to Pi
unchanged, and Pi runs your handler. Pendant does not add extension commands anywhere
else, for example to the editor's list of commands.
Pendant never asks a command for argument completions, so
getArgumentCompletions
is not called. To suggest arguments in Pendant, add an autocomplete provider with
ctx.ui.addAutocompleteProvider. Pendant asks providers while the user types a slash command.
Pendant handles a few commands itself, for example to start a chat, pick a model or open its settings. If an extension command has the same name, Pendant's own command wins whenever it is typed without arguments. Give your commands distinctive names.
The command context works as it does in Pi.
ctx.newSession,
ctx.fork,
ctx.navigateTree,
ctx.switchSession,
ctx.waitForIdle
and
ctx.reload
do what their names say.
ctx.abort()
stops the run.
ctx.shutdown()
closes that session's Pi and leaves Pendant running.
Messages and input
Messages from
pi.sendMessage
appear in the chat when display is true. With a renderer registered
through
pi.registerMessageRenderer, the message shows the renderer's component in a terminal card. Without one, or
when the renderer returns undefined, Pendant shows its own card, labelled
with the message's custom type and with the content rendered as Markdown. Messages
with display false stay out of the chat. Messages from
pi.sendUserMessage
appear like any other.
Pendant expands prompt templates and skills itself, before the message reaches Pi. It
can also add context from the workspace, when the user has allowed that. An
input handler can therefore receive more text than the user typed. Its
source is "interactive". Messages that start with
an extension command are the exception and reach Pi exactly as typed.
Changes an extension makes with
pi.setSessionName,
pi.setModel
or
pi.setThinkingLevel
show up in Pendant's interface.
Pendant lets the user choose the agent's tools for a session. When a session has
such a choice, Pendant applies it again before every message. A tool set chosen
earlier with
pi.setActiveTools
can therefore be replaced. A tool set chosen in a
before_agent_start
handler still holds for that turn.
Tools
Tools registered with
pi.registerTool
run as they do in Pi, and each call appears as one of Pendant's tool cards.
-
Without renderers, the card shows the call and its result in
Pendant's own style. While the call runs, the card shows the partial results the
tool sends through
onUpdate. The text in theircontentshows as it is, and each update replaces the one before. A partial result without text shows a summary of itsdetailsinstead. -
With
renderCallorrenderResult, the renderers' components appear as terminal cards inside the tool card, below its title. The call comes first and the result follows. The result renderer takes the place of the card's own result view, except for images, which Pendant keeps showing. -
Expansion follows the card. Opening or closing it renders again
with the changed
expandedvalue, andcontext.invalidate()renders again at any time. - Pi's built-in tools keep Pendant's cards, which show edits as diffs and command output as it arrives. An extension that replaces a built-in tool with its own definition gets its own renderers instead.
- Subagent tools from subagent extensions that Pendant recognizes keep Pendant's subagent card, and their renderers are not used.
-
A view for Pendant only. A tool result can also carry its own view
for Pendant, in Markdown or HTML, together with a header label and whether the card
opens. Pi's terminal ignores it. The next section explains this
details.pendantobject.
Tool results with details.pendant
A tool can give Pendant its own view of a result. It puts a
pendant object into the result's details. Pendant reads
that object when the tool call finishes, and it draws the view inside the tool card.
Pi's terminal ignores the object. There the tool looks exactly as before.
Pi hands the model the result's content. The details are
for rendering and for state. So keep a plain-text version of the result in
content, both for the model and for Pi's terminal.
The object can come from two places.
-
The tool itself. The
executefunction of a tool registered withpi.registerToolreturns it indetails. -
A
tool_resulthandler. A handler registered withpi.oncan return changeddetailsfor any tool. That includes tools from other extensions and Pi's built-in tools. Pi then stores the changeddetailsin place of the original ones.
Pi saves details in the session file unchanged. The view therefore comes
back when the session is opened again, and nothing has to run again to produce it.
Fields
Every field is optional, and each one works on its own. A result can publish only a header label, or only whether the card opens.
| Field | Type | What Pendant does with it |
|---|---|---|
markdown |
string |
Renders it as Markdown in place of the card's result view. Raw HTML inside
it shows as plain text. Task list items such as - [x] done become
checkboxes.
|
html |
string |
Renders it in place of the card's result view, after Pendant's
sanitizer has cleaned it. Pendant uses it only when markdown is
missing or blank.
|
title |
string | Replaces the tool's name in the card's header. For a built-in tool it also replaces the icon and the file link that Pendant would show. |
subtitle |
string |
Shows after the name, in the muted part of the header where Pendant puts a
search pattern or a path. With no title, it annotates
Pendant's own name for the tool.
|
expanded |
boolean |
true opens the card when the call finishes.
false keeps it closed. Without it, Pendant decides as usual.
|
How Pendant reads the fields
-
Only the final result counts. Pendant reads
details.pendantwhen the call finishes. Partial results that the tool sends while it runs keep Pendant's usual live view. -
Wrong types are ignored. A field with another type counts as
missing. So does a string that is empty or only whitespace.
expandedmust be a real boolean, so a string such as"true"is ignored. -
Unknown fields are ignored. So is a
pendantvalue that is not an object, for example an array or a string. - Markdown wins over HTML. When both are present and usable, Pendant renders the Markdown.
-
No body keeps the usual view. Without a usable
markdownorhtml, the card shows Pendant's own result view, or the tool's result renderer. - Bodies have no length limit. The card holds whatever you publish.
-
Labels stay on one line. Line breaks, tabs, control characters and
invisible formatting characters in
titleandsubtitlebecome single spaces. Pendant shows labels as text, never as markup. It does not shorten them. When the header runs out of room, the label ends with an ellipsis, and the whole label appears as its tooltip.
When the card opens
expanded decides whether the card opens when a call finishes live in the
chat. It overrides Pendant's own choice, including the tool display setting that
opens every successful call. A card kept closed still opens when the user clicks it.
There are a few exceptions.
- A result that is an error always opens.
-
A session opened again from history starts with every card closed, whatever
expandedsays. - An edit or a write whose diff is too large for the chat keeps opening in a separate diff view.
What the HTML may contain
Pendant cleans published HTML before it shows it, and scripts never run. These rules apply.
-
Allowed elements.
a,blockquote,br,button,code,div,em,h1toh6,hr,img,input,kbd,label,li,ol,option,p,pre,progress,section,select,small,span,strong,summary,table,tbody,td,textarea,th,thead,trandul. -
Removed with their content.
script,style,iframe,objectandembed. - Other elements. Pendant removes the element and keeps its content.
-
Attributes. Event handler attributes such as
onclickare removed, and so isstyle.data-andaria-attributes stay, as do common ones such asclass,id,title,alt,value,checked,disabled,colspanandrowspan. -
Links.
hrefworks withhttp:,https:andmailto:addresses and with#anchors. Links may open withtargetset to_blankor_self. -
Images.
srcworks with embeddeddata:andblob:addresses. Pendant removes every other image address.
Buttons and form fields appear, but nothing is connected to them. Use them for display only.
With renderers and Pendant's own cards
- Pendant's own views. A published body replaces the result view Pendant would draw for the tool. That applies to every tool, including Pi's built-in tools with their diffs and command output.
-
renderResult. When the tool has a result renderer and it draws, its terminal card takes the result's place, and the published body stays hidden.title,subtitleandexpandedstill apply. Images in the result stay visible, as they do with a result renderer alone. -
renderCall. The call renderer is not affected. It still appears above the result. -
Subagent tools. While a recognized subagent tool runs, its card
shows Pendant's live subagent view. When the call finishes, a published body
replaces that view, as it does for any tool.
title,subtitleandexpandedapply as well.
details.pendant works the same way in both extension UI modes.
Example: a tool with its own view
This tool lists a repository's releases. The model and Pi's terminal get a
plain list in content. Pendant shows the same list as Markdown, names the
card after the repository and opens it.
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
import { Type } from 'typebox';
interface Release {
tag_name: string;
published_at: string;
}
export default function releases(pi: ExtensionAPI): void {
pi.registerTool({
name: 'list_releases',
label: 'List releases',
description: 'List the latest releases of a GitHub repository.',
parameters: Type.Object({
repo: Type.String({ description: 'The repository as owner/name.' }),
}),
async execute(_toolCallId, params, signal) {
const response = await fetch(
`https://api.github.com/repos/${params.repo}/releases?per_page=5`,
{ signal },
);
if (!response.ok) {
throw new Error(`GitHub answered ${response.status} for ${params.repo}.`);
}
const releases = (await response.json()) as Release[];
const lines = releases.map(
(release) => `- **${release.tag_name}**, published ${release.published_at.slice(0, 10)}`,
);
const markdown = lines.length > 0 ? lines.join('\n') : 'No releases found.';
return {
// What the model and Pi's terminal see.
content: [{ type: 'text', text: markdown }],
details: {
// What Pendant shows. Pi's terminal ignores it.
pendant: {
title: 'List releases',
subtitle: params.repo,
markdown,
expanded: true,
},
},
};
},
});
}
Example: a view for a tool you don't own
A
tool_result
handler can give another tool a view in Pendant. Keep the details that
are already there, because the tool's own renderers and state may depend on them.
Leave content alone, so the model sees what the tool reported.
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
interface Task {
subject: string;
status: 'pending' | 'in_progress' | 'completed';
}
export default function todoView(pi: ExtensionAPI): void {
pi.on('tool_result', (event) => {
if (event.toolName !== 'todo' || event.isError) {
return;
}
const details = (event.details ?? {}) as Record<string, unknown> & { tasks?: Task[] };
const tasks = details.tasks ?? [];
if (tasks.length === 0) {
return;
}
const done = tasks.filter((task) => task.status === 'completed').length;
const markdown = tasks
.map((task) => `- [${task.status === 'completed' ? 'x' : ' '}] ${task.subject}`)
.join('\n');
return {
details: {
...details,
pendant: { subtitle: `${done} of ${tasks.length} done`, markdown, expanded: false },
},
};
});
}
What Pendant recognizes
Some extension output gets a presentation of its own. It applies in both modes.
-
Checklists. A text widget becomes a checklist card with a progress
bar when every non-blank line is a checkbox item. An item is
☐or☑followed by text, or a Markdown item such as- [ ] textor- [x] text. One heading line may come before the first item. A count at the end of the heading, such as(3/5), is removed, because the card shows its own. Any other line keeps the widget as plain text. When every item gets ticked while the user watches, the card hides itself a few seconds later. It stays while the user points at it or has it open. The user can hide checklist cards. - Plans. Pendant recognizes plans from Plannotator. Once the user approves a plan, it shows the plan's steps as a checklist and ticks them off as the agent reports each one done.
- Subagents. Pendant recognizes the tools of several popular subagent extensions and shows their runs in a subagent card, with each step's progress and, where the extension provides one, its transcript. It hides those extensions' own status widgets, since the card already shows the same. Subagents explains exactly how it detects runs, which fields it reads and which names are reserved.
-
Questions. A tool named
ask_user_question, as in rpiv-ask-user-question, appears as Pendant's own question dialogs. -
MCP servers. Pi connects to MCP servers itself. Pendant shows
Pi's MCP manager in the chat, in your language. There you sign in to a server,
reconnect it, turn it on or off, see its tools and choose how the agent reaches
them. You can also add, edit and remove servers from Pendant, and a change to an MCP
configuration file takes effect on its own. Before you save a server, you can test
the connection. Pendant then connects with the settings you entered and lists the
server's tools, or says why it couldn't connect. A test saves nothing and
never starts a sign-in. A tool named
mcp, as in pi-mcp-adapter, keeps Pendant's own card. -
Web tools. Tools named
web_search,fetch_content,get_search_contentandsource_check, as in pi-web-access, keep Pendant's own tool cards. A search shows its query, and the results show as Markdown, with their links. Any extension that names its tool the same way gets the same card. - Task lists. Pendant shows the task list of rpiv-todo as a checklist card titled "Tasks". It hides the extension's own widget, since the card shows the same list.
Tested extensions
Any extension built on Pi's extension API should work in Pendant, within the limits in the next section. These extensions have dedicated support in Pendant, and they are tested with it. The last column says how.
| Extension | What Pendant does for it | How it is tested |
|---|---|---|
| rpiv-ask-user-question | Shows its questions as Pendant's own dialogs, with answers, free text and cancelling | Automated tests run the real extension |
| pi-mcp-adapter | Keeps Pendant's own card for its mcp tool |
Tests of how Pendant reads its format |
| Plannotator | Turns an approved plan into a checklist and ticks off its steps | Automated tests replay output recorded from the real extension |
| pi-subagents | Subagent cards, the subagent panel, transcripts and run controls | Automated tests use a stand-in that reproduces its tools, results and files |
| @tintinweb/pi-subagents | Subagent cards and the subagent panel | Tests of how Pendant reads its format |
| pi-subagents-j0k3r | Subagent cards and the subagent panel | Tests of how Pendant reads its format |
| pi-web-access | Keeps Pendant's own cards for its web tools, with the query and linked results | Automated tests run the real extension against a local search server |
| rpiv-todo | Shows its task list as Pendant's checklist card | Automated tests run the real extension |
Each Pendant release is tested against specific versions of these extensions. A newer version of an extension can change its format. If something stops working, Where to go next explains how to report it.
What is not supported
These parts of the API do nothing in Pendant. None of them raises an error, so an extension that uses them keeps working.
| API | What happens in Pendant |
|---|---|
pi.registerShortcut
|
Registration succeeds, but the handler never runs. Pendant has no way to press an extension's shortcut. |
pi.registerFlag,
pi.getFlag
|
Pendant starts Pi without command-line flags, so nothing can set one.
getFlag
returns the flag's default, or undefined if it has none.
|
getArgumentCompletions
on a command
|
Never called. |
pi.registerMarkdownTransformer
|
Never applied. Pendant renders the conversation's Markdown itself. |
project_trust
handlers
|
Never run. Your answer to the trust question is settled before any extension loads. |
ctx.ui.setEditorComponent
|
Accepted and ignored. Pendant's composer always stays.
getEditorComponent
returns undefined.
|
Overlay positioning in
ctx.ui.custom
|
Accepted, but the card stays in the chat. |
Designing for Pi and Pendant
An extension that follows these guidelines works well in Pi's terminal and in Pendant, whichever mode the user picks.
-
Prefer the standard dialogs.
select,confirm,inputandeditor, together withnotify,setStatusand text widgets, become Pendant's own controls. They fit the chat and follow Pendant's appearance. Keep custom components for interfaces that the standard dialogs cannot express. -
Choose the presentation by
ctx.mode. If you offer both dialogs and a terminal interface, use the dialogs whenctx.modeis"rpc". Don't usectx.hasUIto tell Pendant apart from Pi's terminal, because it is true in both. -
Treat an empty answer as a cancellation.
undefinedfromselect,inputoreditor, andfalsefromconfirm, can mean the user dismissed the dialog, it timed out, its signal aborted or the user stopped the run. Stop what you were doing. Don't ask again in a loop, because while a stop settles every dialog returns as dismissed at once. -
Handle a rejected custom component. Wrap
ctx.ui.customintryandcatch. A stop, a reload or a closed session takes the card down, and the promise rejects. -
Don't depend on keyboard shortcuts. Offer every action as a
command, which the user can find in the slash command list.
ctx.ui.onTerminalInputonly sees keys typed into a focused terminal card. - Don't depend on flags. Read configuration from a file or from settings instead.
-
Suggest arguments with an autocomplete provider, not only with
getArgumentCompletions. - Give commands distinctive names, so none collides with a command Pendant handles itself.
-
Write checklists in checklist form. Use
☐and☑or Markdown checkboxes, at most one heading line, and nothing else in the widget. - Keep widgets short. Put a short title on the first line of a widget with several lines, so Pendant can fold it under that title.
- Don't rely on color alone. Terminal content may start without a Pi theme, and Pendant removes escape codes from the working message, the working indicator and the title.
- Keep essential information out of the header. It starts folded.
- Think twice before replacing the editor. In Pendant only submitting is connected, so key handling you build on the editor's callbacks does not work.
- Try both modes. Run your extension with each extension UI mode, and in Pi's terminal as well.
When something goes wrong
Pendant reports extensions that fail to load as errors in the chat when it starts Pi. If a custom component or a renderer throws, its card says that the terminal could not be displayed, and the rest of the chat carries on. Pendant also lists the extensions it has installed alongside your skills and prompt templates.
Pi's terminal shows the most detail about an extension's own errors. If an extension misbehaves in Pendant, run it in Pi in a terminal as well. If it fails there too, it is a question for the extension or for Pi's documentation. If it only fails in Pendant, Where to go next explains how to report it.
12
Subagents
Some Pi extensions let the agent hand work to subagents. A subagent is another agent that takes one task, works through it and reports back. Pendant does not run subagents itself. It recognizes the tools of subagent extensions, follows their runs and shows them in the chat. This chapter explains exactly how, for people who use these extensions and for people who write them.
Recognized extensions
Pendant recognizes these subagent extensions by the names of their tools. The names are exact and case-sensitive.
| Extension | Tools | What Pendant offers |
|---|---|---|
| pi-subagents | subagent |
Cards, the subagent panel, transcripts, and controls to interrupt, steer, stop, resume and answer runs |
| @tintinweb/pi-subagents |
Agent, get_subagent_result,
steer_subagent
|
Cards and the subagent panel |
| pi-subagents-j0k3r |
subagent_run, subagent_continue,
subagent_status, subagent_result,
subagent_list_tasks, subagent_cancel,
subagent_list_agents
|
Cards and the subagent panel |
Other subagent extensions still work as ordinary Pi extensions. Their tools get ordinary tool cards. Their runs appear in the subagent panel only in the one case the next section describes. Pi extensions in Pendant says how each recognized extension is tested.
How Pendant detects a run
Pendant looks at two things, in this order.
- The tool name. A name from the table above ties the call to that extension. Pendant then reads its arguments and results in that extension's format. A name match always wins.
-
A subagent session on disk. A tool with any other name counts as a
subagent run only when its result
detailshasasyncDirornested_session_pathset to a string. Both point to a subagent's session on disk. Such a run belongs to an unknown extension.
No other field counts. A tool that reports an agentId, a
status or a list of agents keeps its ordinary card and stays out of the
subagent panel. So a tool that runs agents of its own can show its own progress.
A run from an unknown extension appears in the working indicator and the subagent panel, but its tool keeps an ordinary card. It never outlives the turn that started it.
Not every call to a recognized tool starts a run. Pendant sorts each call into one of three roles.
-
Launch.
subagent,Agentandsubagent_runstart a run. The one exception is asubagentcall that has a non-blankaction. That call manages an existing run or the extension's agents instead. A call that resumes a step is the one kind that adds a run. The revived step becomes a run of its own, marked as resumed. -
Continue.
subagent_continuesends a follow-up to an existing run. - Manage. Every other recognized tool looks at or changes an existing run, and never creates one. When such a call finishes, its card shows the tool's result text.
Which fields Pendant reads
Pendant reads each extension's fields in that extension's own format, with a few rules that apply to all of them.
- Nothing is guessed. A missing field, or a field of the wrong type, stays empty.
- Empty text counts as missing.
-
Cost is a number, or an object with one of
totalUsd,total,usd,amount,costorcostUsd. -
Agent names from a launch's arguments start with a letter,
followed by up to 63 letters, digits,
_,.or-. Other names are dropped. A step without an agent name shows as "subagent". - Statuses each extension reports are mapped to Pendant's own. A status Pendant does not recognize becomes unknown and counts as still running. A run without a status of its own takes one from its steps.
pi-subagents
-
Launch arguments.
taskswith more than one item makes a parallel run, andchainwith more than one item makes a chain. Otherwise the run has one step. Each item names its agent inagentorname. A top-levelagentnames the agent of a single run, andtaskbecomes its label.async: falsemakes it a foreground run. Without it the run counts as a background run, as pi-subagents starts it by default, until the call's own progress or result shows it runs in the foreground. -
The run.
runIdorasyncIdis the run's id.stateis its status, andactivityStateflags a run that needs attention or runs long. Pendant also readserror,startedAt,endedAt,totalTokensortotalChildUsage, andtotalCost. A result withasyncIdorasyncDirbelongs to a background run. -
Steps come from
stepsorprogress. For each step Pendant readsworkflowKeyorchildIdas its id, andagent,status,labelortask,model,thinking,activityState,currentTool,currentPath,recentTools,turnCount,toolCount,tokens,totalCost,startedAt,endedAt,lastActivityAt,errorandtranscriptPath. Fromsteering, Pendant reads what became of the newest message sent to the step. -
Workflows. With
modeset toworkflow, Pendant also reads the steps inworkflow.trace, the value inworkflow.value, the times inmission, and the children inworkflowChildren.children. -
Waiting. A
bg_waitcall keeps an ordinary card. Each run its result lists undercompletionstakes thestategiven there, or completed or failed fromsuccess. -
Results. Items in
resultsfill in what the steps left empty. They add each child's outcome,transcriptPath,model, cost, duration, andfinalOutputoroutput.
@tintinweb/pi-subagents
-
Launch arguments.
subagent_typenames the agent, anddescriptionbecomes the label.run_in_background: truemakes it a background run. A run always has one step. -
Results describe that one child. Pendant reads
subagentTypeordisplayName,status,modelName,turnCount,maxTurns,toolUses,tokens,error,activity,description, andagentIdas the run's id.
pi-subagents-j0k3r
-
Launch arguments.
agents, a list of names, oragentnames the agents.taskbecomes the label, andmode: "background"makes it a background run.subagent_continueonly readsprompt. -
Results. Steps come from
tasks,resultsor a singletask. For each Pendant readsid,agent,status,task,model,effort,last_activity,output_preview,usage,attempt,started_at,ended_at,last_activity_at,nested_session_path, anderrorwitherror_metadata.
Unknown extensions
-
Launch arguments. The agent comes from the first item of
agents,tasksorchain, or else fromagent,name,subagent_typeorsubagentType. The label comes fromtask,descriptionorprompt. -
Results. Steps come from the first of the arrays
tasks,results,stepsorprogressthat holds an item withstatusand eitheragentorsubagentType. Pendant takesagentorsubagentTypeandstatusorstatefrom each item. Items that have onlynameandstatusdo not count, so a to-do list never becomes a list of steps. Without such an array Pendant reads the same fields fromdetailsitself.
If the call that launched a run fails, the run is marked failed, with the call's text as its error.
Reserved names
These names mean something to Pendant. Use them only when you mean what Pendant takes them to mean.
-
Tool names from the table above. A tool registered under one of
them gets Pendant's subagent card, and its
renderCallandrenderResultrenderers are not used. -
The
detailskeysasyncDirandnested_session_path. They make a result count as a subagent run. An unrelated tool that sets them shows up in the subagent panel as a run. -
Widget keys. Pendant hides text widgets set under
agents,subagent-async,subagent-fleet-status,subagent-inspectorsubagents-claude-background, and any text widget with a line that starts withPI_SUBAGENT_ASYNC_JSON:orPI_SUBAGENT_INSPECT_JSON:. These are the status widgets of the recognized extensions, and the panel shows the same. -
Event channels. Events on
subagents:created,subagents:started,subagents:completedandsubagents:failedkeep a session counted as busy. A background run announced onsubagent:async-startedappears in the panel, andsubagent:async-completebrings its final state. Neither keeps a session busy. -
PI_SUBAGENTS_TEMP_ROOT. If you set it for pi-subagents, Pendant looks for that extension's runs there too. - Forked sessions. A session that was forked from another one, and whose first message contains the instructions pi-subagents gives a delegated fork, is treated as a subagent's session. Pendant leaves it out of your session list.
How runs appear
- In the transcript. The tool's card shows the run live. There is one row per step, with its status, agent or label, elapsed time and any error. A step's details show its model, effort, turn, tokens, cost, recent tools, the tool it is running, a preview of its output and its error.
- Above the message box. While subagents work, the working indicator shows one dot for each child, or a bar when there are many. Clicking it opens the subagent panel with every run of the session.
- Parallel runs and chains show each step as its own row.
- Nested runs from pi-subagents background runs appear as runs of their own in the panel.
- Stale steps. A step that still claims to be running but has not reported anything for five minutes is marked as stale.
- Transcripts. For pi-subagents, a step whose result or status file names a transcript can show that transcript in the panel. That covers steps in the foreground and in the background.
- Runs without a tool call. pi-subagents can start background runs without a tool call, for example from one of its commands. These appear in the panel too.
- Paused runs. A paused background run of pi-subagents stays followed until the extension finishes it.
- After a run ends, it stays in the panel for a few seconds. A failed run stays until you have seen it.
While a subagent is active, the chat counts as busy. The stop button stays, compacting is not available, and queued messages wait until everything is idle.
While a run goes on, Pendant also follows the progress the recognized extensions write to disk, so the card stays current between tool updates.
When you open a session again, its cards come back from the session file. Background runs of pi-subagents that are still going are picked up again.
Controlling runs
What you can do with a run depends on the extension and on how the run was started.
| Control | Available for |
|---|---|
| Interrupt | pi-subagents runs in the foreground, except a parallel run with several steps. pi-subagents runs in the background with one step, except workflows. |
| Steer | pi-subagents runs in the background, and children of pi-subagents workflows in the foreground. You choose when the step gets the message: now, after its current turn, or automatically. |
| Stop | pi-subagents runs in the background. On a step's page it stops that step and leaves the others running. It asks you to click a second time. |
| Resume | pi-subagents runs in the background that paused, finished or failed. You give the step a message to go on with, and it continues as a new run. |
| Reply | A pi-subagents step that asks for a decision. Your answer reaches the step the way the agent's own answer would. |
| Abort the turn | Every run in the foreground. It stops the whole turn, and it is the only control for the other extensions. |
Pausing is not offered. A run that was stopped, or that ended in the foreground, has no controls. After you steer a step, its page says whether the step got the message. For pi-subagents, Pendant uses the controls that extension provides itself.
With details.pendant
A recognized subagent tool can publish its own view with details.pendant,
like any other tool. While the call runs, its card shows the live subagent view. When
the call finishes, the published view replaces it. The working indicator and the panel
keep following the run either way.
Pi extensions in Pendant describes
details.pendant.
For extension authors
Pendant has no general protocol for subagent extensions. The extensions in the table get full support because Pendant knows their formats. Any other extension's tools get ordinary tool cards.
-
Set
asyncDirornested_session_pathin the resultdetailsif you want your runs in the working indicator and the panel. Put anagentorsubagentTypeand astatuson each step. Aborting the turn is then the only control. -
Or show your own progress. Leave those keys out, and your tool
keeps its ordinary card. Put progress text in the
contentof each partial result, and the card shows it as it arrives. Pi extensions in Pendant says how tool cards show progress. - Don't reuse a recognized tool name unless your tool follows that extension's format. Pendant would read your arguments and results as that extension's.
-
Avoid the reserved
detailskeys in tools that are not about subagents. -
Keep a plain-text result in
content. The model reads it, and so does anyone using Pi in a terminal.
13
Hooks
A hook is a command of your own that Pendant runs when it needs your attention. It runs when an extension waits for an answer, or when the agent has finished a run. What the command does is up to you. It can send a message to your phone, post to a chat, write a log line or play a sound.
Hooks are for the times when Pendant's own notifications cannot reach you. The typical case is a remote window, where Pendant runs on another machine. There a system notification would appear on that machine, not on the computer in front of you. A hook can forward the event to wherever you are.
Pendant's settings mark hooks as experimental, so their details may change.
Hooks and Pi's event handlers
Pi extensions have event handlers of their own, registered with
pi.on. Those are part of an extension's code, and they run inside Pi, in Pendant just
as in a terminal. Pi extensions in Pendant covers them.
Pendant's hooks are something else. You set them up in Pendant's settings, and Pendant runs them itself, outside Pi. They need no extension, and they don't see Pi's events.
Events
A hook can run for two events. By default only input_request runs it.
| Event | When it happens | Window focus |
|---|---|---|
input_request |
An extension waits for an answer. That is every dialog from
ctx.ui.select,
ctx.ui.confirm,
ctx.ui.input
and
ctx.ui.editor, and every custom component from
ctx.ui.custom
when it first appears.
|
The hook runs whether or not Pendant's window has focus, because you may be away from the machine. |
agent_finished |
A run ends and Pi is not about to retry it. That includes a run that finished normally, a run the user stopped and a run that ended with an error. | The hook runs only while Pendant's window does not have focus. |
Each event runs the hook once. Several dialogs at once run it several times. Hooks run
for every session in the window, including sessions that are not in view. Messages
from
ctx.ui.notify, statuses and widgets never run a hook.
Hooks do not depend on Pendant's other notification settings. A hook runs even when system notifications and sounds are off.
Setting up a hook
A hook takes two settings, both among Pendant's notification settings. The first is the hook command. The second is the list of events that run it. While the command is empty, no hook runs.
The command is a command line, and Pendant runs it through the system shell. On macOS
and Linux that is /bin/sh. On Windows it is cmd.exe. Pendant
adds no arguments of its own. You can pass arguments and use the shell's features.
In most cases the command is the absolute path of a script.
- Write a script, for example one of those below, and save it on the machine where Pendant runs.
-
On macOS and Linux, make it executable, for example with
chmod +x. - Enter the script's absolute path as the hook command.
- Choose the events that should run it.
What a hook receives
Pendant passes the event to the hook in two ways at once. Use whichever suits your script.
These environment variables are set, in addition to the environment Pendant itself runs with.
| Variable | Value |
|---|---|
PENDANT_EVENT |
input_request or agent_finished. |
PENDANT_TITLE |
Always Pendant. |
PENDANT_MESSAGE |
A short sentence for a notification, in the editor's display language. For
agent_finished it includes the session's name.
|
PENDANT_SESSION_PATH |
The path of the session's file, or an empty string when the session has none. |
Standard input receives the same values as one JSON object. It is a single line with no line break at the end, and Pendant closes standard input after it. A hook that doesn't need it may ignore it.
{"event":"agent_finished","title":"Pendant","message":"Pendant finished working in “Fix the flaky login test”.","sessionPath":"/path/to/agent/sessions/project/session.jsonl"}
| Key | Type | Value |
|---|---|---|
event |
string | The same as PENDANT_EVENT. |
title |
string | The same as PENDANT_TITLE. |
message |
string | The same as PENDANT_MESSAGE. |
sessionPath |
string | The same as PENDANT_SESSION_PATH. |
Nothing in the data tells a finished run from a stopped or failed one. Every
agent_finished event looks the same.
Decide what to do from event. The message is meant to be read by a
person. Its wording follows the display language, so don't parse it.
How a hook runs
- Where. The hook runs on the machine where Pendant runs. In a remote window, for example over SSH, in WSL or in a container, that is the remote machine. The script must be there, and it sends from there.
- Working folder. Pendant does not choose one. Use absolute paths in the command and inside the script.
- Time limit. Pendant stops a hook that runs longer than ten seconds and counts that as a failure. Give network requests in your script a shorter limit.
-
Success. A hook succeeds when it exits with code
0. Any other exit code is a failure. - Several at once. Every event starts its own process straight away. Pendant does not queue hooks or wait for one to finish before it starts the next.
- The agent never waits. The agent carries on while a hook runs, and a failing hook never stops it.
When a hook fails
A hook fails when it cannot start, when it exits with an error code, when it runs out of time, or when Pendant cannot write to its standard input. Pendant then shows a notification in the editor. It says that the hook failed and gives the reason, for example the exit code and the start of what the hook wrote to standard error.
Pendant also writes every hook run to a log of its own in the editor's output view. The log shows the command, the event, the session's file, every line the hook wrote to standard output and standard error, and how the run ended and how long it took. Pendant has a command that opens this log. Look there first when a hook does not do what you expect.
Security and privacy
- A hook runs as you. Pendant runs the command exactly as written, through the shell, with your user account's rights.
- Workspace settings count too. The hook command is an ordinary editor setting, so a workspace's own settings can set it as well. Check the editor settings of projects you open.
- Settings may sync. Settings Sync can copy the hook command to your other computers. Keep secrets such as access tokens in the script or in a file it reads, not in the command itself.
- Events carry session details. The message can contain the session's name, and a session's name can come from its first message. The session path shows where your sessions live. A hook that forwards events to an outside service sends these details along.
Example: a notification on your phone
This script forwards each event to a phone through a push service, for example ntfy. It marks questions as more urgent than finished runs. Replace the topic with your own.
#!/bin/sh
# Forward Pendant's events to a phone through ntfy.
topic="my-pendant-topic"
case "$PENDANT_EVENT" in
input_request) priority=high ;;
*) priority=default ;;
esac
# Stay well inside Pendant's time limit.
curl --silent --show-error --fail --max-time 8 \
--header "Title: $PENDANT_TITLE" \
--header "Priority: $priority" \
--data "$PENDANT_MESSAGE" \
"https://ntfy.sh/$topic"
curl exits with an error code when the request fails, so a failed
delivery shows up as a failed hook.
Example: a log of every event
This script appends each event to a file, one JSON object per line. It reads the object from standard input and adds the line break that Pendant leaves out.
#!/bin/sh
# Append every event Pendant sends to a JSON Lines file.
{ cat; echo; } >> "$HOME/pendant-events.jsonl"
Example: a hook on Windows
On Windows the command runs through cmd.exe. To run a PowerShell script,
name PowerShell in the command and pass the script's path.
powershell -NoProfile -File "%USERPROFILE%\pendant-hook.ps1"
The script reads the event from its environment, for example as
$env:PENDANT_EVENT and $env:PENDANT_MESSAGE.
14
Voice dictation
Pendant lets you speak a message instead of typing it. Speech recognition runs entirely on your computer. Your audio never leaves it, and neither does the text made from it. The only thing dictation downloads is a speech model, and only when you first need it.
This chapter explains how dictation works, which models it can use, and where they come from.
How it works
- Your microphone recorded by a small helper program
- whisper.cpp turns speech into text on your computer
- The composer the text appears at your cursor
When you start dictating, Pendant starts a small helper program that ships with it and records from your microphone. The helper runs only while you dictate. Each dictation starts a fresh one, and it closes once the text is ready.
While you speak, Pendant shows how loud the input is, so you can see that it hears you. When you stop, the helper trims the silence at the start and the end of the recording. It then hands the recording to whisper.cpp, an open-source speech recognition engine, together with the model file. whisper.cpp runs inside the helper, on your computer. Nothing is sent over the network, and no audio or text reaches Pendant's developers or anyone else.
The text goes into the composer, at your cursor. Pendant does not send it for you. You can read it, correct it and send it when you are ready. If the result is wrong, you can remove the text you just dictated in one step.
By default you hold the microphone button while you speak and let go to stop. A quick click starts and stops dictation as well. You can turn off holding in Pendant's settings. Dictation uses your system's default microphone unless you choose another one there.
Models
whisper.cpp needs a model file. Pendant offers the whisper.cpp models in two sizes. Tiny is faster. Base recognises speech more accurately. Each size comes in a multilingual version and an English-only version. And each of those comes as a full build and as a smaller quantized build, named Q5_1, which trades a little accuracy for a smaller download.
| Model | Languages | File | Download size |
|---|---|---|---|
| Base multilingual, the default | Many languages | ggml-base.bin |
about 142 MB |
| Base multilingual (Q5_1), recommended | Many languages | ggml-base-q5_1.bin |
about 57 MB |
| Tiny multilingual | Many languages | ggml-tiny.bin |
about 75 MB |
| Tiny multilingual (Q5_1) | Many languages | ggml-tiny-q5_1.bin |
about 31 MB |
| Base English | English only | ggml-base.en.bin |
about 142 MB |
| Base English (Q5_1), recommended | English only | ggml-base.en-q5_1.bin |
about 57 MB |
| Tiny English | English only | ggml-tiny.en.bin |
about 75 MB |
| Tiny English (Q5_1) | English only | ggml-tiny.en-q5_1.bin |
about 31 MB |
Pendant recommends the quantized Base builds. They are smaller than the full Tiny builds and still more accurate. English-only models are more accurate for English, so they suit you if English is all you dictate.
When Pendant needs a model, it shows a dialog that lists these models. For each one it shows the languages, the speed, the download size and the exact download address. You can open the same dialog later to switch models or to change the language and microphone.
Using your own model file
You can also use a whisper.cpp model file you already have, for example a larger one than Pendant offers. Give Pendant the file's full path, in the dictation dialog or in Pendant's settings. Pendant then uses that file directly and never downloads a model. The path must be absolute, and the file must exist. The file must be in the format whisper.cpp reads. Which languages it understands depends on the model.
Where models come from
Pendant downloads the built-in models from Hugging Face, from the model repository of
the whisper.cpp project at huggingface.co/ggerganov/whisper.cpp. That
request goes to Hugging Face, which sees ordinary network information such as your IP
address. The Privacy Policy describes this in detail.
A download happens only when you dictate with a model that is not on your computer yet, or when you choose such a model in the dictation dialog. Pendant downloads only the model you selected and shows its progress. It writes the file under a temporary name and keeps it only once the download is complete. It does not compare the file against a checksum.
Downloaded models are kept in Pendant's own storage on your computer, not in your project. They stay there until you delete them. Pendant can show how much space they take and delete them for you. A model file of your own stays where it is.
Languages
The multilingual models transcribe the languages whisper.cpp supports, for example German, Chinese or Spanish. Pendant does not detect the language you speak. You choose it, and dictation transcribes that language. English is the default. The dialog that lists the models also asks for the language, and you can change it in the dictation dialog or in Pendant's settings. Cantonese is not offered separately. Choose Chinese for it.
English-only models ignore the language setting. They always produce English.
Speed and hardware
Smaller models transcribe faster. On a Mac with Apple silicon, whisper.cpp uses the graphics processor. On an Intel Mac, on Windows and on Linux, it runs on the processor.
Offline
Dictation itself never needs the internet. Once a model is on your computer, dictation works without a connection. With Pendant's offline setting turned on, Pendant never downloads a model. If the selected model is not on your computer yet, dictation says so instead. A model file of your own always works offline.
Microphone permission
Pendant uses the microphone only when you start dictating.
- macOS asks for permission the first time. The request names the app Pendant runs in, or Pendant's helper. If you deny it, dictation shows an error. You can allow it later in the system's privacy settings.
- Windows and Linux do not get a prompt from Pendant. The microphone follows your system's own privacy and sound settings.
If no microphone is available, dictation tells you.
The first time
The first time you press the microphone button, Pendant opens the model dialog. You choose a model, or your own file, and the language. That press ends with your choice, and the model downloads straight away. The next press dictates.
Dictation works on macOS, Windows and Linux. On Windows and Linux, both x64 and Arm processors are supported.
Remote development
Dictation runs where Pendant runs. When your editor is connected to another machine, for example over SSH, in WSL or in a container, that is the other machine. Dictation then records from that machine's microphone, not from the one on the computer in front of you, and it keeps the model there. A server or a container usually has no microphone, so dictation cannot record there. See Pendant in VS Code for how remote development works.
15
Remote access
Remote access lets you use Pendant on your computer from your phone. You can follow a session, answer an extension's questions and send prompts while you are away from your desk. Everything still runs on your computer.
In internal development
Remote access and its phone app already work, but both are still in internal development. Neither is published yet. They will ship once they are polished and stable.
This chapter describes what you can expect from it and how it works.
What it lets you do
The Pendant app on your phone connects to Pendant on your computer. It lists the sessions your computer has open, and you pick one to follow. From there you can:
- Watch the session as the agent works, and scroll back through older parts of the conversation.
- Answer an extension's questions, so a long task that stops on a question does not have to wait for you.
- Send a prompt, steer the agent or queue your next message while it works.
- Stop a run, or cancel a tool call.
The phone shows the same chat as Pendant on your computer, because the computer sends the phone Pendant's own chat interface. When you answer a question on the phone, the question disappears on the computer, and the other way round.
The app can also tell you when a session needs you or a run has finished. There is no push service behind this. The app shows these notifications itself, so they arrive only while it is still running in the background.
The phone is for following and steering sessions. Settings, Git and the rest of Pendant stay on the computer.
What stays on your computer
Your sessions, files, tools and model requests keep running on your computer, exactly as they do without a phone. The phone does not run the agent and does not hold your code. It shows the chat and passes on what you type or choose.
A paired phone can do in these sessions what you can do at the keyboard. So pair only phones you own.
Pairing
Remote access is off until you turn it on in Pendant's settings. The first time, your computer's firewall may ask whether Pendant may accept incoming connections. When Pendant runs inside your editor, the firewall asks about the editor. Allow it.
To pair, ask Pendant to pair a phone. It shows a QR code, and you scan it with the Pendant app. There is no account to create. The code carries your computer's network addresses and what the phone needs to recognise your computer. It works once and expires after a few minutes. Showing a new code cancels the previous one.
How the phone connects
The phone connects directly to your computer. Both need to be on the same network, or on the same Tailscale tailnet.
To reach your computer when you are away from home, install Tailscale on both devices and sign them in to the same tailnet. If you add Tailscale after pairing, open the app once while your phone is on the same network as your computer, or scan a new QR code.
If your computer's address changes, scan a new QR code. Pairing again only refreshes the addresses. If the phone cannot connect, check the addresses Pendant lists in its remote access settings, and that your firewall allows the connection.
Encryption and servers
The connection is encrypted end to end. The keys are held only by your phone and your computer. Pendant uses the Noise protocol framework for this, with keys it creates on each device.
Until the encrypted handshake completes, neither device says anything about itself or your sessions. Anything else that reaches the port sees only handshake bytes, with no name and no version. A computer also stops accepting an address that fails the handshake repeatedly.
Nothing passes through a Pendant server. There is no Pendant account, no relay and no push service. If you use Tailscale or another VPN, it carries the encrypted traffic, but it cannot read it.
Revoking a phone
You can revoke a paired phone in Pendant's remote access settings. It is disconnected at once and refused afterwards. Turning remote access off closes the connection point on your computer. On the phone, you can forget a computer in the app.
Your computer keeps its own key and the list of phones you paired, with each phone's name, model and public key. On macOS and Linux only your user account can read them. The phone keeps its key in the system's secure keychain, tied to that device, so a backup never restores it onto another one.
Editor windows and profiles
When Pendant runs inside your editor, every window where it runs accepts phone connections on its own port. Pairing once covers every local window of the same editor installation and profile. A window in another profile is paired separately. So is a window connected to another machine over SSH, in WSL or in a container, because its pairing is kept on that machine. Settings Sync does not carry remote access to your other computers. It is on only where you turned it on.
The Privacy Policy describes what remote access stores on each device.
16
What Pendant does not do
Pendant is deliberately narrow. These are the things people most often expect from it and won't find.
Pendant does not
- Provide models. There are no built-in models, subscriptions or credits. You bring a provider or a local model through Pi.
- Add an agent of its own. The agent is Pi. The settings Pendant offers, for example tool sets or extra prompts, are handed to Pi.
- Sandbox the agent or ask before it acts. Tools run with your permissions, exactly as they do when you run Pi in a terminal. Nothing in Pendant asks before a command runs or a file changes. Pendant will support sandboxing soon; follow issue #367.
- Need an account or a server. There is no Pendant account, no Pendant server, no telemetry, no remote crash reporting and no syncing of sessions between computers.
- Coordinate several agents in one folder. If you run more than one session at a time in the same workspace, keeping them out of each other's way is up to you.
- Use a Pi you installed yourself. Pendant brings its own Pi, tested together with each release. How it works explains why.
- Offer everything the terminal offers extensions. Most extension features have an equivalent in Pendant, but a few terminal-only ones, for example an extension's own keyboard shortcuts, do not. See Pi extensions in Pendant.
Want a safety net?
Start chats with a read-only tool set when you only want answers, use a Pi extension that asks for approval before tools run, and keep your work in version control. For parallel work, worktree sessions give each agent its own copy of the repository.
17
Pendant in VS Code
Pendant for VS Code is an extension. It works in VS Code, VSCodium and other editors built on a recent version of the VS Code extension API, on Windows, macOS and Linux.
Installing
Install Pendant from the Visual Studio Marketplace, or from Open VSX for VSCodium and other editors that use it. Every release is also on the releases page as a file you can install by hand.
Where it lives
Pendant adds a sidebar with the chat and your session list. The list can sit on either side of the chat, or be hidden. You can move Pendant's view to another part of the window, the same way you move any view in your editor.
Settings
Pendant's settings live in your editor's settings, and Pendant's own settings screen shows the same values. When a folder is open, a change you make in Pendant applies to that workspace. Otherwise it applies to your user settings. A few settings belong to the computer rather than a workspace, for example remote access, and always stay there.
You can have Pendant open the session you used last when it starts, instead of the start page. Status values that extensions publish can appear in the editor's status bar, in Pendant's own panel, or both. See Settings for everything else you can adjust.
Chats in editor tabs
A chat can also live in an editor tab, next to your files. Each tab holds its own session, independent of the sidebar, so you can keep several conversations open at once.
To open a session in a tab, drag it from the session list into the editor area. It opens in the editor group where you drop it. You can also open a session in a tab from its menu, move the sidebar's current chat into a tab, or start a new chat beside the file you are editing. That last one attaches the file to the new chat.
Chat tabs behave like any other tab. You can move them between editor groups, and they come back after the window reloads.
From your editor
- Send a reference to the lines you selected from the editor's context menu. See Context and what the agent sees.
- Add files to the chat from the file explorer.
- Write a commit message with a button in the Source Control view. Reviewing changes and Git explains what it sends.
- If you use the GitHub Pull Requests extension, Pendant can write the description in its view for creating a pull request.
The first run
If you already use Pi, there is usually nothing to set up. Pendant picks up your configuration, providers and sessions. If you don't, Pendant offers to connect a provider before you start chatting. When you connect one through Pendant, it is stored in Pi's own configuration folder, where Pi in a terminal would find it too.
Remote development
When your editor is connected to another machine, for example over SSH, in WSL or in a container, Pendant runs on that machine. It then uses that machine's Pi configuration and sessions, and the agent works on that machine's files. Voice dictation runs there too, so it records from that machine's microphone. See Voice dictation. Hooks also run on that machine. See Hooks.
Signing in to an MCP server works a little differently there. Your browser runs on your own computer, but the sign-in has to finish on the other machine. When you approve access, the browser may show an error page. That is expected. Copy the full address from the browser's address bar and paste it into the sign-in screen in Pendant. Pendant then finishes the sign-in.
Browser-based editors
Pendant needs a desktop editor. It does not run in browser-based versions of VS Code.
Inspecting the extension
The installed extension is an ordinary folder inside your editor's extensions folder, named after the extension and its version. Every release is also on the releases page as a file you can download. That file is a zip archive, so your AI agent can unpack it and read it. Where to go next explains what you may do with it.
18
Pendant for Desktop
Pendant for Desktop is a standalone app for macOS, Windows and Linux. It is still in development.
It is not an agent workbench that hides your code. Like Pendant in VS Code, it keeps your code in the center and the chat beside it, and it works much the same way. What sets it apart is setup. You download the app, open a folder and start. There is no editor to install first and no extension to configure.
Because Pendant owns the whole app, it can also tie Pendant and Pi together much more closely. The desktop app includes features that a VS Code extension cannot offer, or that would be too hard to design within an editor built by someone else.
What comes with it
- A full code editor with a file explorer and tabs, where you also review the agent's changes as diffs.
- Code navigation powered by language servers. Hover for details, jump to a definition, and find references and symbols. Servers for common languages are built in. For others, Pendant finds the ones already on your computer or helps you install them.
- A Git view to review, stage and commit your changes, to switch branches, and to fetch and sync with your remote.
- A terminal, and a place to run your project's recurring tasks in the background.
- Several folders side by side, each in its own tab.
- The same chat, sessions, context, extensions and voice input as Pendant in VS Code.
Finding your way
The command palette reaches more than commands, files and symbols. From it you can switch to another session, pick a model or set the thinking level for the chat. It also lists every keyboard shortcut, including the chat's own.
A folder's tab shows when a session in it is working, even while you look at another folder. So does a session open in its own tab.
The file explorer follows the file you are editing. It opens the folders above the file and scrolls to it, without taking focus or changing your selection of several files. You can turn this off.
Reviewing changes
A diff shows the old and new text side by side, or in a single column. You choose the layout in the settings, and open diffs change right away.
The terminal
- Links. Addresses in the output can be opened from the terminal.
- Names. A terminal takes the title your shell sets, and you can give it a name of your own.
- Panes. A terminal can be split into several panes, side by side or stacked. Each pane runs its own shell.
- Your directory. When your shell reports the directory it is in, the terminal remembers it. After a restart, the shell starts there again, and a new terminal opens where you were. Many shells don't report it unless you set that up, for example with the OSC 7 sequence.
- History. The app can keep each terminal's recent output and show it again after a restart. This is off until you turn it on. Output can contain secrets, so it stays on your computer.
Trusting a folder
A folder can bring its own Pi setup, for example extensions, skills, prompt templates, settings or MCP servers. That setup can run code on your computer. So when a folder has one and you haven't decided yet, the app asks whether you trust it before Pi uses it.
You can trust the folder, its parent folder, or only this session. If you don't trust it, Pi still works there with your own setup. Pi in a terminal keeps its answers in the same place, so a folder you trusted there is trusted here too. You can change your answer later, and Pi restarts for that folder.
What it leaves to other tools
The Git view covers everyday work. You can switch to any branch that already exists on your computer. Click the branch name, search the list and pick one. Pendant asks you to confirm first. If Git cannot switch, Pendant shows its message. Creating branches, merging and rebasing stay with Git itself.
Updates
The app checks for its own updates and downloads them in the background. You can have it only tell you about a new release, or never check.
Alongside Pendant in VS Code
The desktop app does not replace Pendant in VS Code. Both will stay, side by side. If you like working in VS Code, the extension remains a first-class way to use Pendant.
19
Privacy and your data
Pendant runs on your computer, and so does the Pi inside it. What leaves your computer depends almost entirely on the model provider you choose.
Model traffic
When the agent works, Pi sends your prompts and the context they need directly to your model provider, using the credentials you gave Pi. Some optional features, for example generated session names or prompt suggestions, send the text they need to the same provider. With a local model, your prompts and code never leave your computer. Pi may still look up its model list online unless you turn on the offline setting.
What Pendant itself connects to
Pendant has no server of its own. It sends no telemetry, no crash reports, and no prompts or code to anyone but your model provider. Beyond that, it connects to the internet only for conveniences you can do without. For example, it downloads a voice model the first time you dictate, and it lets you browse and update Pi packages. An offline setting switches these off. Dictation itself runs on your computer, so your audio never leaves it. See Voice dictation.
Remote access
Remote access, which is still in internal development, lets a phone you pair follow and steer your sessions. The phone connects directly to your computer, encrypted end to end, and nothing passes through a Pendant server. See Remote access.
What Pendant writes
Pendant never edits Pi's session files by hand. It never rewrites, reorders or removes what a session already holds. Everything that goes into a session is added through Pi, in Pi's own format, so Pi in a terminal can always open it.
- Your conversation. Your messages and the agent's work are written by Pi, as they would be in a terminal.
- Your actions in Pendant. A few things you do in Pendant are saved in the session too. Renaming a session and editing or omitting what the model sees are Pi's own kinds of entries. The chat's tool set and the skills you switched off are Pendant's own entries. Pi keeps them the way it keeps data from extensions and never shows them to the model.
- Deleting. The only time Pendant removes a session file is when you delete that session.
Some choices change Pi's own settings, exactly as they would in a terminal. These are connecting a provider or adding a custom one, switching packages or parts of them on or off, automatic compaction, and cache warming. Picking a model or thinking level in a chat does not. It changes only that session. Models, tools and prompts says more.
Stored on your computer
Your sessions and Pi configuration stay where Pi keeps them. Pendant keeps a few things of its own on your computer, for example your preferences, prompt history and any voice model you download.
The Privacy Policy describes in detail what Pendant stores and which connections it can make.
20
Where to go next
Pi question or Pendant question?
If something is about how the agent behaves, which providers and models exist, or how to write an extension or a skill, it is a Pi question, and Pi's documentation is the place to look. How an extension behaves in Pendant is covered in Pi extensions in Pendant. If it is about the interface or anything else in this guide, it is a Pendant question.
When something is broken
Pendant can export a session as a bug report. Personal details are redacted, and you decide whether diagnostic logs go with it. Attach the report to an issue.
For a hook, its log shows what ran and what it printed. See Hooks. For a generated commit message, you can open the exact prompt and reply. See Reviewing changes and Git.
Ask your own agent
You are welcome to point your own AI agent at Pendant's files, to find out how Pendant works and what goes wrong. Pendant is not open source. But its licence explicitly allows you to inspect, unpack, decompile and deobfuscate your copy for this, and to quote what you find in a bug report. You may not redistribute Pendant or copy its code into other software. The EULA has the details.
A few things help the agent.
- Describe the symptom and the steps that lead to it. Ask the agent to trace the behaviour in Pendant's code and in your Pi configuration before it guesses.
- Give it this guide too. The guide page offers all chapters as Markdown in one download, so the agent can compare what it finds with what Pendant is meant to do.
- The published code is minified, so its names are short. An agent can still follow it. It often helps to have it search for a message you saw on screen.
Pendant in VS Code says where to find the extension's files.
Pendant questions
- Read the frequently asked questions on the home page.
- Look through the issue tracker for known problems and feature requests, or report a new one.
- Ask questions and share ideas in discussions.