Spikes
On this page
Spike code lives in spikes/ and is never imported by Short Leash.
Binding ports 80 and 443
Question: can leashd, running as the user, bind 127.0.0.1:80 and :443?
Result: no. On macOS 26 an unprivileged process gets EACCES for loopback ports below 1024; only the wildcard address is allowed, which would expose every site to the network.
Decision: a launchd job with a Sockets key binds both ports as root and hands them to leashd running as the user (ADR 0003).
Driving php-fpm from Rust
Question: can the proxy talk to a static-php-cli php-fpm over a unix socket, and can that PHP load shared extensions such as Xdebug?
Result: yes. A pool is ready 22 ms after it starts, so pools can start on the first request. Keep-alive requests take about 90 µs each. A shared Xdebug built for the same PHP API loads into the static binary.
Gotcha: macOS limits unix socket paths to 104 bytes, and php-fpm silently listens elsewhere when the path is longer. Short Leash keeps its sockets under a short folder and checks the length up front.
Running code inside a booted site
Question: how should a scratchpad run code inside a site for every kind of site Short Leash serves, and where do Node and Go code live so their imports resolve without writing into the project?
Result: a new process for each run, with one runner per language that boots the framework the way its own commands do. Node reads the code as a module on stdin (--input-type=module-typescript): its imports resolve from the project folder, types are stripped, and top-level await works; with tsx in the project (--import tsx) tsconfig paths resolve too. Errors in that code are reported at [eval]:LINE with your own line numbers. Go builds a main package that exists only in a go run -overlay, inside the module, so it can import the module's internal/ packages; the overlay must name the module's real path (macOS's /var is /private/var). A Laravel 13 app boots in about 60 ms.
Decision: ADR 0030.
Screen sharing between two copies of Short Leash
Question: can a WebRTC library, used as it ships with no changes, carry a 1440p screen of code with low latency? Which video codec suits it?
Result: yes. Two peers in one process got a synthetic 2560×1440 code view across in 3–7 ms at the median, measured from handing over a frame to having it decoded. That's after a 2-second warm-up, at 30 frames a second. Every setting needed is public API: no playout delay, screen-content mode, keep resolution, frame rate and bitrate caps. The codec matters most:
- VP8: spends 4 Mbit/s and stalls for up to 100 ms on every scroll.
- AV1: about 0.06 Mbit/s with a 10 ms worst case, using a third of a core for both peers.
- VP9: close behind AV1.
- Hardware H.264: cheaper on CPU, but 16 ms to encode each frame.
Gotcha: the prebuilt WebRTC libraries for macOS and Windows link FFmpeg and OpenH264, whose licenses Short Leash can't ship. Building the same library without H.264 takes three changed build arguments and no source changes. The result has no FFmpeg or OpenH264 code, and its 23 remaining licenses are all permissive. AV1 and VP9 run as fast as before, and both are royalty-free.
A real screen picked through macOS's own screen picker on macOS 26.3 needed no Screen Recording permission, and showed no monthly reminder. AV1 streamed the 4112×2658 display at 712 kbit/s, with a 5 ms median and 8 ms at the 99th percentile.
Voice works on laptop speakers without headphones. The WebRTC library's own echo cancellation kept a test voice played through a MacBook Pro's speakers down at the room's noise floor (−73 to −78 dBFS) in what the other side heard. Someone talking over it still came through clearly.
Remote control is precise and the host can always take it back:
- Positions: sent as fractions of the shared display, they land exactly on a 2× Retina screen.
- Typing: sent as text, it survives any keyboard layout.
- Posting level: guest input posted at the session level, not the hardware level, leaves macOS's "time since the last real mouse move or key" untouched. So Short Leash can stop the guest the moment the host touches the mouse or keyboard, with no extra permission.
Gotcha: macOS's secure input, the protection behind password fields, stops other apps reading keys, not sending them. Injected keys still reached a password field. Short Leash has to check for secure input itself and drop a guest's keys while it's on.
Two machines can pair with no server of Short Leash's:
- Invite: the host's own Cloudflare quick tunnel carries the setup, and the invite is ready about 6 seconds after starting.
- Connection: the guest reached the host through Cloudflare in half a second and connected peer to peer in 50 ms.
- Latency: a Mac and a virtual Mac measured 4.8 ms at the median and 11 ms at the 99th percentile, after correcting for their 41 ms clock difference.
Gotcha: don't look a new tunnel name up through the system resolver; ask Cloudflare's resolver directly, as Short Leash's sharing already does. And two machines behind the same router can't reach each other through its public address, so local addresses stay on by default. Strict networks need the optional relay.
On Windows 11 (Arm64, in a virtual machine), the same stack built and ran:
- Codecs: AV1 at 1440p took about a quarter of a core for both peers. A Mac-to-Windows call connected directly in 0.2 seconds with a 6 ms median.
- Remote input: clicks landed exactly. Windows silently drops input aimed at administrator windows, and Short Leash can detect those windows and say so.
- Firewall: joining a call showed no firewall prompt.
Gotcha: injected input counts as the host's own on Windows, so Short Leash watches a low-level hook to know when the host takes over. The new Notepad turns some injected accented characters into spaces, so Short Leash types a character with the keyboard layout's own keys when it can.
Still open: CPU on a real mid-range Windows laptop, a relay through a Cloudflare TURN key, and two machines on different networks. The full numbers are in spikes/pair/RESULTS.md.
Pair 0.15: capture, focus and control both ways
Three short checks before the 0.15 Pair changes (ADR 0046), on a Mac with macOS 26 and two dev daemons:
- Keeping Short Leash's windows out of a share: the call bar and window chips were already kept out by content protection, and testers' shares through macOS's own picker never showed them. The camera tiles now use the same protection instead of a hatched cover. A share through the real picker still needs a person to confirm it per release.
- Typing into a guest's viewer without a second Dock icon: the helper stays an accessory app and makes itself active, and its window key, when the viewer opens and on each click, as picking a window to hide already did. Typing and IME through it need a person at the keyboard to confirm.
- Controlling a guest's screen: with the guest sharing, giving the host control set control on both daemons and reached the guest's helper, and the guest's turn ending took it back; muting six times kept video at 28 fps with no freezes. Injecting into a guest's real screen was not exercised, to keep input off the Mac in use.
Local models: dictation, meeting notes and chat
Before any of Short Leash's local AI is built, six questions could change its design. The spike checked them on an Apple M3 Max with macOS 26.3, using llama.cpp at the commit llama-cpp-sys-2 0.1.160 vendors, and Qwen3.5 4B and 9B. The full numbers and scripts are in spikes/voice/RESULTS.md.
Can a helper started by leashd use the GPU? Yes, on this Mac. leashd is a launchd daemon with no window-server session. A child of leashd got a Metal device, with or without CoreGraphics linked, at the same speed as a process started from a terminal:
- Qwen3.5-4B, GPU: 1,297 tokens a second reading a prompt and 85 writing.
- Qwen3.5-4B, processor: about 200 and 29.
The first load by a new binary compiles every GPU kernel, about 11–16 seconds. Metal keeps them in a cache keyed on the binary's content, so later loads take under a second, and the first load after each Short Leash update pays it again.
Can Short Leash link llama.cpp that ShortLeash/runtimes built, and handle tool calls itself? Yes:
- Linking: a 136 KB vendored copy of
llama-cpp-sys-2with aLLAMA_PREBUILT_DIRpatch skips CMake. It links exactly the libraries a smallllama-link.txtin the prefix lists. The prefix also needs llama.cpp's common headers, whichcmake --installleaves out. - The shim: about 200 lines of C++ over llama.cpp's own chat code, with JSON in and out.
- Rendering: for 47 tools, thinking on and off, a tool-result follow-up and Unicode, its prompts were byte-for-byte the same as llama.cpp's server's.
- Streaming: tool calls streamed token by token with no parse errors, and ended identical to the server's.
Gotcha: tool descriptions are long. Chat's 36 tools take 3,914 prompt tokens, which is 3–6 seconds before the first word on this Mac and several times that on a base M1. Reusing the cached prefix brought a second request down from 4.8 s to 0.13 s. So the model helper keeps that prefix, and saves it to disk per model and tool set.
Does the GPU still work inside a deny-default macOS sandbox? Yes, at 97% of the speed outside it, with a warm load in 0.6 s. The profile follows the shape of Chromium's GPU-process profile, minus the window server and media. The sandboxed helper couldn't do any of these:
- reach the network, Short Leash's proxy or leashd's socket;
- read Short Leash's secrets or config;
- write anywhere but Metal's own cache files;
- start a shell.
Gotcha: two silent failures:
- Reading
/: without read access to/itself, the loader kills the process before it starts, and nothing is logged. - dirhelper: without the
com.apple.bsd.dirhelperservice, Metal can't find its shader cache and recompiles everything on each load.
Does the download host resume large files? Yes, but resuming can't rely on If-Range. For objects under Cloudflare's 512 MB cache limit, Range and If-Range behave, and the ETag is stable. A 600 MB test object, checked from CI, behaved differently:
- ETag and Range: its ETag stayed the same and Range requests worked.
- If-Range: Cloudflare ignored it, answering 206 with half the object for an ETag that didn't match, where 200 with the whole object was expected.
- First request: the first mid-file request before the object was cached came back without a
Content-Range, holding the wrong bytes.
Decision: resuming asks for the ETag first and compares it itself. It checks that each answer's Content-Range starts where it asked, and starts over otherwise. The manifest's SHA-256 checks the finished file: ETags of multipart uploads aren't content hashes, and the object's own hash isn't on the public URL. Model URLs include part of each file's SHA-256, so the file at a URL never changes. No Worker is needed in front of the bucket.
How reliably do small models call Short Leash's tools? The test was 47 questions, each run three times, with thinking off. The bar was agreed before it ran. Results:
- Qwen3.5-9B: passed 98.6% of runs with chat's 36 tools, with one unwanted action.
- Qwen3.5-4B, all 36 tools: also passed 97% of runs. But asked to share a site, which chat can't do, it called "stop sharing" instead, three times out of three.
- Qwen3.5-4B, 12 read tools only: passed 97% with no unwanted actions.
So on machines that get the 4B, chat only reads, and the system prompt names what chat can't do.
Which speech model should dictation use, and how long does it make you wait? The test ran Parakeet Ultra and Parakeet TDT 0.6B v3 on the same engine, converted from their published weights. The audio was 300 FLEURS utterances in English, Spanish, German, French and Italian:
- Accuracy: Ultra got 3.7% of words wrong against v3's 4.1%.
- Punctuation and capitals: Ultra was better in all five languages, and that's what dictation pastes.
- Wait with the model loaded: after you stop talking, about 60 ms for 5 seconds of speech and 100 ms for 15 seconds on this Mac's GPU, or 120–350 ms on the processor alone. Both models use about 1 GB of memory.
Decision: Ultra is the default dictation model, with v3 offered too. The first GPU load after an update compiles shaders for about 11 seconds, so Short Leash keeps the model loaded while dictation is on.
Still open:
- Hardware: the fn/🌐 key under each keyboard setting needs a person at an Apple keyboard.
- Other machines: the base M1, an Intel Mac, macOS 27 and Windows are still to run.