Privacy

What Halen sees, what stays on your Mac, and the only connection that ever leaves it.

Last updated 9 June 2026 ยท applies to Halen v0.3.0

Halen is built around one constraint: nothing you write leaves the machine. Every model runs on-device. The single outbound connection is the signed software-update check documented below — never your text.

What Halen sees

Once Accessibility is granted, the CaretObserver reads two AX attributes from whichever text field is currently focused, system-wide:

That includes any text you’re editing in any app that exposes a native AX text element: Mail compose windows, Notes, Slack, Notion, Apple Calendar event titles, browser address bars, IDE editor panes, and so on. It does not include the screen pixel buffer, the keyboard event stream, window titles, file system contents, or anything outside the focused field.

Payloads are windowed to 8 000 characters around the caret before being published to the event bus, so a terminal scrollback or a long Notes document can’t blast inference plugins with megabytes of unrelated text.

Plugins that downstream the text further:

What stays local

Everything by default. Concretely:

Network traffic

No inference ever leaves your Mac. The text-processing paths are loopback only — three of them, all to 127.0.0.1:

That traffic does not leave the loopback interface unless you have separately configured Ollama to bind to a non-localhost address.

Aside from the software-update check described next, there is no other outbound network code in the project. No analytics SDK, no remote logging endpoint, no crash reporter. The optional WebSocket bridge on 127.0.0.1:50765 only accepts inbound connections from the local browser extension; it never dials out.

Software updates

Halen keeps itself up to date with Sparkle, the standard open-source updater for Mac apps. This is the one connection that does leave your Mac, and it carries none of your content:

Automatic checks are on by default. You can turn them off from the “Automatically check for updates” toggle in Sparkle’s update dialog, after which Halen makes zero non-loopback connections.

Apple on-device speech recognition

Voice Dictation uses SFSpeechRecognizer with:

req.requiresOnDeviceRecognition = true

When requiresOnDeviceRecognition = true, Apple’s recogniser refuses to fall back to a server-side path: if the on-device model for your locale isn’t installed, the request fails outright (recognizerUnavailable) rather than silently round-tripping audio to Apple.

The model itself is downloaded by macOS the first time you enable Dictation for a language (System Settings โ†’ Keyboard โ†’ Dictation). Once installed, recognition runs entirely on the Neural Engine.

EventKit

Burnout Copilot and Meeting Prep ask for full Calendars access via EKEventStore.requestFullAccessToEvents(). Both plugins:

Calendar data flows through the same pipeline as everything else: read into memory, sent to the on-device model if needed (Meeting Prep), never persisted outside macOS’s own EventKit store.

Telemetry

There is none. No analytics, no usage metrics, no error reporting, no remote feature flags. Logging goes to stderr and the unified system log. Nothing is uploaded.

Permissions, summarised

PermissionUsed byWhy
Accessibilityhost (CaretObserver)Read focused text, write back corrections / dictation / snippets
Input MonitoringAsk Halen (โŒƒH)Observe the โŒƒH hotkey globally; nothing else is recorded
MicrophoneVoice DictationCapture audio for SFSpeechRecognizer
Speech RecognitionVoice DictationConvert audio to text on-device
Calendars (full access)Burnout Copilot, Meeting PrepRead events; Burnout writes the “๐ŸŒฟ Halen break” event
NotificationsMeeting Prep, Ask HalenBriefing & clipboard-fallback alerts

You can deny any of these and Halen continues to run. The dependent plugins surface their own “permission required” detail-view state with a one-click jump to System Settings.

Contact

Questions or concerns about how Halen handles your data? Email the maintainer or open an issue at github.com/lukataylo/halen.