@sassywares/pi-extensions
A zesty power pack for pi: auto-titled sessions, real web access, clean handoffs, isolated subagents, and ask/work modes.
Package details
Install @sassywares/pi-extensions from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@sassywares/pi-extensions- Package
@sassywares/pi-extensions- Version
0.4.2- Published
- Oct 27, 2026
- Downloads
- 2,731/mo · 1,038/wk
- Author
- thekayshawn
- License
- MIT
- Types
- extension
- Size
- 121.8 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions/auto-title.ts",
"./extensions/web-search.ts",
"./extensions/handoff.ts",
"./extensions/subagent/index.ts",
"./extensions/modes/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Sassy Pi Extensions
A zesty power pack for pi — auto-titled sessions, real web access, clean handoffs, and isolated subagents. Install once, ship faster.
Sassy Pi Extensions is the Sassywares pi suite. It names each session from its first real turn, gives the agent web_search and web_fetch tools backed by Exa, carries context into a fresh session with /handoff, and runs bounded work in isolated subagents.
The package is @sassywares/pi-extensions; the repo is sassy-pi-extensions. The subagent tool ships here; the roster it dispatches — scout, worker, reviewer, validator — lives in sassy-agents.
Contents
- Why this suite
- Quick start
- Extensions
- Configuration
- Security
- Provenance
- Repository layout
- Development
- Releasing
- Model profiles
- Repository maintenance
Why this suite
- Zero config where it matters. Exa runs through its public MCP endpoint; titles use the model you are already talking to;
/handoffuses the current session's model. - Native surfaces. Titles use pi's own session name, so they show up in the session picker, footer, and terminal title. Search, fetch, and subagents are ordinary tools the model can call.
- Safe by default.
web_fetchblocks localhost, private network addresses, and URLs with embedded credentials. Subagents run in isolated pi processes with the tools their agent file allows. - Opt out, not opt in. Auto-titling stops with one environment variable. Manual titles always win.
- Composable with
sassy-skills.sassy-cycle,sassy-debug, andsassy-handoffare built around thesubagentand/handoffextensions. - Small. No background servers. Host-provided dependencies only.
Quick start
Install the package:
pi install npm:@sassywares/pi-extensions@0.4.2
Or pin a ref from the repo:
pi install git:github.com/sassywares/sassy-pi-extensions@v0.4.2
Or load a checkout in place:
git clone https://github.com/sassywares/sassy-pi-extensions.git
pi install ./sassy-pi-extensions
The subagent tool ships with no agents. Install the roster from sassy-agents:
git clone https://github.com/sassywares/sassy-agents.git
./sassy-agents/scripts/install.sh
Reload pi (/reload) and confirm:
web_searchandweb_fetchare available to the model.- The session gets a title after the first assistant turn, and
/titleis registered. /handoffis registered.- The
subagenttool lists the Sassy agents.
To try the package without changing settings:
pi -e ./sassy-pi-extensions
Extensions
| Extension | Surface | What it does |
|---|---|---|
auto-title |
automatic + /title [text] |
Generates a short session title after the first meaningful turn. /title regenerates it; /title <text> sets one manually. |
web-search |
web_search, web_fetch |
Searches the web via Exa and fetches readable page text with local-address protection. |
handoff |
/handoff <goal> |
Summarizes the session into a continuation prompt and opens a fresh session with it in the editor for you to submit. |
subagent |
subagent tool |
Runs single, parallel, or chained tasks in isolated pi processes and returns compact results. |
modes |
alt+m, /ask, /work |
Ask mode removes the write tools and runs bash under an OS-level read-only sandbox. Work mode is pi as usual. |
Auto-title
Runs after the first assistant turn that contains text. It builds a small transcript from the opening turns, asks the current model for a 2–6 word title, and writes it with pi.setSessionName().
- Tries once per session. Use
/titleto retry after a provider failure. - Never overwrites a title you set manually.
- Does not delay the turn; the title arrives when it is ready.
- 15 second timeout, 32 output tokens, low temperature.
- Titles are truncated to 60 characters.
Web search and fetch
web_search uses Exa's public MCP endpoint by default. Set EXA_API_KEY to use the direct Exa API instead (higher limits and your own quota).
web_fetch:
- Accepts absolute
http(s)URLs only. - Follows up to 5 redirects.
- Blocks localhost, private/link-local addresses, and URLs with embedded credentials.
- Accepts HTML and plain text, capped at 2 MB.
- Screens the final URL after redirects, not just the first one.
- Returns readable text, truncated to 50,000 characters (12,000 by default).
Handoff
/handoff <goal> generates a focused prompt for a new session, lets you edit it, then opens that session with the prompt seeded in the editor.
The extension uses the current session's model and the current conversation only. The new session cannot see the old one.
Subagent
The subagent tool spawns a separate pi process per invocation, so each task gets its own context window. Three modes:
- single —
{ agent, task } - parallel —
{ tasks: [{ agent, task }, …] } - chain —
{ chain: [{ agent, task }, …] }, with{previous}replaced by the prior result
Agent definitions are Markdown files under ~/.pi/agent/agents/ and the nearest project .pi/agents/. Project agents override user agents with the same name. This package supplies the tool and its parsing; the roster itself lives in sassy-agents, which documents each agent's role, tools, and models, and links them into place with scripts/install.sh.
Every agent declares a profiles: list, so one roster runs on more than one provider. A dispatch picks a profile — a named model choice — and the child process starts on that profile's model.
Pick a profile three ways, highest precedence first:
The tool call:
{ agent: "worker", task: "…", profile: "anthropic" }. Individualtasks[]andchain[]entries acceptprofiletoo.An environment variable:
PI_SASSY_PROFILE=anthropic.A project setting in
.pi/settings.json, which is where a project opts in once for everyone:{ "sassy": { "profile": "anthropic" } }Project settings override
~/.pi/agent/settings.json; with neither set, the default profile isdeepseek.
A profile an agent does not define is an error naming the ones it does — it never falls back to the default provider. The run footer shows the model and, in parentheses, the profile it came from.
Every subagent reads the same grounding brief instead of re-exploring the repo. See the roster's GROUNDING.md for its location, freshness check, and wait rule.
Parallel dispatch defaults to two concurrent subagents. A three-worker MiniMax M3.1 Flash batch showed stream stalls and request timeouts under load; DeepSeek Flash was fine with four. Raise the limit with PI_SUBAGENT_CONCURRENCY for a DeepSeek-only dispatch.
A watchdog kills a subagent when it emits no output for two minutes, which catches provider streams that hang after a partial response. Completed runs append one JSON line to ~/.pi/agent/subagent-runs.jsonl with model, profile, thinking level, wall time, model time, tool time, turns, error turns, retries, token usage, cost, and the watchdog outcome. Use it to compare models instead of reconstructing runs from session files.
Modes
Two modes, toggled with alt+m or /ask and /work. Ask mode makes it safe to think out loud; work mode is pi as usual.
Ask mode enforces read-only access on two levels:
- Toolset.
edit,write,codemode,subagent, and any other tool that can change files or run arbitrary code are removed from the model's active toolset, so it never sees them. Atool_callgate blocks them anyway, which also covers nested calls made by tools that stay active. - Sandboxed bash.
read,grep,find,ls,bash,web_search, andweb_fetchstay available, because searching and reading is how the model answers honestly. Every bash command, including!input, is wrapped in an OS-level sandbox that denies filesystem writes:- macOS:
sandbox-execwith adeny file-write*profile. - Linux:
bwrapwith--ro-bind / /.
- macOS:
Writes do not fail quietly and they are not matched by pattern. rm, sed -i, tee, > redirection, git checkout, and a nested bash -c all fail at the OS level with Operation not permitted, while git log, git diff, rg, and network calls keep working. /dev and the temp directory stay writable so ordinary tools behave.
A backend is only trusted after it proves both halves of the contract: reads inside the working directory must succeed and a probe write there must fail. A backend that is installed but cannot enforce (for example bubblewrap on a distribution that blocks unprivileged user namespaces) is discarded, and ask mode continues without bash rather than falling back to a command allowlist. The working directory always wins over the temp allowance: if the project itself lives under the temp directory, temp writes are denied too and /modes reports temp dir is read-only.
/modes shows the verdict. The mode badge sits on a footer line of its own, aligned to the right edge: ▶ work, ⏸ ask · ro(sandbox-exec), or ⏸ ask · bash off. To place it there without reimplementing pi's footer, the extension delegates to pi's own FooterComponent and appends the badge line, so directory, session name, token stats, model, and context usage stay exactly as pi renders them. If a future pi release changes what that component reads, the extension restores the native footer and falls back to a left-aligned status line. The shim reads the model and thinking level through getters, so /model and /thinking changes appear in the footer immediately instead of staying frozen at the values the session started with.
--askstarts a session in ask mode; the mode persists across resume.- Switching mid-session applies immediately: the toolset is swapped and bash starts or stops being sandboxed. When ask mode is switched off, the model is told once that work mode is active, so it stops repeating an earlier claim that it was in ask mode. Only the newest mode notice is kept in context, and the ask prompt is refreshed on every turn, so a long session does not accumulate stale instructions.
shift+tabis not used, because pi binds it to the thinking level. To match an editor that uses it for modes, bindapp.thinking.cycleto something else in~/.pi/agent/keybindings.jsonand add a shortcut there.- Ask mode is a guardrail for local changes, not a jail. Network access stays open, so a command can still change remote state if it holds a credential. Use pi's containerized setup when the boundary has to hold against a hostile model.
- Windows is unsupported by design. Ask mode needs an OS-level read-only sandbox that Windows does not offer cheaply, so the extension registers nothing there.
/askand/workdo not exist, and every tool behaves as before.
Configuration
| Variable | Default | Effect |
|---|---|---|
PI_AUTO_TITLE |
unset | Set to 0 to disable automatic titles. /title still works. |
EXA_API_KEY |
unset | Use the direct Exa API instead of the public Exa MCP endpoint. |
PI_MODES_BASH |
auto |
Set to off to skip backend detection. Ask mode then runs without bash. |
PI_MODES_ASK_TOOLS |
unset | Comma-separated extra tool names to allow in ask mode. |
PI_SASSY_PROFILE |
deepseek |
Profile subagents run: the model each agent declares under that name. Overridden by the profile parameter, overrides sassy.profile in settings. |
PI_SUBAGENT_CONCURRENCY |
2 |
Maximum parallel subagent processes. DeepSeek Flash handled 4; MiniMax M3.1 Flash stalled with 3. |
PI_SUBAGENT_IDLE_TIMEOUT_MS |
120000 |
Kill a subagent after this many milliseconds with no stdout/stderr events. Set to 0 to disable. |
PI_SUBAGENT_TELEMETRY |
~/.pi/agent/subagent-runs.jsonl |
Telemetry JSONL path, or 0/off to disable. |
The subagent profile can also be set in settings, which is how a project selects a provider for everyone working in it:
// <project>/.pi/settings.json
{ "sassy": { "profile": "anthropic" } }
Precedence: profile parameter, PI_SASSY_PROFILE, project settings, user settings, then deepseek.
Security
Extensions run inside the pi process with the same OS permissions as pi. Review extension source before installing it.
web_fetchis designed for public web pages, not for reaching services on your machine or local network.auto-titlesends a short transcript of the opening turns to the model already selected for the session. Provider terms and data handling apply./handoffsends the conversation and your goal to the current model to generate the continuation prompt.subagentstarts additional pi processes. Their permissions match the tools allowed by their agent file and the pi process that spawned them.modesask mode denies local filesystem writes through the OS sandbox described above. It does not restrict network access, so a sandboxed command can still change remote state if it can authenticate. Work mode has no restrictions.- No credentials are stored by this package.
EXA_API_KEYis read from the environment at call time.
Provenance
handoff and subagent are derived from pi's MIT-licensed examples and documentation, modified for this workflow. See NOTICE.
Repository layout
sassy-pi-extensions/
├── extensions/
│ ├── auto-title.ts
│ ├── handoff.ts
│ ├── web-search.ts
│ ├── modes/
│ │ ├── backends.ts
│ │ └── index.ts
│ └── subagent/
│ ├── agents.ts
│ └── index.ts
├── .github/workflows/
│ └── publish.yml
├── AGENTS.md
├── CONCEPTS.md
├── LICENSE
├── NOTICE
├── README.md
└── package.json
Development
This repo is the source of truth for the extensions. Install it locally so pi loads the working tree directly:
pi install ~/Documents/Workspace/Sassywares/sassy-pi-extensions
Local installs are read in place — no copy, no build step. After changing an extension, run /reload in pi.
Agent definitions live in sassy-agents; edit them there and link the roster with its scripts/install.sh.
Do not also keep copies under ~/.pi/agent/extensions/; pi would load both and register duplicate tools and commands.
Test the package for one invocation without touching settings:
pi -e ~/Documents/Workspace/Sassywares/sassy-pi-extensions
Manual checks after a change:
web_searchwith and withoutEXA_API_KEY.web_fetchon a normal page, and onhttp://localhost:3000(must be rejected)./title,/title <text>, and automatic naming withPI_AUTO_TITLEunset.PI_AUTO_TITLE=0starts a session without a generated title./handoff <goal>opens an editable prompt.subagentwith a single agent, and a chain using{previous}. The roster comes fromsassy-agents; its ownscripts/test-profiles.mjscovers agent parsing and profile resolution.
Releasing
Publishing uses npm trusted publishing (OIDC). There is no long-lived npm token and no NPM_TOKEN secret.
A brand-new package cannot publish its first version through OIDC, because npm's trusted-publisher settings only exist after the package exists. Bootstrap once:
npm login, thennpm publish --access publicfrom a clean checkout.- On npmjs.com, open the package → Settings → Trusted publishing → GitHub Actions and set:
- organization:
sassywares - repository:
sassy-pi-extensions - workflow filename:
publish.yml - environment: blank
- allowed actions: enable npm publish (do not leave it stage-only)
- organization:
- Bump
versioninpackage.json, commit, then tag and push. The workflow publishes with OIDC and provenance.
git tag -a v0.3.1 -m v0.3.1
git push origin v0.3.1
The workflow skips publishing if the tag's version is already on npm. A new trusted publisher must complete its first OIDC publish within 2 days of configuration, or npm expires it.
Model profiles
Profiles are declared by agents, not by this package. Each roster file in sassy-agents lists one entry per provider, and the subagent extension resolves the selection at dispatch time. Provider choice never lives in git history, so there is no second branch to sync and no provider-specific release.
The default is deepseek. A profile an agent does not define is an error naming the ones it does — never a silent fallback. Add or change a profile in the roster repo, then run its scripts/test-profiles.mjs, which exercises this extension's parser and resolution order against the real roster plus malformed fixtures.
Prereleases still get their own channel: the workflow derives the npm dist-tag from the version, so 0.4.0-beta.1 publishes under beta and never becomes latest.
Repository maintenance
- Commit changes under
extensions/, and updateREADME.mdwhen behavior or configuration changes. - Tag releases (
v0.3.0,v0.3.1, …,v0.4.2) so installs can pin a ref. See Releasing and Model profiles. - Bump
versioninpackage.jsonto match the tag before pushing it. AGENTS.mddocuments the workflow for agents working on this repo.CONCEPTS.mdholds the vocabulary.