wisp documentation
These pages are also the documentation site, https://wisp.pidster.com/docs/ under the homepage, an mdBook whose table of contents is SUMMARY.md (ADR 0065).
| Document | Purpose |
|---|---|
| introduction.md | The site’s landing page: what wisp is, who it is for, the CLI and the MCP server, the safety model in five lines, and where to go next |
| guide/install.md | Guide: install with Homebrew, check with wisp doctor, a first wisp "…", a first approval, wisp chat and wisp-tui |
| guide/delegating.md | Guide: wisp as an MCP server for Claude Code and Codex: registering it, respond threads, the condensing tools, approvals that reach the person, the git thread as a worked example |
| guide/approvals-and-sandbox.md | Guide: what is asked and when, what each scope covers, wisp approvals, the sandbox’s writable roots and its refusals, outsideSandbox and !!, what the audit log shows |
| guide/local-models.md | Guide: when to add a local model, which runtime and which model as measured, using one, windows and memory |
| guide/configuration.md | Guide: wisp config and config.json, the settings worth knowing, editing by hand, the instruction layers, the environment |
| guide/troubleshooting.md | Guide: wisp doctor’s findings, restarting modelmanagerd, refused commands and the sandbox, an MCP server that does not connect, a stuck approval, the lockfile’s MLX pins |
| SUMMARY.md | The site’s table of contents, which mdBook reads; every page here is listed in it (scripts/check docs-links) |
| changelog.md | The site’s changelog page, which includes the repository’s CHANGELOG.md |
| decisions/0065-documentation-site.md | The documentation site: an mdBook over docs/ (users first, internals after), built on Linux and deployed to GitHub Pages at wisp.pidster.com by .github/workflows/docs.yml; links that leave docs/ go to GitHub; scripts/check docs-links in the gate and docs-site beside it; amended 2026-10-15: a homepage of its own at the root with the book under /docs/, and a theme in wisp-tui’s palette, held to it by scripts/check palette |
| trust.md | What wisp can do to your Mac, what leaves it, what it remembers, how to see and undo |
| objective.md | What wisp is for and what “done” looks like |
| install-local-models.md | How to install a local model (Ollama, MLX, llama.cpp, LM Studio, Core AI), choose it, and which to choose, as measured |
| wisp.md | Command reference: subcommands, flags, ~/.wisp, config.json, exit codes |
| tools/ | One page per model-facing tool (the ten built-in, memory, models, and ask among them, and custom tools): contract, result format, limits |
| measurements.md | What the eval harness found each delegated task achieves, how it is recorded, and where it is published |
| mcp.md | wisp as an MCP server: client setup, respond and its threads, the condensing tools, set_fact_scope and asking the person to keep a permanent fact, approval through the client and through wisp approvals, the thread, context, facts, and summary resources, structured output, receipts, calls, progress, errors |
| design.md | Architecture: components (the thread record and composer, facts, the session host), data flow, extension points |
| fm-cli.md | What the Apple fm command family does and does not offer, as observed |
| local-model-evaluation.md | Local-model research, candidate shortlist, and agreed workload/delegation evaluation design |
| local-model-installation.md | A dated record (2026-09-19) of the first MLX installations and smoke checks; to install a model now, see install-local-models.md |
| on-device-ai-todo.md | Living backlog for on-device AI: routing, context assembly, approval classification, local generation, audit, model controls; progress per track, what shipped since the last release, and what is planned |
| model-controls.md | Draft common controls for reasoning mode, effort, native speed mode, performance preferences, and reasoning output; marks the part ollama.think, mlx.think, and the thinking display build (ADR 0053) |
| approval.md | Risk classification (rules plus the shipped Core ML classifier by default, or the on-device model), classifier versions, approval scopes and persistence, approval under wisp mcp from wisp approvals and wisp-tui, eval results |
| logging.md | The audit log (format, kinds, wisp logs) and diagnostics (WISP_LOG, unified logging) |
| context-management.md | The small context window: the store and the composer, output handling, facts, the running summary, memory, the assessment, condensing to a target, design rules, and what was measured |
| verification.md | What wisp checks beside a reply: ran:, cited, and the verification pass, whose exact readers (and, behind verification.mode, a model step) check the results a reply reports against the turn’s complete call list |
| policy-and-sandboxing.md | Survey of tool policy and sandboxing options and which layers are implemented |
| decisions/0013-model-selection.md | Which model a session runs on (system or private-cloud), and why the default stays on device |
| decisions/0015-per-command-approval.md | Approve each simple command in a line, remembered by its program; amended 2026-10-11 (a loop or conditional judged and remembered by the commands inside it, and a line whose risk only the whole line shows asked once) |
| decisions/0014-persisted-approvals.md | Approvals have four scopes; project and always persist under ~/.wisp |
| decisions/0012-homebrew-release.md | Release through a Homebrew tap, unsigned, semver from 0.1.0; amended by ADR 0047 (MLX’s Metal library beside the binary) |
| decisions/0011-risk-classifier-and-approval.md | Classify command risk with rules plus the on-device model, and ask above a threshold; amended by ADR 0044 (approval as a host effect) and ADR 0046 (approval under wisp mcp from wisp approvals and wisp-tui) |
| decisions/0010-audit-and-diagnostic-logging.md | Verbatim JSON Lines audit log plus unified-logging diagnostics |
| decisions/0009-command-policy-and-sandbox.md | run_command is governed by a CommandPolicy and a Seatbelt sandbox; amended by ADR 0054 (the deny list refuses a nested wisp agent) and ADR 0064 (an approved run outside the sandbox) |
| decisions/0008-context-condensation.md | Recover from context overflow by condensing to recent turns |
| decisions/0007-conversation-threads.md | Conversation threads over MCP |
| decisions/0006-mcp-server-over-stdio.md | wisp is an MCP server over stdio |
| decisions/0005-tools-as-plain-binaries.md | Tools are plain binaries; the harness owns the model-facing schema |
| decisions/0004-enforced-standards.md | Standards are enforced by tooling, shared between hook and CI |
| decisions/0003-callback-streaming.md | Stream via a delta callback, not an AsyncSequence wrapper |
| decisions/0002-macos-27-baseline.md | macOS 27 is the platform baseline |
| decisions/0001-swift-and-foundationmodels.md | Implement wisp in Swift, linking FoundationModels directly |
| decisions/0030-notifications.md | Notifications through osascript with the text in argv, bounded, rate-limited, and audited, without approval |
| decisions/0031-secret-scanning-and-redaction.md | Secret scanning and redaction: rules first, masked findings and numbered markers, an opt-in model pass over rule-redacted text |
| decisions/0032-log-and-json-condensers.md | Log and JSON condensers are deterministic: templates ranked by severity, parsed crash reports, merged JSON outlines |
| decisions/0033-watch-mode.md | Watch mode reruns a command on file changes and notifies when its outcome turns, triaging new failures; amended: a file-triggered run waits for changes to settle (--settle, watch.settle) |
| decisions/0034-system-info.md | system_info answers questions about the Mac with fixed read-only probes and libproc, without the approval gate |
| decisions/0035-change-drafts.md | Change drafts are written from the diff summary, with the subject and body shape enforced in code |
| decisions/0036-custom-tools.md | Custom tools are command templates in the user’s own config, run through run_command’s gate |
| decisions/0037-routing-by-input-size.md | Route a task to a model by the size of its input, from measured size bands and a configured ladder |
| decisions/0038-fast-specialised-classifiers.md | Classifiers are tasks served by fast, specialised models, measured for speed; wisp classifier, versions with a fixed shipped default, train/dev/test sets under training/, the rules’ read-only list; amended 2026-10-12: the frozen test set grown from real use to 29 dangerous commands |
| decisions/0039-exact-condensers.md | Triage reads known failure formats exactly first; dependency_audit, flaky_tests, and hot_paths without a model |
| decisions/0040-config-from-chat.md | Change the configuration from chat or wisp config set, checked, audited, and only by a person |
| decisions/0041-shipped-classifier-is-the-default.md | The fast Core ML classifier each release ships is the default risk classifier, not the on-device model |
| decisions/0042-personal-data-classifier.md | A personal-data classifier flags lines beside the rules in scan_secrets; the trained file, not the training, ships |
| decisions/0043-context-window-from-memory.md | An Ollama model’s context window is sized from its shape and the Mac’s memory when it is selected, unless configured; refined 2026-10-04 (a model reported per layer, such as gemma4, counted layer by layer, with its draft model) and 2026-10-05 (a hybrid model sized by its attention layers plus its recurrent state); amended by ADR 0052 (MLX’s window sized the same way, Core AI’s from its bundle) |
| decisions/0044-host-effects.md | A tool asks the host (the face on the person’s screen) for effects such as approval and notifications; each face declares what it carries, and notify routes to the front end or terminal before osascript; amended by ADR 0046 (approve-mcp, approval for an MCP client through another face) ADR 0048 (permanent facts over MCP), and ADR 0062 (the model’s question, a third effect, Asker) |
| decisions/0045-layered-context.md | The model’s context is composed for each request from a store that keeps everything: instructions, an earlier block of facts and a summary, literal turns with tool output as references after their turn, and the request; condensing to a token target; memory to recall; the assessment built and off; with the phase 6 checkpoint’s figures; amended by ADR 0048 (a permanent fact an MCP caller asks for is admitted from wisp facts or wisp-tui too) |
| decisions/0046-approval-and-notifications-over-mcp.md | Under wisp mcp, a command waiting for approval is filed in ~/.wisp/pending and announced by a notification; the person answers with wisp approvals approve|deny or in wisp-tui, never through the MCP conversation; with elicitation too, both at once and the first answer wins; respond lists the notifications its turn posted; amended by ADR 0048 (the channel also carries facts to keep) and ADR 0062 (and the model’s questions); the terminal-only answers reviewed and kept on 2026-10-12; amended 2026-10-12: the banner waits approval.bannerDelaySeconds for the client’s dialog; plain wisp chat shows and answers waiting requests |
| decisions/0047-mlx-in-the-release.md | The release is built with MLX and carries its Metal library as mlx.metallib beside wisp, which MLX finds through the binary’s real path; the formula installs both in libexec; wisp doctor checks it; with the probe’s figures |
| decisions/0048-permanent-facts-over-mcp.md | An MCP caller asks for a fact to be kept as permanent with set_fact_scope; the call returns at once and the person answers through the pending channel (wisp facts keep|drop, or wisp-tui under keep-facts); a drop is remembered by the thread; a caller never moves a permanent fact; respond counts the proposals waiting; amended 2026-10-12: a request has no time limit and outlives its thread and server; amended by ADR 0062 (the channel also carries the model’s questions) |
| decisions/0049-commands-typed-in-chat.md | A line typed in chat as !command runs it as the person: the policy, sandbox, and audit apply but not the classifier or approval; the output is shown and reaches the model as a reference; wisp-tui shows command mode in a pale amber box, entered with ! and left with Backspace at the start of the line; amended 2026-10-11 (Ctrl-C during a model’s turn stops the turn and its command, audited as turn.stopped); amended by ADR 0064 (a line typed as !!command runs outside the sandbox, wisp-tui’s red outside mode) |
| decisions/0050-tool-output-budget-and-overflow.md | Every tool result bounded by one budget from the model’s window (an eighth, 4 to 64 KiB) in place of a fixed 4 KiB; the rest kept whole in the conversation’s output store, marked, and reachable through memory by page, filter, exact summary, or a condensing question; nothing past the budget dropped and no command run again to see its output; accepted 2026-10-11 with the build’s choices recorded; the person’s views (the stored whole in /show, /last, and wisp-tui’s Ctrl-O) built 2026-10-12 |
| decisions/0051-the-turns-tool-calls-beside-the-reply.md | A muted line under each reply of what the turn actually ran, counted from its audit events (tools, counts, failed, denied, declined), in chat, wisp-tui, and respond’s ran; ran: no tools when a reply names a tool and none ran |
| decisions/0052-mlx-on-a-par-with-ollama.md | MLX models run through wisp’s own executor: the window sized from config.json and memory as for Ollama, exact counts with the model’s tokenizer, usage reported, and a thread’s processed prefix reused across its requests; the bridge stays as mlx.executor: bridge; wisp models pull fetches mlx-community models after asking; Core AI reports its bundle’s window; refined 2026-10-05 (hybrid windows, array tool calls, ChatML’s end of turn) and 2026-10-06 (thinking left to the chat template unless mlx.think is set, a schema reply thinks first); amended 2026-10-09 (any publisher can be pulled, one outside mlx.trustedPublishers only after the person confirms it) |
| decisions/0053-the-models-thinking-shown.md | A reasoning model’s thinking decoded from Ollama, counted, audited as model.reasoning, shown as a thinking activity (wisp-tui’s thought bubble) and folded under a ∴ thought line, kept as the turn’s reasoning entry and never sent back to the model; ollama.think; refined 2026-10-06 (MLX’s thinking shown the same way, mlx.think); amended 2026-10-11 (thinking chosen per model, <runtime>.models.<name>.think, and per call: wisp’s side calls send think: false, respond takes think) |
| decisions/0054-the-sandboxs-refusals-checked.md | A command that fails with Operation not permitted checked against the writable roots by the paths its error names (refused, not the sandbox, or a guess when it names none), the model told in one line that no policy rule denied it, audited on command.outcome; the model cannot start a wisp of its own; amended by ADR 0064 (the note offers outsideSandbox) and 2026-10-14 (Homebrew’s not writable lines read as well) |
| decisions/0055-cited-entries-checked.md | Beside ran:, a muted line naming the entries a reply cites in wisp’s reference forms (entry 19, entries 16-30) that the conversation does not hold; cited in the JSON turn line, unknownEntries in respond |
| decisions/0056-models-enabled-and-disabled.md | Models turned on and off (models.disabled): a disabled model hidden from /model and Tab and refused by /model, --model, model, and MCP respond, the default never disabled; wisp models as a table of every known fact, fitted to the terminal, the same in chat, --json, and wisp-tui’s picker; enabling a cached MLX model links it, and enabling an MLX model with no declared capabilities checks them on the model and records what passes (wisp models check, model.verified); chat starts on system when its configured model is unavailable |
| decisions/0057-context-defaults-from-checkpoint-2.md | Context checkpoint 2’s results and the defaults they set: memory off by default and turned on by context.memory (a list that names it still gets it), context.target 0.6, assessment.taskChanges restated; the headroom (8), the guard, and the assessment off unchanged; amends ADR 0045 |
| decisions/0058-a-shared-http-executor.md | One executor for runtimes that serve OpenAI’s chat-completions API, with a small dialect for what differs: llamacpp:<model> (llama.cpp’s llama-server) and lmstudio:<model> (LM Studio), each with its config.json section; the API key from the environment or the file, never logged or shown; the window read from the server; capabilities reported by LM Studio, and for llama.cpp declared or checked on enable; what is shared with Ollama’s executor extracted (ReplyRelay) |
| decisions/0059-one-set-of-model-actions.md | Accepted 2026-10-11, built for 0.22.0: one ModelActions layer behind wisp models, /models and /model, and a models tool for the model (off by default); /models pull and trust in chat with the publisher question as a dialog; the model may list and show freely, and every pull, enable, disable, or check it asks for goes to the person, a pull never remembered and trust and switch the person’s alone; a background pull’s end is a note in the conversation |
| decisions/0060-a-verification-pass.md | Accepted 2026-10-12 for 0.23.0, ADR 0051’s open option: after a reply, exact readers check the results it reports (a call form with a result, a command run, an exit status, a denial, a test or build outcome, a notification, a removal, quoted output, a count) against the turn’s complete call list from the audit log with each call’s whole output, and a model step behind verification.mode asks the same question; unsupported: beside ran: and cited, verified in the JSON turn line, verification in respond, response.verification in the audit; the model step measured on 2026-10-12 and left off by default (it flagged 6 of 14 honest replies on the on-device model), so the release’s floors are on the readers; amended by ADR 0062 (a reader for claims about the person’s answer) |
| decisions/0061-process-names-set-at-spawn.md | Processes named by their parent at spawn, never by rewriting their own arguments: scripts/wisp-mcp starts wisp mcp (CLIENT) from WISP_MCP_CLIENT, wisp-tui its child as wisp chat (tui); wisp finds its executable by the kernel’s path, never argv[0] |
| decisions/0062-the-inquiry-verb.md | Accepted and built 2026-10-12 for 0.24.0: the model asks the person a question mid-turn through an ask tool (off by default), free text or a choice, with limits and secrets refused; asked through chat, wisp-tui, MCP elicitation, and the pending channel (wisp questions), the first answer winning; only the person answers; by default no deadline, waiting safely and quietly (one notification) until the person answers or it is withdrawn; under MCP the turn parks after the dialog is held 60 s and the answer reaches the thread’s next turn as a note; audited |
| decisions/0063-command-time-limits.md | Accepted 2026-10-14 for 0.24.2: a model’s command may ask for its own time limit (run_command’s timeoutSeconds) and an MCP thread for one when it starts (respond’s commandTimeoutSeconds), each clamped to the operator’s commandMaxTimeoutSeconds (1,800 s by default); the default rises from 60 to 120 s; the limit each command ran under is audited |
| decisions/0064-an-approved-run-outside-the-sandbox.md | Accepted 2026-10-14: run_command’s outsideSandbox runs one command without the Seatbelt sandbox (for brew upgrade, cargo install, rustup update, which it refuses), after the policy’s lists and the person’s approval of that exact line, asked every time whatever its level, taken as once and remembered by nothing; refused where no person can be asked and when the operator sets commandPolicy.sandbox.allowOutsideSandbox false; every face marks it; audited; amends ADR 0009 and ADR 0054; amended 2026-10-14 (the person runs one outside the sandbox themselves with !!, wisp-tui’s red outside mode) |
| decisions/0029-tui-front-end.md | The terminal chat is a Rust front end (wisp-tui, ratatui) over a headless wisp chat --json; one palette for both faces; amended 2026-10-11 (Ctrl-C during a model’s turn stops the turn, through interrupt) and 2026-10-12 (an interactive status bar, with questions: N and approvals: N) |
| decisions/0028-rename-to-wisp.md | The project is wisp; one mechanical rename, no compatibility layer, a new Homebrew tap |
| decisions/0027-verb-patterns.md | Approval patterns include the verb for programs like git and cargo, from an embedded list; old patterns still count |
| decisions/0026-task-catalogue.md | Eval results ship with the tool catalogue as measurements, recorded by the eval run and embedded at build time |
| decisions/0025-context-estimation.md | The agent condenses ahead of a known window from the usage the runtime reports, because local runtimes truncate silently |
| decisions/0024-edit-file.md | edit_file writes inside the sandbox’s writable set, needs approval like a command, and replaces only an exact single match; refined 2026-10-06 (a line edit keeps the old line’s indentation and finds a stale line by find, without guessing) and 2026-10-12 (lines split on bytes as read_file numbers them, each line keeping its ending, and a guard refusing an edit that would change lines it does not name) |
| decisions/0023-condensing-tools.md | Purpose-built MCP tools condense local content on device; triage runs or reads build output and returns only the failures, amending ADR 0006 |
| decisions/0022-structured-output.md | A caller’s JSON Schema shapes the reply through guided generation, in an accepted subset, refused when the model does not declare it |
| decisions/0021-receipts.md | respond returns a receipt of the turn, derived from the audit events rather than collected separately |
| decisions/0020-coreml-risk-classifier.md | A Core ML text classifier can judge commands behind a versioned contract, beside the rules, never lowering a level |
| decisions/0019-model-backends.md | Model backends are a registry keyed by scheme; capabilities are declared by the framework, the runtime, or config, and checked before a session opens; amended by ADR 0047 (MLX in the release) and ADR 0052 (wisp’s own MLX executor, wisp models pull) |
| decisions/0018-introspection.md | wisp’s own config, status, approvals, and audit are readable, read-only, through the model’s inspect tool, MCP resources, and the CLI |
| decisions/0017-three-layer-instructions.md | wisp’s system prompt (a resource file), the operator’s extension, and the caller’s instructions, rendered in order |
| decisions/0016-local-runtimes-through-an-executor.md | Locally installed models plug in through a wisp-supplied executor; what the spike measured; ollama:<name> built |
| release.md | How a release is cut: tag, tarball, GitHub release, Homebrew tap formula |
| roadmap.md | The releases planned on 2026-10-01, all shipped by 0.23.0; the releases since; and what is not yet scheduled, grouped by what it waits on, for choosing the next |
| backlog.md | Agreed work not yet scheduled, and what has shipped, done and dated; the release plan is in roadmap.md |
| engineering.md | Standards, tooling, the pre-commit gate (with the lockfiles’ MLX pins it guards), and CI |
| backends.md | Model backends: Apple’s, Ollama, llama.cpp, LM Studio, Core AI, MLX; asset preparation, naming, declared capabilities, errors |
| decisions/ | Architecture Decision Records, one per significant decision |
| proposals/2026-09-22-tui-spike.md | Spike: a ratatui front end over a headless wisp chat --json; what was built, what was answered, what needs a real terminal |
| proposals/2026-09-29-layered-context.md | Done, recorded in ADR 0045; the detailed record of decisions D1 to D12 and every evaluation, phases 1 to 6: stored, active, and shown views of a conversation; a context composed for each request from literal turns, a summary, facts, and recall; display decoupled from context by output handling |
| proposals/2026-10-06-context-checkpoint-2.md | Run 2026-10-06 to 2026-10-08, decided in ADR 0057: the second checkpoint of the layered context (ADR 0045’s open items): the target and headroom at the default budget on a longer scenario, the 50% variants under the guard, a model switch mid-conversation, memory on and off, and the assessment changing the task only when restated; each question’s measurement, decision rule, run time, and command |
| proposals/2026-09-20-escalations.md | For review: two escalation verbs (approval, inquiry) and a choice of channels, including a non-blocking hand-off to the calling agent. Its approval part was settled otherwise by ADR 0046 (through another face, not the calling agent), and its inquiry verb by ADR 0062 |
| reviews/ | Dated code and documentation reviews with their todo lists and status |
Conventions: documentation is updated in the same change as the code it describes (see the definition of done
in engineering.md). Documents describe the current state and are edited in place. Decisions are append-only; a
superseded ADR keeps its file and gets a Superseded by line. Add an ADR whenever a choice would be
non-obvious to a newcomer or expensive to reverse.