Skip to main content
Mockingbyrd is a native macOS app. It checks your Mac against an operating doctrine, sets up the controls the doctrine calls for, and tells you when one of them stops working. You can pause or stop enforcement at any time without uninstalling it. It ships with one doctrine, Never Touch The Original. Every rule is a shell probe you can edit, so the score follows whatever rule pack you load.
Everything Mockingbyrd measures stays on your Mac, stored as plain JSON you can read, and nothing it measures is ever sent anywhere. Two requests can leave, both switchable off in Settings. The update check fetches a static file from the download site to compare version numbers and carries no identifier. Crash reporting is off by default — not started, not sampled, not pinged — and sends a stack trace only once you turn it on.

What it does

Install

Apple silicon, macOS 14 or later.
1

Download it

mockingbird.marcobuhlmann.com — the button on that page serves the current build. The line beneath it reads the version, size and build straight from the published file, so what it says is what you get.
2

Open the disk image

Drag Mockingbyrd onto Applications. It is signed with a Developer ID and notarized, so it opens on a double-click — no trip to System Settings, and no clicking past a warning.
3

Apply the guard fix

Open Doctrine and apply the fix beside The T0 guard hook is installed. Until you do, Mockingbyrd measures your Mac but enforces nothing.
A machine with no saved preferences opens with a short tour: six things worth knowing before the first scan, including the one that decides whether anything is enforced at all. Skipping counts as seeing it, and Settings → Show the tour brings it back. The first seven days are a trial with nothing held back — see Trial and licence for what changes afterwards, and what does not.
If you would rather check the file than trust the page: /download/latest.json carries the version, commit, byte count and SHA-256 of the image being served. shasum -a 256 Mockingbyrd.dmg should match it, and spctl -a -t open --context context:primary-signature -v Mockingbyrd.dmg should answer source=Notarized Developer ID.

From source

Requires a Swift toolchain (Xcode 16 or later).
make-dmg.sh signs the app and the image with a Developer ID, submits them to Apple and staples the ticket, when a certificate is in the keychain; without one it still builds, and says so. The build stamps the repository into the bundle: the commit count as the build number, the short hash as the thing that identifies a build exactly. Settings shows both, along with whether the guard hook on disk matches the app that installed it.

Headless use

You can run Mockingbyrd from a shell or a cron job:
--context prints the allow-listed JSON a copilot may see, --projection-script prints the P2 narrowing without installing it, --price-routine writes the script that checks public price lists, --prune-log strips payloads from older audit entries, and --restore-pack resets built-in rules while keeping your custom ones. Run --help for the full list.

Trial and licence

Mockingbyrd runs for seven days with nothing held back. After that it needs a licence.

What stops, and what does not

When the trial ends the app keeps showing everything it has already measured — the score, every control and its evidence, the roster, the ledger — and stops being able to change anything on the Mac. Scans, fixes, rule edits, enforcement changes and cap changes are locked, and each refusal says what still works rather than failing quietly.
The guard keeps enforcing. The T0 guard is a hook on disk that Claude Code invokes directly. It keeps blocking exactly what it blocked yesterday whether or not this app is licensed, running, or even installed. Nothing on your Mac becomes less safe because a date passed.
That is deliberate. An expired trial is a reason to lock an interface; it is not a reason to withdraw a control somebody has come to rely on.

Activating

There is no activation server. A licence is a small signed file, checked against a key compiled into the app, so activation works offline and keeps working when nothing is reachable.
1

Find your machine code

Settings → Licence shows a code like BE6A-8F77-FE96. It is a digest of this Mac’s hardware identifier, not the identifier itself — a tool that reported machine identifiers in the clear would be the sort of thing this app exists to catch.
2

Ask for a code

Use the form at mockingbird.marcobuhlmann.com — it asks for an email address and nothing else. Settings → Request a code opens it for you. Reply to the mail you get back with the machine code above and the licence is tied to that Mac; without one it runs anywhere.During the test phase codes are issued by hand, so allow a day. There is no checkout, no account and no payment flow in the app.
3

