AppleBridge lets an AI drive a real Macintosh running System 7.6.1 (and Mac OS 9) as if it were a local shell: write C or 68K assembly on the host, compile, link, run and observe it on the classic machine, all over a single bridge. This note is a brief inventory of the pieces that live on the Mac — and of a deliberate split, where the ordinary logic is written in C and a handful of resident, trap-level extensions must be hand-written in 68K assembly.
What it is, in one paragraph
The emulated Mac sits behind NAT, so the architecture is reversed: a faceless daemon on the guest dials out to a small Python relay on the host (host_server.py, serving :9000 for the daemon and :9001 for the MCP/control surface). The relay carries requests to the daemon, which executes them — running MPW/ToolServer commands through Apple Events, capturing the framebuffer, injecting synthetic input — and streams the results back. Claude reaches all of it through MCP tools.
flowchart TB
subgraph Host["Host (macOS, Python)"]
HS["host_server.py
:9000 daemon / :9001 MCP"]
end
subgraph Guest["System 7 / Mac OS 9 (68K)"]
D["Daemon 'ABrg'
C: transport, protocol, commands"]
CP["Control Panel 'ABcp'
C: configuration cdev"]
WD["Watchdog 'ABwd'
C: keep-alive"]
IN["Installer 'ABis'
C: preflight + copy"]
MS["msinit.a / mspatch.a
asm: boot INIT + MenuSelect patch"]
JR["ABJournal.a
asm: journaling DRVR"]
LED["menuled 'ABin'
asm: status-LED INIT"]
end
HS <--> D
D --> TS["ToolServer / MPW"]
WD --> D
CP -.prefs.-> D
MS -.global menu driving.-> D
JR -.modal driving.-> D
The C components — the Toolbox logic
Everything that is ordinary application logic is written in C and built with MPW SC/Link.
- The daemon (creator
'ABrg', a faceless background application) is the heart of the system: about fourteen translation units covering a pluggable transport (Open Transport, MacTCP or serial, hot-swappable at runtime), the length-framed wire protocol with optional mutual authentication, command dispatch to ToolServer over Apple Events, screenshot capture (main device PixMap → streamed pixmap), file transfer, and synthetic keyboard/mouse injection. - The Control Panel (
'ABcp', acdev; earlier a foreground app'ABcf') is the human face of a UI-less daemon. It shows whether the daemon and autostart are running, installs or removes autostart, picks helper apps to chain-launch through Standard File, and edits the prefs (host IP, transport, helper list). It never links against the daemon — the two communicate only through the shared prefs file and a Startup Items alias. - The Installer (
'ABis') turns “copy some fork-bearing binaries and hope” into a verified install: it probes preconditions with Gestalt (System 7, Apple Events, a TCP stack, 32-bit addressing, sufficient RAM) and refuses an environment that cannot work, then copies both forks, seeds aHOME=pref so the install is relocatable, and installs autostart. - The Watchdog (
'ABwd', also faceless) is the single Startup Items entry. Using only the Process Manager, it launches the daemon at boot — which in turn chain-launches ToolServer — and relaunches it if it ever dies.
The assembly components — resident and trap-level
Some work cannot be done in C at all. A standalone INIT, cdev-less code resource or DRVR runs with no A5 world, and MPW C cannot emit a working one: taking the address of a C callback produces an A5-relative jump-table reference the linker cannot fold into a bare code resource. So the pieces that must be resident, run at boot, patch a trap, or act as a driver are written directly in 68K assembly.
- The boot INIT (
msinit.a, an'INIT'code resource, id 0, loaded into the system heap and locked). At startup the System calls it; it copies a MenuSelect patch into the system heap and re-points the_MenuSelect($A93D) tool trap at it. Installed this way the patch is global — it runs inside every application, the reach a running app cannot achieve (an app’s ownNSetTrapAddressis process-local). - The MenuSelect patch (
mspatch.a, embedded in the INIT) is that head patch. A daemon-owned “Armed” word selects its behaviour per call: Armed = 0 is a transparent pass-through to the real trap (normal mouse tracking); Armed = 1 injects a scripted menu selection — letting the bridge drive a foreign application’s menus with no synthetic mouse at all. - The journaling DRVR (
ABJournal.a) is an Event Manager journaling playback driver. When the low-memoryJournalFlagis negative the ROM calls it to synthesize the next input event, which is exactly how it drives the modal tracking loops —MenuSelect,ModalDialog, Standard File — that ordinaryPostEventinjection can never reach, because those loops poll the hardware directly. - The status-LED INIT (
applebridge_menuled.a, creator'ABin') is a small resident extension that tail-patchesDrawMenuBar($A937): every time any application redraws the menu bar, it paints a persistent activity dot in the corner — a live health indicator visible across the whole system.
(A C sketch of a presence extension, init/applebridge_init.c, is kept in the tree as a readable reference — and as the record of exactly why it does not work as a code resource. It is the reason the extensions that ship are assembly.)
The shape of it
The division is not arbitrary. C carries the parts that want a runtime and an A5 world and a lot of Toolbox — the daemon, the panel, the installer, the watchdog. Assembly carries the parts that must exist below that level: extensions installed at boot, patches that live in the system heap and run inside every process, and a driver the ROM calls at interrupt-adjacent moments. Together they make a thirty-year-old Macintosh answer to a modern agent as dependably as a local terminal.
A brief architectural overview. Full design and progress live in the project’s ARCHITECTURE.md and the roadmap ledger; the journaling and Route B mechanics are documented separately.

