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', a cdev; 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 a HOME= 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 own NSetTrapAddress is 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-memory JournalFlag is 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 ordinary PostEvent injection 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-patches DrawMenuBar ($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.