Paste it in

Paste the licence into Settings → Licence and click Activate. A licence that does not verify is refused and changes nothing, so a bad paste cannot displace one that works.
A licence is normally bound to one Mac. One issued without a machine code runs anywhere, which is what a replacement machine gets.
A licence cannot be revoked. There is no server to revoke it with — that is the same fact that makes activation work offline. Stopping a leaked licence means publishing a build with a different key, which invalidates every licence already issued.

A modified copy cannot be activated

macOS already refuses to launch a Developer ID build whose bundle has been edited, and nothing an app does about itself improves on that. What Mockingbyrd adds is narrower: re-signing a build with a different certificate is the ordinary way to get a modified one past Gatekeeper, and a copy signed by anyone other than the publisher will not accept a licence. It still runs, still measures, still shows everything it can see, and says plainly why it will not activate. If that check cannot run at all, the copy is treated as genuine. This layer must never be the reason somebody cannot use a licence they paid for.

From a shell

Reading is never gated: --scan, --roster, --ledger and the rest keep working after the trial ends. --preset, which writes caps, does not, and exits 3 saying so.

Checking it

scripts/check-licence.sh runs the shipping binary against a throwaway home directory — 30 checks covering the trial arithmetic, a clock wound backwards, a lost trial record, a forged payload, a licence issued for another Mac, an expired one, rubbish input, what an expired trial does and does not stop, and what a re-signed copy refuses.
The licences it uses are signed with the real key, so a break in the signing tool — or in the bytes either side signs over — shows up there rather than in somebody’s first activation.

Updates

Mockingbyrd asks the download site once per launch whether a newer build exists. That request is a GET for a static JSON file. It carries no licence, no machine code, no version and no identifier of any kind, and it downloads and installs nothing — it compares two version numbers and offers a link. It is a switch in Settings, and turning it off makes the app silent on the network again — unless you have also turned on the other one.

Crash reporting

A signed build that falls over on somebody else’s Mac leaves nothing behind to read. Mockingbyrd can report a crash to Sentry so it can be fixed, and this is the one part of the app that sends anything about what happened on your machine — so it is off, and off means not started.
Nothing is sent, sampled, queued or pinged until you turn this on yourself. The SDK is not initialised on a Mac that has not opted in: there is no anonymous heartbeat, and no session is opened. An older preferences file, written before the switch existed, reads as off — silence is not consent.
Once it is on, a report carries the error and its stack trace, this build’s version and commit, your macOS and hardware version, and basic performance timings. What it does not carry, and why that took verifying: The first two were found by sending a real error and reading what arrived, not by reading the SDK’s documentation — both were present despite sendDefaultPii being off.
One thing cannot be removed from the app side. Sentry sees the address a report arrives from, as any web request does, and derives an approximate country from it. Switching that off is an organisation setting in Sentry — Security & Privacy → Prevent Storing of IP Addresses — not something the SDK controls. The switch in Settings says so rather than claiming more privacy than the app delivers.

The eight screens

Grouped in the sidebar as Posture, Control and Spend.

Overview

Coverage, then Start here — the three controls worth doing next, each marked whether Mockingbyrd can apply it or it needs you. Below that the doctrine’s sections in order: §0’s numbers measured live, §2’s direction of travel, N1–N7 and P0–P5 at a glance, and every open finding ranked by what it costs.

Doctrine

Every control, filterable. Each one shows its evidence, its editable probe, its proposed fix and its results from the last 24 scans.

Monitor

Score over time, config fingerprints, and a timeline of drift, fixes, drills and enforcement changes.

Enforcement

Install and register the guard. Enforce, pause or stop it, switch individual layers, and view the end of the audit log.

Drills

The six P5 exercises and their pass conditions. Recording a run here satisfies the rules that depend on drills.

