A Bluesky / AT Protocol client built from two languages that have no business talking to each other: Ada (the DoD systems language from 1983) and Pharo Smalltalk (the live object environment from Xerox PARC lineage).
Pharo provides the UI. Ada provides the bridge daemon. libcurl provides the HTTP. The three of them have been forced to cooperate.
Pharo Smalltalk (Spec2 UI)
→ OSSUnixSubprocess pipe
→ Ada daemon (adatalkd)
→ libcurl FFI
→ bsky.social XRPC API
The Ada bridge (adatalkd) runs as a subprocess spawned by Pharo. Communication is over stdin/stdout: one JSON object per line in, one JSON object per line out. The Ada process makes all HTTP calls via a direct libcurl FFI binding — no C wrapper, no intermediate layer.
- Login with a Bluesky handle and app password
- Fetch your timeline
- Post to Bluesky
All from a Pharo Smalltalk window, with Ada and libcurl doing the actual work underneath.
Requests:
{"op": "ping", "params": {}}
{"op": "login", "params": {"identifier": "you.bsky.social", "password": "xxxx-xxxx-xxxx-xxxx"}}
{"op": "getTimeline", "params": {}}
{"op": "createPost", "params": {"text": "Hello from Adatalksky"}}Responses:
{"ok": true, "result": "pong"}
{"ok": true, "result": {"handle": "you.bsky.social", "did": "did:plc:..."}}
{"ok": false, "error": {"kind": "AuthError", "message": "Not logged in"}}stdout is the protocol channel. stderr is diagnostics only.
The Ada bridge is a stdio loop: read a line, parse the op, call bsky.social via libcurl, write the response. The libcurl binding is a direct Ada FFI — pragma Import(C, ...) — no wrapper library.
The main complication during development was the Pharo VM shipping its own ancient libssh2.so.1.0.1 which shadows the system libcurl's TLS dependencies. Fix: a wrapper script sets LD_PRELOAD before exec to force the system library.
AdatalkskySubprocessTransport spawns ./ada/bin/main via OSSUnixSubprocess, attaches stdin/stdout pipes, and handles the JSON line protocol. AdatalkskyBridge provides the public API (ping, loginIdentifier:password:, getTimeline, createPost:). AdatalkskyMainPresenter is a Spec2 window.
The Ada curl binding calls curl_global_init, curl_easy_init, curl_easy_setopt, curl_easy_perform, and curl_easy_cleanup directly. The write callback uses pragma Convention(C) and a fixed global staging buffer with memcpy. Getting the Curl_Slist type right (passing it directly, not via 'Address) took most of a debugging session.
gprbuildand GNATlibcurl-dev- Pharo 12 with
OSSUnixSubprocessinstalled via Metacello
cd ada
gprbuild -P adatalksky_core.gpr -fThe bridge binary lands at ada/bin/main. Run it as ./ada/bin/main from the repo root (the wrapper script in ada/bin/adatalkd handles LD_PRELOAD).
printf '{"op":"ping","params":{}}\n' | ./ada/bin/main
printf '{"op":"login","params":{"identifier":"you.bsky.social","password":"xxxx"}}\n' | ./ada/bin/mainInstall OSSUnixSubprocess:
Metacello new
repository: 'github://pharo-contributions/OSSubprocess';
baseline: 'OSSubprocess';
load.Load the Adatalksky packages from smalltalk/ via Iceberg or by compiling the .class.st files directly in a Playground.
Then:
bridge := AdatalkskyBridge startOnStdio.
bridge ping.Or run the full demo:
AdatalkskyDemo run.| Package | Contents |
|---|---|
Adatalksky-Bridge |
AdatalkskyBridge, AdatalkskySubprocessTransport |
Adatalksky-Client |
AdatalkskyDemo |
- Fortransky — Fortran
- Assemblersky — x86 Assembly
- Cobolsky — COBOL [still private repo, release soon]
- Adatalksky — Ada + Pharo Smalltalk ← you are here
- Malbolgesky — Malbolge
- INTERCALsky — INTERCAL
Each one is a proof that the AT Protocol works regardless of what's on the other end.
Two languages from different decades of computing history, forced to cooperate over a pipe.