An AI agent that lives on your Mac, and keeps your data there.
brew install pidster/tap/wisp
wisp "What is listening on port 8080?"
wisp chatIt runs on Apple's on-device model out of the box. For a larger window and stronger tool use, add a model of your own: How to install local models.
- Local AI only. Apple's on-device model, or yours in Ollama, MLX, llama.cpp, or LM Studio. No cloud, no key, no account.
- Nothing runs unchecked. A sandbox, a deny list, a sub-millisecond risk classifier, and you.
- Trained on your data. Teach the risk classifier from your own command history.
- Redact before you share. Secrets and personal data stripped from a file, on the Mac.
- Watches while you work. Reruns your tests on save; pings you when they break.
- Your coding agent's local helper. A 14,000-line log comes back as its top 30 messages.
- Shows its work. Every reply says what actually ran and flags any result no call produced; every action is logged.
wisp redact crash.log | pbcopy # secrets and personal data replaced
git diff --cached | wisp scan # block a commit that leaks a key
wisp watch 'swift test 2>&1' # a notification when tests start or stop failing
wisp classifier train --from-audit --use # a risk classifier trained on this Macwisp is a small AI agent that runs on your Mac and stays there. It drives Apple's on-device
Foundation Model, the one behind Apple Intelligence, or a model you run locally, and gives it tools: it
can run a command, read and edit a file, report on the machine, send you a notification, and, when you
turn them on, recall what its context no longer holds, ask you to pull or enable a model, and ask you a
question when it needs your answer to go on. You use it from the terminal, as a command or a
chat, and your coding agent uses it as an MCP server, handing it the local chores that would otherwise
fill the agent's context: run the tests and return the failures, condense a log, summarise a diff, scan
a commit for secrets. Every command the model runs passes a policy, a sandbox, a risk classifier, and,
when it matters, you, and every step is written to an audit log you can read back. When the model working for
your coding agent needs your approval or your answer, you give it in wisp chat, wisp-tui, or any terminal,
never through the agent. Beside each reply wisp
shows what the turn ran and flags any result the reply reports that no call produced.
You and your coding agent reach the same session, and a command the model asks for passes the gate before it touches your Mac:
The model never carries the whole conversation. The audit log keeps everything, and wisp composes each request from it, most stable first, while you see every tool's full output whatever the model carries (context-management.md, ADR 0045):
Ask it something, or hand it a task:
wisp "What is the date in Tokyo?"
wisp "What is listening on port 8080, and how much disk is free?"
wisp --yes "Run the tests in $PWD and tell me if they pass"
wisp chat # a conversation; /help lists the commandsRunning tests changes state, so it counts as a moderate action: chat asks you before doing it, and
plain wisp "…" cannot ask, so it refuses unless you pass --yes. In chat you answer each request
once, for the session, for this project, or always, and /model switches models mid-conversation. A
long conversation keeps short facts (a codename, the task, whether the tests pass) and a summary of the
turns it had to drop; /inspect facts and /inspect summary show them and /fact corrects one. A line
that starts with ! (! git status) runs the command yourself, in the sandbox and without asking, and the
model is told what you ran on its next request. On a
terminal the chat runs in wisp-tui, installed beside wisp: the conversation scrolls in your
terminal's own history above a pinned input and status line, with the model's tool calls shown as they
happen.
Some subcommands need no prompt at all. They put the model, or plain rules, to one job each:
wisp watch 'swift test 2>&1' # rerun on every save; a notification when it starts or stops failing
git diff --cached | wisp scan # credentials in a commit, before it is made; exits 1 on a finding
wisp redact crash.log | pbcopy # secrets and personal data replaced by markers, ready to paste
wisp draft > /tmp/msg && $EDITOR /tmp/msg && git commit -F /tmp/msg # a commit message from the staged diff
make test; wisp notify "Tests finished" --sound # a macOS notification
wisp logs --last 20 # what just happened, from the audit log
wisp approvals # what it has been told to remember; revoke or clear herewisp --help lists every subcommand; wisp.md is the reference.
Register wisp as an MCP server and Claude Code, Codex, or any other MCP client can delegate work to it.
For Claude Code, in .mcp.json:
{
"mcpServers": {
"wisp": {
"command": "/opt/homebrew/bin/wisp",
"args": ["mcp"]
}
}
}The agent then has respond, which runs a task on the local model with wisp's tools and returns the
reply with a receipt of what it did; ten condensing tools that read something large on your Mac and
return something small; and two that manage respond's threads:
| Tool | Gives back |
|---|---|
respond |
The model's reply to a task, on a named thread you can continue; with a JSON Schema, a reply of that shape |
triage |
Only the failures from a build or test run, read exactly from known formats or judged by the model |
summarise_diff |
A diff as one line per file, with review flags: a deleted test, a credential, a binary |
draft_change |
A commit message, PR description, or changelog line from a diff |
scan_secrets |
Where credentials, and optionally personal data, occur, with the values masked |
redact |
The text with them replaced by numbered markers |
condense_log |
A log as its distinct messages, ranked by severity; a crash report as what explains it |
json_shape |
A JSON document's structure without its data |
dependency_audit |
An npm, cargo, or pip audit as what needs action, most severe first |
flaky_tests |
Tests that pass in some runs and fail in others |
hot_paths |
A profile's folded stacks as where the time goes |
set_fact_scope |
Moves a fact of a respond thread to the thread or the session, or asks you to keep it as a permanent fact |
close_thread |
Frees a respond thread |
A failing swift test run comes back as a headline and a few file:line: message findings; two
minutes of the unified log, 14,333 lines, came back as its top thirty message templates in about 10 KB. The raw output never
leaves the Mac and never enters the calling agent's context. When the model inside respond wants to
run a risky command, you are asked through your client's dialog and, at the same time, by a notification
naming wisp approvals approve <id>, which answers it from any terminal (a running wisp-tui shows it as
its own dialog, and plain wisp chat answers it with /approvals approve <id>); the first answer wins. A client with no dialog, such as the Claude mobile app, is approved
that way; a request nobody answers is refused, never run silently. A fact the agent asks you to keep across
sessions reaches you the same way, and you answer with wisp facts keep <id> or drop <id>; the agent can
never keep one itself. When the model has the ask tool and asks you a question, the question waits for
wisp questions answer <id>, wisp-tui, or chat's /questions answer <id>, and the turn picks up your
answer. mcp.md has each tool's arguments and result.
Nothing leaves your machine. The default model runs on your Apple silicon, so prompts, files, and
command output stay local, and there is no API key, no account, and no bill. Apple's Private Cloud
Compute is an explicit opt-in (--model private-cloud), noted on stderr and in the audit log; it needs
an entitlement that an unsigned command-line binary cannot carry, so it is refused from this build
(backends.md). Any model a local Ollama serves can be chosen with --model ollama:<name> when a task needs a larger window than the on-device model's 8k tokens, and a model in MLX
layout with --model mlx:<name>, run in wisp's own process on the GPU; a model a local llama.cpp
llama-server or LM Studio serves is llamacpp:<name> or lmstudio:<name>. wisp models lists what
will work, wisp models pull fetches an MLX model into the Hugging Face cache after asking, and wisp models disable hides one you do not want offered.
Every command passes a gate, and the gate is fast. Before a command runs it must clear a deny list
(sudo, rm -rf /, piping into a shell, disk tools, starting a wisp of its own), and it runs under a Seatbelt sandbox that
confines writes to the directory wisp was launched in, the temporary directory, and configured build
caches. Then a classifier rates each simple command in the line safe, moderate, or dangerous.
Rules set the floor and a Core ML text classifier, shipped with each release and trained on 2,135
labelled commands, 1,075 of them real, can only raise the level; it answers in under a millisecond
however busy the Mac is. On 996 real commands it rated 817 exactly, against 664 for the on-device
language model at 2.5 seconds a verdict (approval.md). A command the rules know
to be read-only, such as git status, never reaches a classifier at all. You can train a version on
your own history with wisp classifier train --from-audit.
You decide, at the level you choose. At moderate and above wisp asks. Your answer has a scope,
this turn, this session, this project for 30 days, or always for 30 days, and is remembered by the
program and its verb, so approving git commit never approves git push. A dangerous verdict is
never remembered beyond the session, so a stored rm approval never covers rm -rf build. A dialog
nobody answers is a refusal. wisp approvals shows what is remembered; revoke and clear forget it;
pending lists the commands an MCP client's session is waiting on, and approve or deny answers one.
You can see exactly what happened. ~/.wisp/logs/audit.jsonl records every prompt, reply, tool
call, policy decision, classifier verdict, approval, and command outcome, verbatim, in order. wisp logs reads it, and respond returns each turn's receipt folded from the same events, with the results the
reply reports that no call produced (verification.md), so a calling agent can check
delegated work without reading the log. trust.md states in one page
what wisp can and cannot do to your Mac.
It is a microharness. One binary, one session, a registry of ten tools, and the smallest correct
agent loop; the framework runs the loop and wisp puts the care around it. Your own tools are command
templates in ~/.wisp/config.json, run through the same gate as everything else
(tools/custom.md). State is a directory, ~/.wisp, that you can read, edit,
and delete.
| Area | Feature | Read |
|---|---|---|
| Models | The on-device model by default; Ollama, MLX, Core AI, llama.cpp, and LM Studio models by name; Private Cloud Compute as an explicit opt-in | install-local-models.md, backends.md, ADR 0058 |
| A context window sized from the Mac's free memory for each Ollama and MLX model, or set for one model; a llama.cpp or LM Studio model's is the server's | wisp.md, ADR 0043 | |
wisp models: every model this Mac can run as one table; enable or disable a model, and have its capabilities checked when you enable it |
wisp.md, ADR 0056 | |
Models in chat: /models pull, trust, and cancel beside enable, disable, and check; the model's own models tool (off by default), whose every change asks you |
tools/models.md, ADR 0059 | |
wisp models pull: an MLX model fetched into, or reused from, the Hugging Face cache, after asking; an interrupted download resumes |
backends.md, ADR 0052 | |
A reasoning model's thinking shown while it thinks, on Ollama and MLX, kept for you and never sent back to the model; ollama.think and mlx.think, or per model; wisp's own side calls do not think |
ADR 0053, backends.md | |
| Which local model suits which work, as measured: the local-model and Falcon comparisons | measurements.md | |
| Safety | The policy's deny and allow lists, the Seatbelt sandbox, and what a refused write is reported as | tools/run_command.md, ADR 0054 |
| Risk classifiers: rules, the shipped Core ML classifier, or the on-device model | approval.md | |
| Custom classifiers: train one on your own audit log or labelled commands, measure it, and switch to it | wisp.md, approval.md | |
| Approvals with a scope (turn, session, project, always), and answering an MCP session's request from another terminal or from chat | approval.md | |
| One page on what wisp can and cannot do to your Mac, and how to undo it | trust.md | |
| Conversation | wisp chat and the wisp-tui front end; ! command to run something yourself; save and resume a conversation |
wisp.md, ADR 0049 |
One tool-output budget from the model's window, with what does not fit kept whole for the model to page, filter, summarise, or question, and for you to read whole (/show, /last, wisp-tui's Ctrl-O) |
context-management.md, ADR 0050 | |
| Ctrl-C stops a model's turn and the command it started, and returns to the prompt | wisp.md | |
The model asks you a question mid-turn with its ask tool (off by default), in chat, wisp-tui, or from an MCP session through wisp questions; never for a secret |
tools/ask.md, ADR 0062 | |
Facts, the running summary, references to earlier output, and the model's memory tool (off by default) |
context-management.md, tools/memory.md | |
| Your own instructions on top of wisp's system prompt, per conversation or for every one | ADR 0017 | |
Settings changed from chat (/config set) or the command line (wisp config set) |
wisp.md | |
| Seeing what happened | The audit log and wisp logs; wisp doctor |
logging.md, wisp.md |
ran: beside every reply, and references to entries that do not exist flagged |
ADR 0051, ADR 0055 | |
The verification pass: a result the reply reports that no call in the turn produced (a command, an exit status, a denial, quoted output) shown as unsupported:, and returned to an MCP caller |
verification.md, ADR 0060 | |
What the model carried on any turn, without spending its context (/inspect context) |
context-management.md | |
| Everyday jobs | wisp watch, wisp scan, wisp redact, wisp draft, and wisp notify |
wisp.md |
| Notifications from your terminal or its app, with what routes each takes | tools/notify.md, ADR 0044 | |
| Extending | Custom tools: a command template in config.json, run through the same gate |
tools/custom.md |
| wisp as an MCP server for Claude Code, Codex, or any MCP client | mcp.md | |
A front end of your own over wisp chat --json |
wisp.md, ADR 0029 |
An Apple silicon Mac on macOS 27 or later with Apple Intelligence enabled, and Homebrew.
brew install pidster/tap/wisp # installs wisp, wisp-tui, and MLX's Metal library
wisp doctor # checks the model, MLX, sandbox, classifier, config, home, notifications, pending requests, and the output store
wisp "What is the date in Tokyo?"That is all wisp needs: it runs on Apple's on-device model. To add a local model through Ollama, MLX, llama.cpp, LM Studio, or Core AI, and to choose which, follow How to install local models.
Homebrew also installs shell completions for zsh, bash, and fish; with any other install, wisp completions install
writes them for your shell (wisp.md, "wisp completions").
State lives in ~/.wisp: an optional config.json (change it with wisp config set or /config set
in chat), saved transcripts, remembered approvals, the facts you keep (facts.json), the audit log, and
the classifier versions. Upgrade
with brew upgrade pidster/tap/wisp; remove with brew uninstall wisp and rm -rf ~/.wisp.
Everything is under docs/, which is also published, with guides and search, as the documentation site at wisp.pidster.com (ADR 0065). Start with the row that matches your question.
| If you want to… | Read |
|---|---|
| Understand what wisp is for and what is out of scope | objective.md |
Use the command line: subcommands, flags, config.json, exit codes |
wisp.md |
| See what the model can do and the limits on each tool | tools/ |
| Know how well each delegated task works, as measured | measurements.md |
| Install a local model and choose which one | install-local-models.md |
| Know how each model backend works: windows, capabilities, errors | backends.md |
| Connect it to another harness over MCP | mcp.md |
| Know what it can do to your Mac, what it remembers, and how to undo | trust.md |
| Know how commands are confined and when you are asked | tools/run_command.md, approval.md |
| Know what the model carries in a long conversation: facts, the summary, references, condensing | context-management.md, tools/memory.md |
| Read or query the audit log, or debug wisp itself | logging.md |
| Understand how the code is put together | design.md |
| Know why a decision was made | decisions/ (one record per decision) |
| Work on the code to the project's standard | engineering.md |
| Cut a release | release.md |
A background page records what we learned about the platform: policy-and-sandboxing.md, on what macOS and the framework offer for confinement.
You need macOS 27 and Xcode 27 (the Command Line Tools alone lack the @Generable macro plugin), and
Rust 1.98 or later for the terminal front end in tools/.
git clone git@github.com:pidster/wisp.git
cd wisp
scripts/check install-hooks # once: enables the pre-commit gate
swift build --package-path harness
harness/.build/debug/wisp tools
harness/.build/debug/wisp "What is the date in Tokyo?"
cargo build --manifest-path tools/Cargo.toml # wisp-tui, the terminal front end
WISP_BIN=harness/.build/debug/wisp tools/target/debug/wisp-tuiTo see every tool working together on your Mac, ask wisp to run the functional evaluation in
functionality-self-test.wisp. It is a step-by-step guide to wisp's tools and checks, which wisp itself
wrote during a self-test:
$ harness/.build/debug/wisp chat
› Read functionality-self-test.wisp and carry out each step, then report pass or fail for each.
Expect it to ask before commands that change anything, and to refuse sudo by policy. Some probes exit
non-zero when they find nothing: lsof on an unused port, and du or find on folders macOS will not
let them read. One step appends a line to the guide itself to exercise edit_file, and the last posts
a notification; git checkout -- functionality-self-test.wisp undoes the edit. It is a quick smoke
check, not a measurement; scripts/check eval measures the model.
| Path | What it is |
|---|---|
harness/ |
The Swift package: the wisp binary, WispCore, WispMCP, and the model backends |
harness/Evals/ |
The model evaluations, a package of their own that scripts/check eval runs |
tools/ |
The Cargo workspace: wisp-tui, the terminal front end over wisp chat --json |
docs/ |
Documentation and decision records |
training/ |
Labelled training sets for the fast classifiers, with their reviews |
scripts/check |
The quality gate: lint, warnings-as-errors build, tests, hygiene, coverage, model eval |
How we work, in short:
scripts/checkis the whole gate and the pre-commit hook runs it. Lint is strict, warnings are errors, Swift 6 strict concurrency stays on, and there are no escape hatches.- Tests never need the model. The model is exercised by running the binary, and by
scripts/check evalfor the classifier and every delegated task (measurements.md). - A change is done when it is tested, documented in code, and documented in
docs/, in the same commit. A choice that is non-obvious or hard to reverse gets a decision record. - Dogfooding:
.mcp.jsonregisters this repository's own release build (scripts/check mcp-build) as an MCP server, so Claude Code sessions here use it.
engineering.md is the full standard, and AGENTS.md the orientation given to AI agents working in this repository.