Token Ledger

What your agents would have cost at API rates, by model, project and week. Rebuilt from session files already on your Mac.

Agent Roster

Who can reach what. A map of every agent around a hub, wired to the capabilities it actually holds, with a matrix behind it.

Settings

Interface mode, scan cadence, what happens when the guard refuses something, the projection scope and the rule pack — plus your licence, the update check, crash reporting, and a way to reopen the tour.

Two modes

The same data, twice.

Nerd Mode

The screens above — probes, evidence, weights, and the full roster matrix.

Intuitive

Plain language over identical numbers: areas instead of invariants, a card per finding, and the roster map without the matrix.
Neither mode is a subset that hides a problem. The mode changes the vocabulary, not the score. Settings → Open the app in decides which one a new window starts in: whichever you used last, or always one of them. The switch at the bottom of the sidebar changes the mode you are in now. A control that is not holding is titled as the problem in Intuitive mode, not as the goal. “AI agents can’t reset your privacy settings” beside a red mark reads as reassurance; “AI agents can reset your privacy settings” reads as what it is.

Fixes are scripts, deliberately

The doctrine’s N3 says an agent’s output is only a proposal, and you are the one who applies it. Mockingbyrd follows the same rule:
1

Prepare script

Writes the change to ~/agent/work/mockingbird/fix-<rule>-<timestamp>.sh and shows you the whole script.
2

Apply

Runs that same file after you confirm. Nothing is written until you press it.
Fixes are only as aggressive as the doctrine. Removing grants that override the deny list gets rid of blank cheques, including prefixed forms like sudo, timeout 30, an absolute path or -c. It keeps the spelled-out commands that N5 tells you to keep. Moving outbound network access to ask leaves narrow WebFetch(domain:…) grants alone. No fix deletes anything: removing the guard renames it with a timestamp.

Enforcement in one paragraph

Settings alone are not a real boundary on a single-user Mac. The one control Mockingbyrd actually holds is a PreToolUse hook at ~/.claude/hooks/t0-guard. It checks each tool call for T0 paths and destructive commands like rm -rf, DELETE FROM, DROP TABLE and --force, no matter which interpreter would run them. It reads the command and the paths the call touches, not the file contents the call carries. Registering the guard adds it alongside your existing hooks. The guard reads ~/.config/mockingbird/enforcement.json on every call, so pausing works right away and a timed pause ends by itself.

Read the enforcement guide

Storage

Plain JSON, stored locally. Nothing Mockingbyrd measures leaves the machine.
The ledger reads ~/.claude/projects and ~/.codex/sessions and writes nothing back to either. Its cache holds token counts only, keyed by file size and modification time — prices are applied on read, so editing prices.json re-prices your history immediately. The rule pack is written once, on first launch, and Mockingbyrd never reads it from the app binary again. That way your edits survive upgrades. To pick up new built-in rules after an update, use Settings → Restore built-in pack. Your custom rules are kept.

Limits

Mockingbyrd does not isolate anything.
Mockingbyrd runs as your user. An agent with a general-purpose interpreter can reach anything your user can reach, including this app. What the controls buy you is that a mistake takes more separate steps, and you can see each one. That lowers the risk, but it is not a boundary. For a real boundary, use a second macOS user account that owns ~/agent/, or a virtual machine. Mockingbyrd also can’t stop context from leaving your Mac. Anything an agent reads is sent to a model provider. Narrowing the screenpipe projection (P2) is the only control here that affects that, so take it seriously.

Next steps

Architecture

How scans run, how scoring and drift work, the projection scope, and how to write a probe.

Enforcement

Installing the guard, what it blocks, and how pausing works.

Rule reference

All 58 controls with their tier, weight, type and fix.

Agent Roster

Who can reach what, and where each cell’s evidence comes from.

Token Ledger

Token use and API-equivalent spend, and how prices stay current.