<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Spritely Institute</title><id>https://spritely.institute/feed.xml</id><subtitle>Recent Posts</subtitle><updated>2026-05-13T12:10:52Z</updated><link href="https://spritely.institute/feed.xml" rel="self" /><link href="https://spritely.institute" /><entry><title>Hoot 0.9.0 released!</title><id>https://spritely.institute/news/hoot-0-9-0-released.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2026-05-13T12:00:00Z</updated><link href="https://spritely.institute/news/hoot-0-9-0-released.html" rel="alternate" /><content type="html">&lt;p&gt;We are excited to announce the release of &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; 0.9.0!  Hoot
is a Scheme to WebAssembly compiler backend for
&lt;a href=&quot;https://gnu.org/software/guile&quot;&gt;Guile&lt;/a&gt;, as well as a general purpose
WebAssembly toolchain.  In other words, &lt;em&gt;Scheme in the browser!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release contains new features and bug fixes and since the &lt;a href=&quot;/news/hoot-0-8-0-released.html&quot;&gt;0.8.0
release&lt;/a&gt; back in February.&lt;/p&gt;
&lt;h2&gt;Use Hoot in the upcoming Lisp Game Jam!&lt;/h2&gt;
&lt;p&gt;On Friday (May 15th 2026), the &lt;a href=&quot;https://itch.io/jam/spring-lisp-game-jam-2026&quot;&gt;Spring edition of the Lisp Game
Jam&lt;/a&gt; will begin!  It's
a 10-day long game jam where participants make games using their
favorite flavor of Lisp.&lt;/p&gt;
&lt;p&gt;Does making a small web game in Scheme using Hoot sound appealing to
you?  Well, then we have just the thing to get you started: the &lt;a href=&quot;https://codeberg.org/spritely/hoot-game-jam-template&quot;&gt;Hoot
game jam
template!&lt;/a&gt; This
template project has everything you need to start making an HTML5 game
with Hoot quickly.&lt;/p&gt;
&lt;p&gt;The template repository includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bindings to the necessary web APIs to make an interactive game with
HTML5 canvas&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A Makefile for compiling, running a development web server, and
generating a .zip bundle for uploading to itch.io&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A very simple Breakout-like example game that demonstrates how to
put all the pieces together&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to contributor Gonzalo Delgado, the game jam template now
features gamepad input support!&lt;/p&gt;
&lt;p&gt;For more inspiration, here are some games made with Hoot for past
jams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://davexunit.itch.io/cirkoban&quot;&gt;Cirkoban&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://davexunit.itch.io/strigoform&quot;&gt;Strigoform&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://podatus.itch.io/shields-tyvm&quot;&gt;Shields TYVM&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, onto the release notes!&lt;/p&gt;
&lt;h2&gt;Toolchain changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Added the concept of host provided types.  Useful for Hoot on
Wastrel support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switched from legacy exceptions to standard Wasm exceptions, which
were officially adopted in July 2025 but have been available in
browsers for much longer.  The &lt;code&gt;--experimetal-wasm-exnref&lt;/code&gt; flag is
passed to NodeJS in case it is old enough to still require this
feature flag (which is currently the case for Guix).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for DWARF custom section.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Compiler changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Replaced function name/source metadata with DWARF.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changed default debug level to 1, which now includes emitting DWARF.
For a production build stripped of all debug data, use debug level 0
(&lt;code&gt;-g0&lt;/code&gt; in the CLI) or strip the binary afterwards using &lt;code&gt;hoot strip&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update compiler backend for new primitive bytevector predicates
introduced in Guile 3.0.11.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for bitvector literals on big endian host systems.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Runtime changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Floating point number to string conversion is now implemented in
Scheme rather than relying on an import.  This makes binaries
slightly bigger but makes it easier to support Hoot on Wastrel and
non-JavaScript runtimes generally.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bignum imports have been monomorphized to ease non-JavaScript
runtimes (Wastrel, again).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scheme binaries now export a &lt;code&gt;main&lt;/code&gt; function that takes 0 arguments
and returns 0 values that invokes the internal &lt;code&gt;$load&lt;/code&gt; function.
This makes it posible for Wastrel to boot a Hoot program without
support for the Scheme reflection interface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added DWARF parser to &lt;code&gt;reflect.js&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Removed &lt;code&gt;fsqrt&lt;/code&gt; import in favor of using &lt;code&gt;f64.sqrt&lt;/code&gt; instruction.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Scheme changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Range errors now include the range in the exception irritants list.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;uint8array-&amp;gt;bytevector&lt;/code&gt; procedure to &lt;code&gt;(hoot typed-arrays)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vectors are now considered self-evaluating in &lt;code&gt;(hoot expander)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added internal &lt;code&gt;(hoot module-syntax)&lt;/code&gt; module to gather runtime
module macros and support code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;define-record-type&lt;/code&gt; now works in the Scheme interpreter for record
types with up to 8 fields.  (Supporting more than 8 fields is
planned but will require larger changes to the Hoot runtime.)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CLI changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;guild compile-wasm&lt;/code&gt; has been deprecated in favor of the new &lt;code&gt;hoot compile&lt;/code&gt; subcommand.  Both commands accept the same flags.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;hoot help&lt;/code&gt; subcommand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;hoot strip&lt;/code&gt; subcommand to remove debugging information from a
Wasm binary.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feature flags have been split out from debug options in &lt;code&gt;hoot compile&lt;/code&gt;/&lt;code&gt;guild compile-wasm&lt;/code&gt;.  For example, &lt;code&gt;-gruntime-modules&lt;/code&gt; is
no longer valid; use &lt;code&gt;-fruntime-modules&lt;/code&gt; instead.  The &lt;code&gt;-g&lt;/code&gt; flag now
exclusively handles debugging data such as whether to emit DWARF or
a names section.  The &lt;code&gt;-f&lt;/code&gt; flag handles things that change program
behavior such as whether to include a runtime module system for use
with the Scheme interpreter.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Documentation changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Updated manual to use &lt;code&gt;hoot compile&lt;/code&gt; instead of &lt;code&gt;guild compile-wasm&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hoot compile --bundle&lt;/code&gt; is now recommended in web deployment section
rather than manually copying support files.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;repl.js&lt;/code&gt; file missing from 0.8.0 (which broke &lt;code&gt;hoot repl&lt;/code&gt;) is
now included in the release tarball.  Apologies for the oversight!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed pathologically large function emitted by &lt;code&gt;lower-globals&lt;/code&gt;,
which Wasm engines such as Wastrel can struggle with.  Instead, many
smaller functions are emitted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hash-set!&lt;/code&gt;, &lt;code&gt;hashq-set!&lt;/code&gt;, etc. now return the passed value.  This
matches Guile's behavior and allows more existing Guile programs to
work as expected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed bug in parsing zero-length custom sections in Wasm binaries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed validation of &lt;code&gt;return_call_indirect&lt;/code&gt; instruction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed missing EOF handler in REPL meta-command reader.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Browser compatibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Safari 26 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Firefox 121 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Chrome 119 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Get Hoot&lt;/h2&gt;
&lt;p&gt;Hoot is available in GNU Guix:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile guile-hoot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, Hoot is now available in
&lt;a href=&quot;https://tracker.debian.org/pkg/guile-hoot&quot;&gt;Debian&lt;/a&gt;, though it will
take awhile for this release to make it there.&lt;/p&gt;
&lt;p&gt;Otherwise, Hoot can be built from source via our release tarball.  See
the &lt;a href=&quot;/hoot&quot;&gt;Hoot homepage&lt;/a&gt; for a download link and GPG signature.&lt;/p&gt;
&lt;p&gt;Documentation for Hoot 0.9.0, including build instructions, can be
found
&lt;a href=&quot;https://spritely.institute/files/docs/guile-hoot/0.9.0/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/hoot&quot;&gt;Hoot project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build something cool with Hoot, let us know on our &lt;a href=&quot;https://community.spritely.institute&quot;&gt;community
forum&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alex Sassmannshausen&lt;/li&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Bavier&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Aria Stewart&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;Ellie High&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Gerome Bochmann&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;James Smith&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Michael Orbinpost&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stefan Magdalinski&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;chee rabbits&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Until next time, happy hooting! 🦉&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.18.0: Sleepy actors!</title><id>https://spritely.institute/news/spritely-goblins-v0-18-0-sleepy-actors.html</id><author><name>Dave Thompson and Christine Lemmer-Webber</name><email>contact@spritely.institute</email></author><updated>2026-04-21T12:00:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-18-0-sleepy-actors.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2026-04-21-sleepy-actors.png&quot; alt=&quot;Goblins version 0.18.0 release art: a Spritely goblin takes a nap in a chair by a fireplace, tea steams on a nearby table&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to announce the release of &lt;a href=&quot;/goblins/&quot;&gt;Spritely Goblins&lt;/a&gt;
0.18.0!  This release features a new caching layer called “sleepy
actors”, OCapN protocol updates, and numerous bug fixes.  So get cozy
by the fire, pull out a steaming cup of tea, and let’s have a nice
relaxing read about this exciting new Goblins release!&lt;/p&gt;
&lt;h2&gt;Sleepy actors&lt;/h2&gt;
&lt;p&gt;Remember when we &lt;a href=&quot;https://spritely.institute/news/spritely-goblins-v0130-object-persistence-and-easier-io.html&quot;&gt;introduced persistence back in Goblins
0.13.0&lt;/a&gt;?
You’re not sure?  Okay, as a quick refresher, Goblins’ &lt;a href=&quot;https://spritely.institute/files/docs/guile-goblins/latest/Persistence.html&quot;&gt;persistence
system&lt;/a&gt;
is able to serialize a running Goblins program for you and wake it
back up later!  Pretty cool!&lt;/p&gt;
&lt;p&gt;Goblins is pretty smart about only saving the changes that need to
change. But... if we can save actors to disk, do we really need them
to be “awake” all at once?  What if we let them take a little nap, and
just woke them up when it’s time for them to do something?  Then they
could go back to bed when they aren’t needed anymore!&lt;/p&gt;
&lt;p&gt;Well that’s exactly what we’ve built!  Sleepy actors are a new,
optional caching layer has been added to the core of Goblins.  Actors
may now go to sleep or be woken up depending on a customizable caching
algorithm known as a “sleep strategy”.  When an actor goes to sleep,
it is saved to the vat’s persistence store but its reference remains
live.  When an asleep actor receives a message, its state is restored
from the vat’s persistence store and the message is processed as
usual.&lt;/p&gt;
&lt;p&gt;Goblins currently ships with two sleep strategies: an extremely simple
strategy where your little goblins head to bed after each and every
turn, and a “least recently used” algorithm, which functions as a hot
cache where only the most recently activated goblins stay awake, and
the rest go take a nap.&lt;/p&gt;
&lt;p&gt;For a feature that’s so sleepy, we’re pretty wired about its
potential, and we hope you are too!&lt;/p&gt;
&lt;h2&gt;OCapN protocol updates&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://ocapn.org&quot;&gt;OCapN&lt;/a&gt; draft specification has changed in the
time since the last Goblins release.  The &lt;code&gt;op:deliver-only&lt;/code&gt; operation
has been dropped in favor of a single &lt;code&gt;op:deliver&lt;/code&gt; operation.  GC
operations now accept a list of export positions instead of a single
position so that GC can be done in batches; their operation names have
likewise been changed to the plural form (&lt;code&gt;op:gc-export&lt;/code&gt; is now
&lt;code&gt;op:gc-exports&lt;/code&gt;, etc.)  The protocol version number has thus been
bumped, which means that applications built with an earlier release of
Goblins are incompatible with the OCapN shipped in this release.&lt;/p&gt;
&lt;h2&gt;Notable bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed a race condition when restoring multiple vats from persisted
data.  If Alice in vat A is referenced by Bob in vat B but &lt;em&gt;no other
actors in vat A&lt;/em&gt; then it was possible for Alice to be garbage
collected before vat B is restored.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed a signing oracle vulnerability in the WebSocket netlayer’s
designator authentication code.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting the release&lt;/h2&gt;
&lt;p&gt;This release includes all the features detailed above as well as many
bug fixes.  See the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.18.0/NEWS&quot;&gt;NEWS&lt;/a&gt; for
more information about all of the changes.&lt;/p&gt;
&lt;p&gt;As usual, Guix users can upgrade to 0.18.0 by running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;guix pull
guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the tarball on our &lt;a href=&quot;https://spritely.institute/goblins/&quot;&gt;release
page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re making something with Goblins or want to contribute to
Goblins itself, be sure to join our community at
&lt;a href=&quot;https://community.spritely.institute/&quot;&gt;community.spritely.institute&lt;/a&gt;!
We also host regular office hours where you can come and ask questions
or discuss our projects.  Information about office hours is available
on the forum.  Thanks for following along and hope to see you there!&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;Ellie High&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Gerome Bochmann&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Michael Orbinpost&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Hoot 0.8.0 released!</title><id>https://spritely.institute/news/hoot-0-8-0-released.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2026-02-25T12:00:00Z</updated><link href="https://spritely.institute/news/hoot-0-8-0-released.html" rel="alternate" /><content type="html">&lt;video title=&quot;Live hacking with Hoot in Emacs&quot; src=&quot;https://files.spritely.institute/videos/2026-02-24-hoot-geiser.mp4&quot; autoplay=&quot;true&quot; loop=&quot;true&quot; muted=&quot;true&quot; controls=&quot;true&quot;&gt;&lt;/video&gt;
&lt;p&gt;We are excited to announce the release of &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; 0.8.0!  Hoot
is a Scheme to WebAssembly compiler backend for
&lt;a href=&quot;https://gnu.org/software/guile&quot;&gt;Guile&lt;/a&gt;, as well as a general purpose
WebAssembly toolchain.  In other words, &lt;em&gt;Scheme in the browser!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release contains new features and bug fixes and since the &lt;a href=&quot;/news/hoot-0-7-0-released.html&quot;&gt;0.7.0
release&lt;/a&gt; back in October.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;(hoot repl)&lt;/code&gt; module.  At long last, there is now a built-in
read-eval-print loop implementation!  Previous releases added a
macro expander, a Scheme interpreter, and a runtime module system,
but now it’s possible to do live hacking from a Hoot program inside
a WebAssembly runtime!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;To use the REPL, compile your Wasm binary with the necessary debug
flag during development: &lt;code&gt;guild compile-wasm -g1&lt;/code&gt;.  This will
include the runtime module system in the resulting binary.  Expect
compilation time and binary size to increase significantly.  The
trade-off is that a live hacking workflow will make recompilations
fewer and farther between.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;While not shipping in Hoot directly, initial support for using the
Hoot REPL from Emacs has been added in the new
&lt;a href=&quot;http://codeberg.org/spritely/geiser-hoot&quot;&gt;geiser-hoot&lt;/a&gt; extension.
We have submitted geiser-hoot for inclusion in
&lt;a href=&quot;https://melpa.org&quot;&gt;MELPA&lt;/a&gt; and &lt;a href=&quot;https://guix.gnu.org&quot;&gt;Guix&lt;/a&gt; so it
will be easy to install in the very near future.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhanced &lt;code&gt;(hoot web-server)&lt;/code&gt; module.  To support the use of REPLs
running within a web browser tab, the most common development use
case, the web server doubles as a REPL server, proxying TCP traffic
from REPL clients (more about that below) over a WebSocket to the
connected browser tab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;These enhancements introduce two new, &lt;em&gt;optional&lt;/em&gt; depedencies to
Hoot: &lt;a href=&quot;https://codeberg.org/guile/fibers&quot;&gt;Fibers&lt;/a&gt; and
&lt;a href=&quot;https://git.dthompson.us/guile-websocket&quot;&gt;guile-websocket&lt;/a&gt;.  If
either of these dependencies are not present at build time, the
&lt;code&gt;(hoot web-server)&lt;/code&gt; module will not be built.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The web server can now be extended with a user-supplied request
router.  An example of this can be found in our
&lt;a href=&quot;https://codeberg.org/spritely/hoot-slides/src/commit/0575c38e2899c90d19e0c007c1b9b8bd06b379ca/bin/hoot-slides.in#L16&quot;&gt;hoot-slides&lt;/a&gt;
repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;(hoot web-repl)&lt;/code&gt; module.  This module can be imported and
compiled into the Wasm binary so that it can act as a REPL server.
This is complicated by the fact that a browser client cannot act as
a server, it is strictly a client.  Instead, it connects to the
aforementioned &lt;code&gt;(hoot web-server)&lt;/code&gt; which acts as a proxy for all
connected REPL clients.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;hoot&lt;/code&gt; command-line tool.  This command will be used as a place
to collect handy Hoot development tools.  So far, there are two
subcommands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hoot repl&lt;/code&gt;: Open a REPL running in Node.  Useful for quickly
trying out basic Scheme expressions in Hoot without having to
compile a standalone WebAssembly program.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;hoot server&lt;/code&gt;: Conveniently launch the development web server in
&lt;code&gt;(hoot web-server)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;(web request)&lt;/code&gt; and &lt;code&gt;(web response)&lt;/code&gt; modules that export a
sliver of the API defined in Guile’s modules of the same names.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;(web socket)&lt;/code&gt; module that provides a input/output interface to
WebSocket client connections.  Mimicks the module of the same name
in guile-websocket.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added customizable module loader interface via new
&lt;code&gt;current-module-loader&lt;/code&gt; parameter.  Two concrete loaders are
provided: By default, modules are loaded from the file system by
searching a load path.  This is useful when running in a non-browser
runtime such as NodeJS.  When &lt;code&gt;run-web-repl&lt;/code&gt; in &lt;code&gt;(web repl)&lt;/code&gt; is
used, connected REPLs are configured to use an HTTP-based loader.
This loader makes HTTP requests to a special endpoint on the
development web server to fetch source code.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Note that modules loaded at runtime are loaded &lt;em&gt;from source&lt;/em&gt; and
then &lt;em&gt;interpreted&lt;/em&gt;.  Unlike Guile, where modules are automatically
compiled to bytecode, Hoot cannot compile individual modules to
Wasm (which would require compiling the compiler to Wasm which is
an interesting future possibility).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Community highlights&lt;/h2&gt;
&lt;p&gt;Check out this &lt;a href=&quot;https://git.solarpunk.moe/vv/hoot-tracker&quot;&gt;chiptune
tracker&lt;/a&gt; made with Hoot by
Vivianne Langdon!&lt;/p&gt;
&lt;video title=&quot;Hoot tracker&quot; src=&quot;https://files.spritely.institute/videos/2026-02-25-hoot-tracker.mp4&quot; controls=&quot;true&quot;&gt;&lt;/video&gt;
&lt;p&gt;Additionally, check out
&lt;a href=&quot;https://wingolog.org/archives/2026/02/06/ahead-of-time-wasm-gc-in-wastrel&quot;&gt;Wastrel&lt;/a&gt;,
a Wasm GC to C compiler developed by Andy Wingo.  Wastrel notably uses
Hoot’s Wasm toolchain.  A Wasm program compiled with Wastrel runs
&lt;em&gt;faster&lt;/em&gt; than the same program on NodeJS!&lt;/p&gt;
&lt;h2&gt;Documentation changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Updated &lt;code&gt;Installation&lt;/code&gt; chapter to mention new optional dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;Modules&lt;/code&gt; and &lt;code&gt;REPL&lt;/code&gt; sections to the Scheme reference chapter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;Development&lt;/code&gt; chapter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update &lt;code&gt;Status&lt;/code&gt; section to remove mention of missing R7RS support
that we have now.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Removed docs for obsolete &lt;code&gt;--emit-names&lt;/code&gt; flag&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add documentation for &lt;code&gt;-g&lt;/code&gt; flag to &lt;code&gt;guild compile-wasm&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed example in the &lt;code&gt;JavaScript reflection&lt;/code&gt; section that was using
the obsolete &lt;code&gt;load_main&lt;/code&gt; signature.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Toolchain changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Split Wasm validation out of &lt;code&gt;(wasm vm)&lt;/code&gt; and into new &lt;code&gt;(wasm validation)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keep data computed within the validation pass in &lt;code&gt;&amp;lt;validated-wasm&amp;gt;&lt;/code&gt;
records so that data can be used during instantiation rather than
redundantly recomputing it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added explicit support for representing a “canonicalization”: a
world in which structurally equal types are equal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;(wasm vm)&lt;/code&gt; types &lt;code&gt;&amp;lt;wasm-func&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;wasm-struct&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;wasm-array&amp;gt;&lt;/code&gt; now
refer to their types by index into a canonicalized set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added untagged &lt;code&gt;&amp;lt;wasm-array&amp;gt;&lt;/code&gt; backing stores to &lt;code&gt;(wasm vm)&lt;/code&gt; for all
simple scalar numeric types, including &lt;code&gt;i8&lt;/code&gt; and &lt;code&gt;i16&lt;/code&gt; packed types.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Modified &lt;code&gt;(wasm vm)&lt;/code&gt; to look up named heap type references in the
instance’s canonicalization.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;bytevector-&amp;gt;wasm-array&lt;/code&gt;, &lt;code&gt;wasm-array-&amp;gt;bytevector&lt;/code&gt; to &lt;code&gt;(wasm vm)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for some of the “none” bottom types.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Packed array data is now stored signed, wrapped from &lt;code&gt;i32&lt;/code&gt; when set,
and only unwrapped to unsigned in &lt;code&gt;get_u&lt;/code&gt; functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;string.from_code_point&lt;/code&gt; and &lt;code&gt;string.concat&lt;/code&gt; lowerings in
&lt;code&gt;(wasm lower-stringrefs)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Renamed outdated &lt;code&gt;extern.internalize&lt;/code&gt; and &lt;code&gt;extern.externalize&lt;/code&gt; to
their current names, &lt;code&gt;any.convert_extern&lt;/code&gt; and &lt;code&gt;extern.convert_any&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added new &lt;code&gt;has-wasm-header?&lt;/code&gt; procedure to &lt;code&gt;(wasm parse)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parse core reference types to &lt;code&gt;&amp;lt;ref-type&amp;gt;&lt;/code&gt; records rather than
symbol abbreviations in &lt;code&gt;(wasm parse)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Miscellaneous changes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Modified &lt;code&gt;schedule-task&lt;/code&gt; in &lt;code&gt;(fibers scheduler)&lt;/code&gt; (which is
implemented using inline Wasm on the target) to be a no-op when
called at expansion time on the host i.e. used at the module
top-level or from a procedural macro.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for &lt;code&gt;vector&lt;/code&gt; and &lt;code&gt;call-with-values&lt;/code&gt; primitives to
&lt;code&gt;(hoot primitives)&lt;/code&gt; module so they can be used in interpreted code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;truncate&lt;/code&gt; is now exported from &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow exports to clobber each other in &lt;code&gt;module-declare!&lt;/code&gt; to support
live hacking of modules where &lt;code&gt;define-module&lt;/code&gt; forms are often
re-evaluated many times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracted JS &lt;code&gt;Uint8Array&lt;/code&gt; bindings from internals of &lt;code&gt;(fibers streams)&lt;/code&gt; to new &lt;code&gt;(hoot typed-arrays)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement subset of Guile’s procedural module API for hackable
programs (i.e. programs that are built with runtime module support).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot config)&lt;/code&gt; target-side module for accessing certain
build-time constants (currently just the Hoot version string).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracted &lt;code&gt;(hoot library)&lt;/code&gt; module from &lt;code&gt;(hoot library-group)&lt;/code&gt; so
that the library parser can be used on the target for live hacking
purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;define-module&lt;/code&gt; implementation to &lt;code&gt;(guile)&lt;/code&gt; that simply throws
an error if used during compilation.  A separate implementation is
installed for use by the interpreter in hackable programs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;#:replace?&lt;/code&gt; argument to &lt;code&gt;module-export!&lt;/code&gt; to allow replacement
of exports for live hacking purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exported &lt;code&gt;module-root&lt;/code&gt; from &lt;code&gt;(hoot modules)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;module-imported-modules&lt;/code&gt; procedure to &lt;code&gt;(hoot modules)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changed file I/O host functions to return &lt;code&gt;null&lt;/code&gt; when a file cannot
be opened so a Scheme exception that can be handled by user code
rather than a host exception that cannot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracted contents of &lt;code&gt;(scheme file)&lt;/code&gt; to new &lt;code&gt;(hoot file)&lt;/code&gt; module
for use in internal code such as the implementation of the file
system module loader in &lt;code&gt;(hoot hackable)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Moved implementation of &lt;code&gt;string-join&lt;/code&gt;, &lt;code&gt;string-concatenate&lt;/code&gt;,
&lt;code&gt;string-prefix?&lt;/code&gt;, and &lt;code&gt;string-prefix-ci?&lt;/code&gt; from &lt;code&gt;(guile)&lt;/code&gt; to &lt;code&gt;(hoot strings)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Moved case-insensitive string procedures from &lt;code&gt;(scheme char)&lt;/code&gt; to
&lt;code&gt;(hoot strings)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;string-drop&lt;/code&gt; to &lt;code&gt;(hoot strings)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;every&lt;/code&gt; and &lt;code&gt;fold-right&lt;/code&gt; procedures to &lt;code&gt;(hoot lists)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Moved implementation of &lt;code&gt;and-map&lt;/code&gt; and &lt;code&gt;or-map&lt;/code&gt; from &lt;code&gt;(guile)&lt;/code&gt; to
&lt;code&gt;(hoot lists)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;symbol-append&lt;/code&gt; to &lt;code&gt;(hoot symbols)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added less verbose custom printer for &lt;code&gt;&amp;lt;module&amp;gt;&lt;/code&gt; record type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switched from positional to keyword arguments for &lt;code&gt;make-soft-port&lt;/code&gt;
in &lt;code&gt;(hoot ports)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;list-index&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;format-exception&lt;/code&gt; not writing all of its output to the
current error port.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fix &lt;code&gt;eof-object&lt;/code&gt; export in &lt;code&gt;(ice-9 binary-ports)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed off-by-one error for procedures with rest args in &lt;code&gt;(hoot eval)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;min&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt; to only accept real numbers, handle NaNs, and
normalize exact zeroes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed continuation composition leaving an unwind continuation on the
stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed prompt unwinding in certain join continuation situations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed compilation of &lt;code&gt;unwind&lt;/code&gt; primcalls at join points.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed runtime module system ignoring replacement bindings in Guile
modules.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Browser compatibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Safari 26 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Firefox 121 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Chrome 119 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Get Hoot&lt;/h2&gt;
&lt;p&gt;Hoot is available in GNU Guix:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile guile-hoot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, Hoot is now available in
&lt;a href=&quot;https://tracker.debian.org/pkg/guile-hoot&quot;&gt;Debian&lt;/a&gt;, though it will
take awhile for this release to make it there.&lt;/p&gt;
&lt;p&gt;Otherwise, Hoot can be built from source via our release tarball.  See
the &lt;a href=&quot;/hoot&quot;&gt;Hoot homepage&lt;/a&gt; for a download link and GPG signature.&lt;/p&gt;
&lt;p&gt;Documentation for Hoot 0.8.0, including build instructions, can be
found
&lt;a href=&quot;https://spritely.institute/files/docs/guile-hoot/0.8.0/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/hoot&quot;&gt;Hoot project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build something cool with Hoot, let us know on our &lt;a href=&quot;https://community.spritely.institute&quot;&gt;community
forum&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alex Sassmannshausen&lt;/li&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Danny OBrien&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Bavier&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Joshua Simmons&lt;/li&gt;
&lt;li&gt;Justin Sheehy&lt;/li&gt;
&lt;li&gt;Matt Panhans&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Aria Stewart&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Gerome Bochmann&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;James Smith&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Michael Orbinpost&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Rodion Goritskov&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stefan Magdalinski&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Until next time, happy hooting! 🦉&lt;/p&gt;
</content></entry><entry><title>Mandy: ActivityPub on Goblins</title><id>https://spritely.institute/news/mandy-activitypub-on-goblins.html</id><author><name>Jessica Tallon</name><email>contact@spritely.institute</email></author><updated>2026-01-06T12:00:00Z</updated><link href="https://spritely.institute/news/mandy-activitypub-on-goblins.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;https://spritely.institute/static/images/sprites/mandy-square.png&quot; alt=&quot;Mandy character artwork&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3.org/TR/activitypub/&quot;&gt;ActivityPub&lt;/a&gt; is the protocol that powers
the Fediverse. Not only does it allow different instances of the same app to
federate with one another, it also allows different apps to federate. For
example, a post on a video hosting app could federate to a microblogging app.
ActivityPub does a good job of this and is a leap forward from what
&lt;a href=&quot;https://en.wikipedia.org/wiki/OStatus&quot;&gt;came before it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For those unfamiliar, ActivityPub is a decentralized social networking protocol
standardized under the W3C. Both Spritely’s Executive Director,
Christine Lemmer-Webber and myself (Jessica Tallon) worked on standardizing
ActivityPub. The ActivityPub specification  left holes in for identity,
distributed storage, and more. Since then Spritely has been a continuation of
this work, researching and developing the next generation of social web
infrastructure.&lt;/p&gt;
&lt;p&gt;But where does this leave ActivityPub? Has it been abandoned by Spritely as a
stepping stone? No! We’ve long had a project (codenamed Mandy) on our roadmap to
implement ActivityPub on top of Goblins. If you open up the ActivityPub
specification you’ll actually see
&lt;a href=&quot;https://www.w3.org/TR/activitypub/#actors&quot;&gt;mention&lt;/a&gt; of actors. The protocol
itself is designed with the actor model in mind. Since Goblins is an
implementation of the actor model, they should be a natural fit.&lt;/p&gt;
&lt;p&gt;The source code for the prototype this blog post is based off can be found
&lt;a href=&quot;https://codeberg.org/spritely/mandy&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Goblins actors over HTTP&lt;/h2&gt;
&lt;p&gt;ActivityPub is a protocol on top of HTTP, but Goblins doesn’t use HTTP. So, the
first step was to make Goblins actors available over HTTP. Fortunately, hooking
this up was quite easy. There are many different ways we could do this, but for
this prototype I took a fairly simple approach.&lt;/p&gt;
&lt;p&gt;Guile has a
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Web-Server.html&quot;&gt;built in web server&lt;/a&gt;.
not only that but &lt;a href=&quot;https://codeberg.org/guile/fibers&quot;&gt;fibers&lt;/a&gt; (the concurrency
system Goblins uses) has a backend for this. It means we can pretty
quickly start handling requests.&lt;/p&gt;
&lt;p&gt;The webserver can be started using the &lt;code&gt;run-server&lt;/code&gt; procedure. It takes in a
symbol which specifies an implementation (&lt;code&gt;'http&lt;/code&gt; would be the built in HTTP
server, &lt;code&gt;'fibers&lt;/code&gt; is the one provided by Fibers):&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;run-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'fibers&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The handler is a procedure which takes a request and a body and expects the
HTTP response as the return value. When writing a typical HTTP server in Fibers
we’d suspend the fiber until the response is ready. However, Goblins code is
built around sending messages and waiting for promises to resolve. To bridge the
API between these two different philosophies, we use a channel.&lt;/p&gt;
&lt;p&gt;Channels allow two fibers to send a message to one another. Reading or
writing to a channel causes the fiber to suspend until a message is available.
We can then send a message to one of our actors and use &lt;code&gt;on&lt;/code&gt; to listen to the
response, once we have the response we can write our response to our channel.&lt;/p&gt;
&lt;p&gt;Goblins vats are event loops which run on a fiber. These event loops manage a
queue of messages sent to actors spawned within that vat and are processed
sequentially. If we were to just write to the channel, we would suspend
underlying fiber for the vat. When the vat’s fiber suspends, it stops it from
processing other messages within the queue. To ensure that we’re not blocking
the vat by suspending it, we’ll use the helper procedure &lt;code&gt;syscaller-free-fiber&lt;/code&gt;
which gives us a new fiber outside the vat which can be safely suspended.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^web-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;router&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response-ch&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-channel&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;router&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;syscaller-free-fiber&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response-ch&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;vector&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'ok&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-symbol&quot;&gt;*unspecified*&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-keyword&quot;&gt;#:catch&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;syscaller-free-fiber&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
               &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response-ch&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;vector&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'err&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-symbol&quot;&gt;*unspecified*&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;get-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response-ch&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;content-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;content-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;,content-type&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;content-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;content-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,content-type&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,@headers&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;content-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;text/plain&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;Oh no!&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;syscaller-free-fiber&lt;/span&gt;
   &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;run-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'fibers&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-keyword&quot;&gt;#:addr&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;inet-pton&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;AF_INET&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;0.0.0.0&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'running&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;web-server&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^web-server&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^router&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a slightly simpler version than the one used in the prototype, but it
shows how we’re making asynchronous actors which can return promises accessible
to HTTP requests. From the code above, we’ve already bridged into our
Goblins actors. This is a pretty flexible bridge as this &lt;code&gt;^router&lt;/code&gt; actor just
takes in a request and provides a response, we could dispatch this request in
any number of ways. For our prototype, this is the approach we took:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-values&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;registry&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;locator&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;call-with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;spawn-nonce-registry-and-locator&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^router&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;request-url&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;request-uri&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-split&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;uri-path&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;request-uri&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#\/&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;static&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;static-file&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-append&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;mandy/web/static/&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;registry&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'fetch&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;base32-decode&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'request&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most web frameworks have a special-purpose routing language that uses strings,
which is an inexpressive &lt;a href=&quot;https://wiki.c2.com/?StringlyTyped&quot;&gt;anti-pattern&lt;/a&gt;.
We’re fortunate in Scheme to have a powerful
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Pattern-Matching.html&quot;&gt;pattern matcher&lt;/a&gt;
that we can use instead. In this case we’re matching a filename for our static
files and we’re also making some objects available at
&lt;code&gt;/object/&amp;lt;base32-encoded-id&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can see above the router we’re spawning something called the
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.17.0/Nonce-Registry.html&quot;&gt;nonce registry&lt;/a&gt;.
This is an actor which provides a mechanism to register any number of actors
against some identifier and look them up. The actor handles salting and hashing
the IDs and even persistence. This works great for registering ActivityPub
objects. Each one gets a unique ID which can be used for lookup later.&lt;/p&gt;
&lt;p&gt;These IDs are bytevectors, so we base32 encode them to convert them into a
textual form that can be included in a URI. We then just need to add a route to
our &lt;code&gt;match&lt;/code&gt; clause to look them up. You may notice we’re using &lt;code&gt;&amp;lt;-&lt;/code&gt; to send
messages which means we get a promise in return. This isn’t a problem for our
web server though as the &lt;code&gt;on&lt;/code&gt; handler will wait until it’s resolved.&lt;/p&gt;
&lt;p&gt;The prototype has more routes and handles slightly more situations than the
snippet of code shown above, but the principles introduced are the same.&lt;/p&gt;
&lt;h2&gt;How does ActivityPub work?&lt;/h2&gt;
&lt;p&gt;Let’s take a step back and look at ActivityPub itself both because how it
works will be useful to keep in mind the rest of the post, and to see if we can
see the actor model within the specification.&lt;/p&gt;
&lt;p&gt;ActivityPub is actually not too tricky. It has concepts like inboxes and
outboxes that you’re probably familiar with from email. It also has activities
which describe something the user is “doing”. Activities are the building block
of the protocol. Finally, it has objects which are things like Notes, Images,
Videos, etc.&lt;/p&gt;
&lt;p&gt;ActivityPub is actually two protocols in one. There’s the client-to-server
protocol and then the federated server-to-server protocol. These protocols are
actually very similar and for the most part mirror one another, but
unfortunately the client-to-server protocol gets little love from ActivityPub
implementations. Even so, let’s take a look at how I might go about posting a
&lt;code&gt;Note&lt;/code&gt; (think toot/tweet/microblog text of choice) to my good friend Christine:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;POST /outbox/ HTTP/1.1
Host: tsyesika.se
Authorization: Bearer XXXXXXXXXXX
Content-Type: application/ld+json; profile=&amp;quot;https://www.w3.org/ns/activitystreams&amp;quot;

{
    &amp;quot;@context&amp;quot;: &amp;quot;https://www.w3.org/ns/activitystreams&amp;quot;,
    &amp;quot;type&amp;quot;: &amp;quot;Create&amp;quot;,
    &amp;quot;to&amp;quot;: [&amp;quot;https://dustycloud.org/&amp;quot;],
    &amp;quot;object&amp;quot;: {
        &amp;quot;type&amp;quot;: &amp;quot;Note&amp;quot;,
        &amp;quot;content&amp;quot;: &amp;quot;Ohai Christine!&amp;quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JSON object above represents a
&lt;a href=&quot;https://www.w3.org/TR/activitypub/#create-activity-outbox&quot;&gt;&lt;code&gt;Create&lt;/code&gt; activity&lt;/a&gt;
which basically is posting something. Other activities might be &lt;code&gt;Like&lt;/code&gt;, &lt;code&gt;Share&lt;/code&gt;,
&lt;code&gt;Delete&lt;/code&gt;, etc. Most activities are transitive (the activity has an object) and
our &lt;code&gt;Create&lt;/code&gt; activity is no exception. The object inside is a &lt;code&gt;Note&lt;/code&gt; with some
content.&lt;/p&gt;
&lt;p&gt;The activity itself is posted to my outbox as an HTTP &lt;code&gt;POST&lt;/code&gt;. The server will
assign IDs to both objects and assign me (Jessica) as the author of the note. If
you were to do a &lt;code&gt;GET&lt;/code&gt; on the outbox, you’d see something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;GET /outbox/ HTTP/1.1
Host: tsyesika.se
Authorization: Bearer XXXXXXXXXXX
Content-Type: application/ld+json; profile=&amp;quot;https://www.w3.org/ns/activitystreams&amp;quot;

{
    &amp;quot;@context&amp;quot; : &amp;quot;https://www.w3.org/ns/activitystreams&amp;quot;,
    &amp;quot;id&amp;quot; : &amp;quot;https://tsyesika.se/outbox&amp;quot;,
    &amp;quot;type&amp;quot; : &amp;quot;OrderedCollection&amp;quot;,
    &amp;quot;name&amp;quot;: &amp;quot;Jessica's Outbox&amp;quot;
    &amp;quot;totalItems&amp;quot; : 1,
    &amp;quot;items&amp;quot;: {
        {
            &amp;quot;@context&amp;quot;: &amp;quot;https://www.w3.org/ns/activitystreams&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;Create&amp;quot;,
            &amp;quot;id&amp;quot;: &amp;quot;https://tsyesika.se/objects/79402654-a9e5-4356-a50d-5109fedbaacc&amp;quot;
            &amp;quot;actor&amp;quot;: &amp;quot;https://tsyesika.se&amp;quot;
            &amp;quot;to&amp;quot;: [&amp;quot;https://dustycloud.org/&amp;quot;],
            &amp;quot;object&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;Note&amp;quot;,
                &amp;quot;attributedTo&amp;quot;: &amp;quot;https://tsyesika.se&amp;quot;
                &amp;quot;id&amp;quot;: &amp;quot;https://tsyesika.se/objects/2f614e93-1fe7-4a8a-ba39-f9e4468ed77f&amp;quot;
                &amp;quot;content&amp;quot;: &amp;quot;Ohai Christine!&amp;quot;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reading from a user’s outbox gives you all the things they’ve posted (often it’s
paginated, but that was left unimplemented in the prototype). You can see that
the note we posted is the only item and the server has assigned IDs the
activity, note, and author.&lt;/p&gt;
&lt;p&gt;The server also should deliver this activity and note to Christine by looking
up her inbox and doing an HTTP &lt;code&gt;POST&lt;/code&gt; to it with the activity. I’m not going to
go any further into federation but it’s very similar to the client-to-server
API.&lt;/p&gt;
&lt;h2&gt;ActivityPub meets Goblins&lt;/h2&gt;
&lt;p&gt;If you’ve worked with Goblins or other actor frameworks you might be thinking
“these activities look an awful lot like messages between different objects” and
you’d be right.&lt;/p&gt;
&lt;p&gt;That &lt;code&gt;Create&lt;/code&gt; activity we posted above could be written something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;note&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^note&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:content&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;Ohai Christine!&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;tsyesika-outbox&lt;/span&gt;         &lt;span class=&quot;syntax-comment&quot;&gt;; The outbox
&lt;/span&gt;    &lt;span class=&quot;syntax-symbol&quot;&gt;'create&lt;/span&gt;                 &lt;span class=&quot;syntax-comment&quot;&gt;; A method called create
&lt;/span&gt;    &lt;span class=&quot;syntax-keyword&quot;&gt;#:object&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;note&lt;/span&gt;           &lt;span class=&quot;syntax-comment&quot;&gt;; Create an object
&lt;/span&gt;    &lt;span class=&quot;syntax-keyword&quot;&gt;#:to&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;christine&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;syntax-comment&quot;&gt;; Send it to Christine.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The outbox can then implement whatever things it needs to for its &lt;code&gt;Create&lt;/code&gt;
activity (assigning an ID), federating the post out, etc. Just like any other
Goblins actor would implement a method.&lt;/p&gt;
&lt;p&gt;The prototype I’ve built does a fairly simple transformation from the
unmarshalled JSON data. The JSON data is accepted and parsed to an association
list. Then there is then an unmarshalling step where any nested objects get
converted into their corresponding Goblins actors. The result is an activity
actor which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-actor&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^as2:activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:key&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;actor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;origin&lt;/span&gt;
                             &lt;span class=&quot;syntax-symbol&quot;&gt;instrument&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;extend-methods&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;parent&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;actor&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;actor&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;to-method&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-&amp;gt;symbol&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-downcase&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;assoc-ref&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'to-data&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;to-data&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;append&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;filter-data&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;actor&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,actor&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;object&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;target&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,target&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;result&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,result&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;origin&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,origin&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;instrument&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,instrument&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;parent&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'to-data&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ActivityPub is based on Activity Streams 2.0 which has a hierarchical structure.
The &lt;code&gt;Activity&lt;/code&gt; type extends from a base type of &lt;code&gt;Object&lt;/code&gt;. This is represented in
the Mandy prototype as &lt;code&gt;parent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can then use this very simple procedure which takes an activity and converts
it to a message:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define*&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;activity-&amp;gt;message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:key&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;send-to&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;method-name&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'to-method&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;object&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;to&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;send-to&lt;/span&gt;
        &lt;span class=&quot;syntax-symbol&quot;&gt;send-to&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;to&lt;/span&gt;
        &lt;span class=&quot;syntax-symbol&quot;&gt;method-name&lt;/span&gt;
        &lt;span class=&quot;syntax-keyword&quot;&gt;#:object&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'object&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-keyword&quot;&gt;#:actor&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'actor&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-keyword&quot;&gt;#:target&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'target&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-keyword&quot;&gt;#:self&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;activity&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces our message as a list with its method name and a bunch of keyword
arguments. The methods can then be defined as normal Goblins methods. If all the
keywords aren’t needed for that method behavior, it can include
&lt;code&gt;#:allow-other-keys&lt;/code&gt; so that everything else can be ignored.&lt;/p&gt;
&lt;p&gt;As an example of this let’s see the &lt;code&gt;Collection&lt;/code&gt; type which is basically a list
of objects. Here’s the implementation in the prototype:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;(define* (^as2:collection bcom parent #:optional [items (make-gset)])
  (extend-methods parent
    ((add #:key object #:allow-other-keys)
     (bcom (^as2:collection bcom parent (gset-add items object))))
    ((remove #:key object #:allow-other-keys)
     (bcom (^as2:collection bcom parent (gset-remove items object))))
    ((move #:key object target #:allow-other-keys)
     (define new-items (gset-remove items object))
     ($ target 'add #:object object)
     (bcom (^as2:collection bcom parent new-items)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see it supports &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt; and &lt;code&gt;move&lt;/code&gt; methods. The specification
&lt;a href=&quot;https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add&quot;&gt;defines the behavior of add&lt;/a&gt;
as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Indicates that the &lt;code&gt;actor&lt;/code&gt; has added the &lt;code&gt;object&lt;/code&gt; to the &lt;code&gt;target&lt;/code&gt;. If the
&lt;code&gt;target&lt;/code&gt; property is not explicitly specified, the target would need to be
determined implicitly by context. The origin can be used to identify the
context from which the &lt;code&gt;object&lt;/code&gt; originated.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this case, there might be two things which would be important to this method,
the first being the &lt;code&gt;#:object&lt;/code&gt; keyword and the second being the &lt;code&gt;#:target&lt;/code&gt;.
Since the collection is being sent the &lt;code&gt;add&lt;/code&gt; message, it’s being assumed in the
above code that the sender has figured the collection out. The &lt;code&gt;add&lt;/code&gt; method
then just needs to care about the object, in which case it specifies object as
the only key and ignores anything else. Finally, the behavior is straightforward
it adds the object to the collection.&lt;/p&gt;
&lt;p&gt;Hopefully the above shows how we can take these ActivityPub activities and
transform them into Goblins messages. This gives us the desired Goblins
ergonomics while implementing ActivityPub objects.&lt;/p&gt;
&lt;h2&gt;Going further&lt;/h2&gt;
&lt;p&gt;The prototype that I implemented was a demo trying to explore some of both the
Goblins actor HTTP interface and how ActivityPub might be implemented in an
actor framework like Goblins. Having helped co-author ActivityPub and then
develop Goblins, I’ve had musings of how this implementation might look, but
it’s been very exciting to see that they work in practice.&lt;/p&gt;
&lt;p&gt;This demo has explored both a HTTP interface with Goblins actors and
ActivityPub. I think each one has a lot of potential for future work and I’d
love to see Goblins applied in building websites. Goblins could be used both to
build the backend of websites by handling the HTTP requests themselves, and in
the browser by using &lt;a href=&quot;https://spritely.institute/hoot/&quot;&gt;Hoot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There’s a many aspects of an ActivityPub implementation left to explore, for
instance Goblins’ persistence system would be well suited to be our database. We
could explore adding federation (Goblins being a distributed framework would be
well suited to implement). Hopefully in the future we’ll be able to build on
this experiment more.&lt;/p&gt;
&lt;p&gt;If you found this blog post interesting, both myself and Christine
Lemmer-Webber will be giving a &lt;a href=&quot;https://fosdem.org/2026/schedule/event/HVJRNV-how_to_level_up_the_fediverse/&quot;&gt;FOSDEM 2026
talk&lt;/a&gt;
on this. We’d love to see you there if you’re attending, but if not
the video will be posted shortly after the event.&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alex Sassmannshausen&lt;/li&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Brian Neltner&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Danny OBrien&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Bavier&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Joshua Simmons&lt;/li&gt;
&lt;li&gt;Justin Sheehy&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Aria Stewart&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;James Smith&lt;/li&gt;
&lt;li&gt;Jamie Baross&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Michael Orbinpost&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Rodion Goritskov&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stefan Magdalinski&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;chee rabbits&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Composing capability security and conflict-free replicated data types</title><id>https://spritely.institute/news/composing-capability-security-and-conflict-free-replicated-data-types.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-11-24T12:00:00Z</updated><link href="https://spritely.institute/news/composing-capability-security-and-conflict-free-replicated-data-types.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat.png&quot; alt=&quot;Various personified brassicas chatting&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;In August, I attended the &lt;a href=&quot;https://dwebseminar.org/&quot;&gt;DWeb Seminar&lt;/a&gt;
where a small group of builders gathered to discuss the
state-of-the-art and open problems in the distributed web space.  Some
in the group are primarily concerned with &lt;em&gt;distributed data&lt;/em&gt; and focus
on sync algorithms and local-first use cases.  I am mainly concerned
with &lt;em&gt;distributed behavior&lt;/em&gt; and focus on the object capability
security model.  Both areas of study are steeped in their own lore and
research papers, which makes it difficult for the two camps to
communicate effectively with each other.&lt;/p&gt;
&lt;p&gt;It is in the interest of unity that I write this blog post.  I will
show how distributed behavior and data techniques can be composed to
build local-first applications that combine the strengths of each
paradigm.&lt;/p&gt;
&lt;p&gt;Fortunately, the distinction between behavior and data is a false
dichotomy; they are two sides of the same coin.  This circular
relationship is well understood in the Lisp world where we say that
“code is data and data is code” in reference to Lisp’s
&lt;a href=&quot;https://en.wikipedia.org/wiki/Homoiconicity&quot;&gt;homoiconic&lt;/a&gt; syntax.&lt;/p&gt;
&lt;p&gt;Messages are both behavior and data.  They invoke behavior but are
also encoded as a string of bytes and sent across the wire.  Our
context within the tower of abstraction determines how we look at a
message.  What is treated as data at one abstraction level may be
treated as behavior in another.  We need to be equipped to handle both
cases.&lt;/p&gt;
&lt;p&gt;I’ve crystalized all that I’ve learned recently into a small prototype
that combines the following techniques:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On the behavior side, &lt;a href=&quot;https://en.wikipedia.org/wiki/Object-capability_model&quot;&gt;object
capabilities&lt;/a&gt;
(ocaps) and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;actor
model&lt;/a&gt;, both of which are
common subjects on this blog.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the data side, &lt;a href=&quot;https://crdt.tech&quot;&gt;conflict-free replicated data
types&lt;/a&gt; (CRDTs) and &lt;a href=&quot;http://wiki.erights.org/wiki/Capability-based_Active_Invocation_Certificates&quot;&gt;authorization/certificate
capabilities&lt;/a&gt;
(zcaps).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Local-first chat again&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-oleracea.jpg&quot; alt=&quot;There are no new vegetables, just new brassica oleraceavarieties&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;I started down the well-trodden path of making a local-first group
chat application.  Seemingly every other DWeb-adjacent project has
one, after all, so why shouldn’t Spritely?  I then branched off and
went down my own trail, trying to compose tools in ways I hadn’t quite
seen before in this context.  The result is &lt;a href=&quot;https://codeberg.org/spritely/brassica-chat&quot;&gt;Brassica
Chat&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Brassica Chat is written in Scheme, is built upon
&lt;a href=&quot;https://spritely.institute/goblins&quot;&gt;Goblins&lt;/a&gt;, our distributed
programming environment, and uses
&lt;a href=&quot;https://spritely.institute/hoot&quot;&gt;Hoot&lt;/a&gt; to compile it all to
WebAssembly so it can be used on the web.  Besides just posting
messages, it also supports some of the usual features like
editing/removing messages and emoji reacts. 🚀&lt;/p&gt;
&lt;h2&gt;Demo time!&lt;/h2&gt;
&lt;p&gt;Below is an embedded and simplified demo of Brassica Chat that
simulates a conversation between Alice, Bob, and Carol.  Messages are
sent over a fake network and each user’s network access can be toggled
with a button to simulate network partitions and offline usage.  Alice
is the chat room creator and has the privilege to edit/remove any
post.  Bob and Carol can only edit/remove their own posts.  Okay,
hopefully that’s enough context.  Try it out!&lt;/p&gt;
&lt;iframe src=&quot;https://files.spritely.institute/embeds/brassica-chat&quot; frameborder=&quot;0&quot; style=&quot;display: block; margin: auto; width: 100%; height: 60rem;&quot;&gt;
&lt;/iframe&gt;
&lt;p&gt;If you’d like more screen real estate, &lt;a href=&quot;https://files.spritely.institute/embeds/brassica-chat&quot;&gt;try this demo on its own
dedicated web
page&lt;/a&gt;.  Check
out &lt;a href=&quot;https://codeberg.org/spritely/brassica-chat/src/branch/main/embed.scm&quot;&gt;the source
code&lt;/a&gt;
if you’d like.&lt;/p&gt;
&lt;h2&gt;High-level design&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-unum.png&quot; alt=&quot;Brassica Chat unum diagram&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Let’s examine the scenario modeled in the demo more closely.  First,
Alice creates a new chat room on her computer.  She then shares a
capability with her friend Bob and another (distinct) capability with
Carol that grants them the privilege to send messages to her chat
room.  Bob and Carol reciprocate by giving Alice capabilities to their
respective chat room copies.  The resulting network is shown in the
diagram above.&lt;/p&gt;
&lt;p&gt;Note that Bob and Carol are not directly connected to each other but
rather indirectly connected through Alice.  This is because Bob and
Carol did not exchange capabilities with each other.  This is okay!
They can all still chat with each other in real time as long as Alice
is online.  When Alice goes offline, Bob and Carol can still send
messages locally.  Everything done while in offline mode will be
synchronized once Bob and Carol can connect to Alice again.  Perhaps
Bob and Carol will exchange capabilities with each other later so they
can still chat in real time when Alice is offline.  The important
detail is that Brassica Chat does not try to wire everyone together
directly without the &lt;em&gt;active consent&lt;/em&gt; of its users.&lt;/p&gt;
&lt;p&gt;Each user in the system has a cryptographic identity in the form of a
public/private key pair.  This key is used for signing messages.  In
addition to the key, an identity also contains a human-readable,
self-proposed name for displaying in the user interface.&lt;/p&gt;
&lt;p&gt;Each chat room is an &lt;em&gt;eventually-consistent replica&lt;/em&gt; of the
distributed chat room state managed using a collection of CRDTs.  Chat
rooms can propagate locally created or remotely received messages to
other replicas of the chat room for which it holds a capability.  The
replication process works to eventually achieve convergence across all
reachable replicas.&lt;/p&gt;
&lt;p&gt;At a meta level, these replicas can be thought of as forming a single,
conceptual chat room actor.  To use some ocap jargon, the chat room is
an &lt;a href=&quot;https://habitatchronicles.com/2019/08/the-unum-pattern/&quot;&gt;unum&lt;/a&gt;
where each &lt;em&gt;presence&lt;/em&gt; (replica) communicates by broadcasting messages
to the other presences it knows about.  In the diagram above, there’s
a dotted line drawn around the three replicas to indicate that the
chat room is an &lt;em&gt;abstract&lt;/em&gt; entity whose canonical form does not live
on any single machine.  The presences are all &lt;em&gt;co-equal&lt;/em&gt;; no single
presence has more privilege than any other.&lt;/p&gt;
&lt;h2&gt;The stack&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-layers.png&quot; alt=&quot;Brassica Chat layers diagram&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;There are four levels of abstraction in the Brassica Chat
architecture.  From bottom to top, they are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Object capabilities&lt;/strong&gt;: online access control through reference
passing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actors&lt;/strong&gt;: online, asynchronous messaging through object references.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRDTs&lt;/strong&gt;: eventually consistent, offline messaging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization capabilities&lt;/strong&gt;: offline access control through
certificate chains.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All objects in the application are represented as actors, including
CRDTs.  Implementing CRDTs as actors has been done elsewhere,
&lt;a href=&quot;https://doc.akka.io/libraries/akka-core/current/typed/distributed-data.html#replicated-data-types&quot;&gt;Akka&lt;/a&gt;
being a notable example.&lt;/p&gt;
&lt;p&gt;A reference to an actor is an &lt;strong&gt;object capability&lt;/strong&gt;.  In other words,
holding a reference to an actor gives you the authority to send
messages to it.  An actor needs to be online in order to receive
messages, however.  For offline usage, an object capability variant
known as an &lt;strong&gt;authorization or certificate capability&lt;/strong&gt; is used, as
well.&lt;/p&gt;
&lt;p&gt;Messages are sent between machines using the &lt;a href=&quot;https://ocapn.org&quot;&gt;Object Capability
Network&lt;/a&gt; (OCapN) protocol, which handles the burden
of secure message transport.  Messages can be transported over any
medium with an associated OCapN netlayer.  For this prototype, I used
a
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API&quot;&gt;WebSocket&lt;/a&gt;
netlayer with a relay in the middle.  The CRDT implementation has its
own messaging protocol which is defined using actors so that it
automatically works over OCapN.&lt;/p&gt;
&lt;h2&gt;On capabilities&lt;/h2&gt;
&lt;p&gt;Brassica Chat’s use of capabilities stands in contrast to most
existing local-first applications that use the access-control list
(ACL) model.  In the ACL model, users are associated with groups or
roles that grant privileges.  When compared to capabilities, the ACL
model has many deficiencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ACLs are too coarse-grained.  It’s difficult to follow the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Principle_of_least_privilege&quot;&gt;principle of least
authority&lt;/a&gt;
with a limited set of role-based privilege levels so the norm is for
users to have more privilege than is necessary.  By contrast,
capabilities can be arbitrarily fine-grained.  Want to make it so
that Bob can only moderate Carol’s posts and not Alice’s?  It’s easy
and natural to make a capability for this but awkward to define a
one-off ACL role.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ACLs can’t be safely delegated.  Only an administrator may grant or
revoke privileges.  As a non-admin, your only option is to share
your credentials, which is unsafe and hard to audit.  Credential
sharing happens often in the real world due to the friction involved
in doing things “the right way”.  With capabilities, it is easy to
delegate a &lt;em&gt;subset&lt;/em&gt; of your authority to someone else in an
auditable, revokable manner without sharing your own credentials or
communicating with a central authority.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most importantly, ACLs have &lt;a href=&quot;https://waterken.sourceforge.net/aclsdont/current.pdf&quot;&gt;inherent
vulnerabilities&lt;/a&gt;,
such as the &lt;a href=&quot;https://en.wikipedia.org/wiki/Confused_deputy_problem&quot;&gt;confused deputy
problem&lt;/a&gt;.
The “if you don’t have it, you can’t use it” approach of
capabilities avoids an entire class of security bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, capabilities are safer, more expressive, and more
decentralized than ACLs.  Now, let’s move on to some implementation
details.&lt;/p&gt;
&lt;h2&gt;The chat room actor&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-screenshot.png&quot; alt=&quot;Screenshot of a chat application where Alice, Bob, and Carol are talking&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;The chat room actor is implemented as a composition of several CRDTs.
Rather than using one giant CRDT for the entirety of a chat room’s
history, it is partitioned by time into a set of chat log CRDT actors.
Each partition covers some uniform number of seconds of real time
known as the “period”.  This means that all presences must use the
same period value in order to converge properly (30 minutes was chosen
as a reasonable default).  The benefit of this partitioning strategy
is that it allows each replica to perform garbage collection (GC) on
entire chunks of history without coordinating with the other replicas
(GC within a CRDT requires coordination).  This ought to keep the
append-only log for any individual chunk of history quite small and
manageable.  Rebuilding the state of a previously deleted chunk from
scratch shouldn’t take much time, assuming there is another replica
online with the data.  For this prototype I didn’t bother to GC old
message history as the chat rooms are ephemeral and not persisted to
disk (but we could use Goblins’ &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/latest/Persistence.html&quot;&gt;persistence
API&lt;/a&gt;
to do so in the future).&lt;/p&gt;
&lt;p&gt;In addition to the message log partitions, there are two additional
CRDT actors that make up the chat room: profiles and certificates.
The profiles CRDT contains a mapping from a user’s public key to their
self-proposed display name (and could later be extended to include
other metadata that a user would like to share with the room).  The
certificates CRDT contains the set of all zcaps that have been issued
for the chat room.&lt;/p&gt;
&lt;h2&gt;The CRDT actors&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-crdt-diagram.png&quot; alt=&quot;Simple chat log CRDT diagram&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;CRDTs can be roughly divided into two categories: state-based or
operation-based.  Brassica Chat uses &lt;strong&gt;operation-based CRDTs&lt;/strong&gt;, which
can be thought of like a Git repository with automatic conflict
resolution.  Each replica of an operation-based CRDT maintains an
event log containing all of the operations that have occurred.  Due to
concurrency in distributed systems, an event may have one or more
direct causal predecessors (a fancy term for “parents”).  Thus, the
log entries form an append-only, &lt;a href=&quot;https://en.wikipedia.org/wiki/Directed_acyclic_graph&quot;&gt;&lt;strong&gt;directed acyclic
graph&lt;/strong&gt;&lt;/a&gt; (DAG),
as shown in the diagram above.&lt;/p&gt;
&lt;p&gt;An event has the following immutable fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ID&lt;/strong&gt;: Unique ID of the event (SHA-256 hash).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parent IDs&lt;/strong&gt;: IDs of all causal predecessors (forming a DAG).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timestamp&lt;/strong&gt;: Timestamp from a &lt;a href=&quot;https://jaredforsyth.com/posts/hybrid-logical-clocks/&quot;&gt;hybrid logical
clock&lt;/a&gt;
indicating when the event occurred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Author&lt;/strong&gt;: Creator of the event (ed25519 public key).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signature&lt;/strong&gt;: Crytographic signature of the event.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blob&lt;/strong&gt;: &lt;a href=&quot;https://github.com/ocapn/syrup&quot;&gt;Syrup&lt;/a&gt; encoded event data
(Syrup is the binary serialization format used by OCapN).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Events are delivered in &lt;em&gt;causal order&lt;/em&gt;, meaning that an event is not
applied to the CRDT’s internal state until all of its predecessor
events have been applied.  Concurrent events may be applied in any
order, so it’s important that operations on the CRDT state are
&lt;em&gt;commutative&lt;/em&gt;.  Despite causal order being encoded in the event graph,
a logical timestamp is included in each event.  This is important for
handling concurrent events and is used to implement common CRDT
patterns like the &lt;a href=&quot;https://crdt.tech/glossary&quot;&gt;“last write wins”&lt;/a&gt;
register.&lt;/p&gt;
&lt;p&gt;Brassica Chat contains a generic operation-based CRDT actor with
&lt;code&gt;prepare&lt;/code&gt;, &lt;code&gt;effect&lt;/code&gt; and &lt;code&gt;query&lt;/code&gt; hooks (straight out of the CRDT
literature) for special-purpose CRDTs to implement.  The CRDT actor is
used as the basis for the chat log, certificates, and profiles actors.&lt;/p&gt;
&lt;p&gt;This CRDT implementation, though on the simple side, is &lt;a href=&quot;https://en.wikipedia.org/wiki/Byzantine_fault&quot;&gt;&lt;strong&gt;Byzantine
fault tolerant&lt;/strong&gt;&lt;/a&gt;.  A
Byzantine fault is best explained by the following scenario: Mallet, a
user who is up to no good, sends Alice and Bob an event with the &lt;em&gt;same
ID but different contents&lt;/em&gt;.  When Alice and Bob sync data with each
other, they ignore events with IDs that they already have and don’t
realize that Mallet has tricked them.  The result is that Alice and
Bob will never converge to the correct state because their message
logs contain different operations.&lt;/p&gt;
&lt;p&gt;Divergence due to Byzantine behavior is prevented through
&lt;strong&gt;content-addressing&lt;/strong&gt; and &lt;strong&gt;cryptographic signing&lt;/strong&gt; of events, much
like Git, as described in Martin Kleppmann’s &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3517209.3524042&quot;&gt;“Making CRDTs Byzantine
Fault Tolerant”&lt;/a&gt;
paper.  Mallet cannot send Alice and Bob events with the same ID but
different contents because &lt;em&gt;the ID is the hash of the contents&lt;/em&gt; and if
the hash doesn’t match then the event is rejected.  Events are signed
to associate them with the author for use with the authorization
capability system and the parent IDs are incorporated into the
signature to prevent replay attacks.  For this prototype, SHA-256 was
chosen for the hash function and ed25519 for signatures.&lt;/p&gt;
&lt;p&gt;Any number of Byzantine replicas may be in the network, but as long as
Alice and Bob can directly connect to each other, or indirectly
connect through a non-Byzantine node such as Carol, the well-behaved
nodes will eventually converge to the correct state.  While not
implemented in this prototype, detection of Byzantine behavior from a
replica could be used as the basis for revoking the object capability
being used to send such messages, adding a layer of accountability to
the system.&lt;/p&gt;
&lt;h2&gt;Authorization capabilities&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-zcap-diagram.png&quot; alt=&quot;Certficiate chain diagram&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;With CRDTs in the mix as an offline messaging layer, object
capabilities alone are insufficient for access control.  The ocap
layer controls access to synchronize chat messages between two
replicas but it does not (and cannot) control what those messages
contain.  Why is that?  Because the chat messages are at a higher
level of abstraction than the actor messages for which the ocaps
apply.  When Bob writes the message &lt;code&gt;(react alice-message-1 &amp;quot;👋&amp;quot;)&lt;/code&gt; to
his local replica, he is sending a message to the &lt;em&gt;abstract chat room&lt;/em&gt;
that doesn’t exist in any single location.  What if Alice wanted to
prevent Bob from reacting to messages?  Who even has the authority to
impose that restriction when there’s no central server?  We’ve traded
away strong consistency to support local-first usage, so there is no
way for an adminstrator to install an ocap on all replicas such that
they are all guaranteed to reject this message from Bob and converge
to the same state.  Ocaps are &lt;em&gt;online&lt;/em&gt; capabilities, but CRDTs use
&lt;em&gt;offline&lt;/em&gt; messaging.  We need an &lt;em&gt;offline capability&lt;/em&gt; that can be used
to process the offline messages.&lt;/p&gt;
&lt;p&gt;This is where &lt;strong&gt;authorization capabilities&lt;/strong&gt; (zcaps) come in.  A zcap
is a signed certificate that describes what actions a controller of
that certificate may perform.  Like ocaps, zcaps support &lt;em&gt;delegation&lt;/em&gt;
which is represented as a chain of signed certificates.  A crucial
property of a delegated zcap is that it cannot expand privilege, only
reduce it.  Certificate chains need to bottom out somewhere, so we
need to decide upon a root signer.  In Brassica Chat, the initiator of
the chat room (Alice in our example scenario) is considered to be the
root signer for all zcaps used in the chat room.  This is just a
convention, though, and a user could decide to place their trust in a
different root signer.&lt;/p&gt;
&lt;p&gt;Certificates in Brassica Chat are inspired by
&lt;a href=&quot;https://w3c-ccg.github.io/zcap-spec/&quot;&gt;ZCAP-LD&lt;/a&gt; and are composed of
the following immutable fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ID&lt;/strong&gt;: Unique ID of the certificate (SHA-256 hash).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parent ID&lt;/strong&gt;: ID of the previous certificate in the delegation
chain.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signer&lt;/strong&gt;: The public key used to sign the certificate.  The signer
&lt;em&gt;must&lt;/em&gt; be a controller of the parent certificate to be considered
valid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controllers&lt;/strong&gt;: A list of public keys for the users who are allowed
to invoke the capabilities of this certificate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predicate&lt;/strong&gt;: An expression that constrains (or &lt;em&gt;attenuates&lt;/em&gt;, to
use the ocap term) the capabilities granted by the parent
certificate.  For example, the expression &lt;code&gt;(when-op (edit delete) (allow-self))&lt;/code&gt; says that &lt;code&gt;edit&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; operations can only be
used on posts authored by the user invoking the capability (one of
the controllers).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Certificates also carry one piece of mutable state: a flag that the
signer can flip from false to true to revoke the certificate.
Revocation cannot be reversed, making this a trivially monotonic
operation within the certificates CRDT.&lt;/p&gt;
&lt;p&gt;At first glance, zcaps might appear to have the same problem as ocaps:
a zcap cannot prevent Bob from sending a message that is not permitted
because there’s no strong consistency.  Instead, zcaps specify the
rules by which well-behaved clients should &lt;em&gt;interpret&lt;/em&gt; the events that
have occurred.  For example, Bob can send a message that edits the
contents of Carol’s post, but if the zcap Bob used for that operation
does not grant the capability to edit posts authored by Carol then
that edit will simply be ignored when updating the chat room state on
a given replica.  Since zcaps are encoded as certificate documents,
they can be synced amongst all replicas so that the user interface can
eventually render the correct view of the chat room.  This is a good
example of something treated as data at one level of abstraction but
behavior at another.&lt;/p&gt;
&lt;h2&gt;Security considerations&lt;/h2&gt;
&lt;p&gt;The security implications of sharing a capability to a chat room are
rather large.  If Alice, Bob, and Carol have replicas of the same chat
room then sending Alice a message means indirectly sending Bob and
Carol messages, too.  Each presence of the chat room is co-equal with
all other presences, after all.  As a consequence, we cannot perform
administration in a centralized manner like we could if there was a
single canonical chat room actor living on a single machine.
Revocation, for example, is now a communal effort.  If Mallet can
propagate messages through Bob and Carol (because Mallet holds a
capability to both) then Bob and Carol must each revoke their
respective capabilities in order to prevent Mallet from sending
messages to the chat room in the future.  While it’s possible to
create a zcap that would cause Mallet’s messages to be ignored by
clients, it doesn’t change the fundamental truth that Mallet has the
capability to send messages to the chat room until such a time that
all previously issued ocaps have been revoked.  The formation of
complete networks, where each replica holds a capability to sync with
every other replica, is thus discouraged in this design.  The
connectedness of a replica is a function of how trusted the user of
that replica is in the real world social group.  The more strongly
connected a user is, the harder it becomes to remove them later if the
social dynamic changes.  There is a tension between the risk imposed
by a strongly connected network and the desire to maximize
availability of the chat room for online users.&lt;/p&gt;
&lt;p&gt;The overall security goal for this prototype was to prevent Mallet
from irreparably destroying the shared state of the chat room, which
was achieved through Byzantine fault tolerance.  Additionally, message
signing and zcaps provide a means of holding Mallet accountable for
anti-social/malicious actions that the system is technically incapable
of preventing, giving users some agency over what they see in their
client interface.  &lt;em&gt;Is this good enough?&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Things left undone&lt;/h2&gt;
&lt;p&gt;This prototype was focused on exploring the core of a minimally viable
p2p chat built on capability security principles.  It is not
production software.  I did not concern myself with optimal bandwidth
nor memory usage.  As mentioned earlier, chat history is not even
saved to disk.&lt;/p&gt;
&lt;p&gt;Some areas for improvement are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Decentralized identity and naming.  This was deliberately left out
to keep the scope of this experiment manageable.  Spritely has
another project, codenamed Brux, to explore this topic.  See also
our paper on
&lt;a href=&quot;https://files.spritely.institute/papers/petnames.html&quot;&gt;petnames&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ergonomic UI/UX for the complexity introduced by decentralization
and eventual consistency.  What’s a user-friendly way to add and
revoke ocaps and zcaps?  The UI doesn’t even attempt to allow
viewing or editing zcaps right now.  How can we clearly communicate
what the security properties are/aren’t so that users don’t get
false impressions?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;History rewriting.  If Mallet writes some truly terrible content to
the append-only chat log, it’s stuck in there even if it’s hidden in
the user interface.  Introducing some amount of synchronization to
deal with this scenario seems okay.  We could take inspiration from
Git where the commit graph is append-only but branch names are
mutable pointers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Preventing new members from reading past messages like in Signal
groups.  This should be an option like it is in other secure chat
programs, but it’s a complex topic and exploring it was out of
scope.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/brassica-chat-screenshot-2.png&quot; alt=&quot;Another Brassica Chat screenshot&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;I hope this was an interesting walkthrough of how ocaps, actors,
CRDTs, and zcaps can be composed with each other!  Big thanks to the
DWeb Seminar organizers for providing the spark of inspiration I
needed to dive into the CRDT literature and build this prototype.&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alex Sassmannshausen&lt;/li&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brian Neltner&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Danny OBrien&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Bavier&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Joshua Simmons&lt;/li&gt;
&lt;li&gt;Justin Sheehy&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Shane Redman&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Alexander Poylisher&lt;/li&gt;
&lt;li&gt;Aria Stewart&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;James Smith&lt;/li&gt;
&lt;li&gt;Jamie Baross&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Rodion Goritskov&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stefan Magdalinski&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;chee rabbits&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>GoblinShare: Secure, Peer-to-Peer File-Sharing with Goblins</title><id>https://spritely.institute/news/goblinshare-secure-peer-to-peer-file-sharing-with-goblins.html</id><author><name>Juliana Sims</name><email>contact@spritely.institute</email></author><updated>2025-11-18T12:00:00Z</updated><link href="https://spritely.institute/news/goblinshare-secure-peer-to-peer-file-sharing-with-goblins.html" rel="alternate" /><content type="html">&lt;p&gt;This year, instead of participating in the &lt;a href=&quot;https://itch.io/jam/autumn-lisp-game-jam-2025&quot;&gt;Autumn Lisp Game
Jam&lt;/a&gt;, Spritely decided to make
some non-game demos to show off our tech.  To that end, I spent the week of
November 3rd writing &lt;a href=&quot;https://codeberg.org/spritely/goblinshare&quot;&gt;GoblinShare&lt;/a&gt;, a
secure, peer-to-peer file-sharing utility using the Guile port of
&lt;a href=&quot;https://codeberg.org/spritely/magenc&quot;&gt;Magenc&lt;/a&gt; for storage and distribution,
relying on &lt;a href=&quot;https://spritely.institute/goblins&quot;&gt;Goblins&lt;/a&gt; for the peer-to-peer
connection abstraction, and delivered over &lt;a href=&quot;https://torproject.org/&quot;&gt;Tor&lt;/a&gt;.
Thanks to Goblins, this turned out to be super easy to implement.  Let me show
you how!&lt;/p&gt;
&lt;h2&gt;🎶️ Demos; glorious demos! 🎶️&lt;/h2&gt;
&lt;p&gt;We at Spritely absolutely &lt;strong&gt;love&lt;/strong&gt; our technology demos.  We know it can be
tricky to understand an unfamiliar paradigm, but we also think &lt;a href=&quot;https://dustycloud.org/blog/if-you-cant-tell-people-anything/&quot;&gt;hands-on demos
help&lt;/a&gt;.  To that
end, we build &lt;a href=&quot;https://codeberg.org/spritely/community-garden&quot;&gt;lots&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/spritely/goblin-chat&quot;&gt;of&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/spritely/fantasary&quot;&gt;usable&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/spritely/cirkoban&quot;&gt;demos&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/spritely/terminal-phase&quot;&gt;to&lt;/a&gt;
&lt;a href=&quot;https://codeberg.org/spritely/goblinville&quot;&gt;try&lt;/a&gt;.  We particularly like building
games during the Lisp Game Jam so we can connect with the broader Lisp
community, encourage more developers to use our technology, and end up with
something fun that people &lt;em&gt;want&lt;/em&gt; to use.  After all, we can't build the future
of the social web all by ourselves!  This year, though, we decided to focus not
on showing off work we've already done, but pushing our work in a new direction;
and it felt best to do that outside the context of the jam.&lt;/p&gt;
&lt;p&gt;My assignment (which I &lt;em&gt;did&lt;/em&gt; choose to accept) was tripartite: port Magenc to
Guile, implement a simple file-sharing tool (which I decided to somewhat model
on &lt;a href=&quot;https://github.com/magic-wormhole/magic-wormhole&quot;&gt;Magic Wormhole&lt;/a&gt;), and port
&lt;a href=&quot;https://codeberg.org/spritely/racket-crystal&quot;&gt;Crystal&lt;/a&gt; to Guile.  The Crystal
work remains ahead, but I've &lt;a href=&quot;https://codeberg.org/spritely/magenc&quot;&gt;ported
Magenc&lt;/a&gt; and built
&lt;a href=&quot;https://codeberg.org/spritely/goblinshare&quot;&gt;GoblinShare&lt;/a&gt; as the file-sharing
tool.  I treated the Magenc port as prep work and GoblinShare proper as the
equivalent of a jam entry, so that will be the focus of this post.  First,
though, I'll provide an overview of these two projects, what they do, and how
they do it.&lt;/p&gt;
&lt;h2&gt;Magenc&lt;/h2&gt;
&lt;p&gt;Magenc is an encrypted, distributed, content-addressed data store relying on
magnet URLs as capabilities, inspired by
&lt;a href=&quot;https://tahoe-lafs.org/trac/tahoe-lafs&quot;&gt;Tahoe-LAFS&lt;/a&gt;.  It does not use Goblins,
but it is built with capability security concepts in mind.  It consists of three
components, each functioning as a subcommand under a single program: &lt;code&gt;magenc serve&lt;/code&gt;, which starts a server; &lt;code&gt;magenc put&lt;/code&gt;, which encrypts and &lt;code&gt;POST&lt;/code&gt;s files to
a server; and &lt;code&gt;magenc get&lt;/code&gt;, which &lt;code&gt;GET&lt;/code&gt;s files from a server.  (Although the
architecture is designed to support arbitrary remote stores, only a web store
using HTTP has been implemented so far.)&lt;/p&gt;
&lt;h3&gt;A quick summary&lt;/h3&gt;
&lt;p&gt;The core of Magenc's functionality is the magnet URL, which looks like
&lt;code&gt;magnet://?xt=&amp;lt;url-encoded-urn&amp;gt;&amp;amp;ek=&amp;lt;base64-url-encoded-string&amp;gt;&amp;amp;es=&amp;lt;string&amp;gt;&lt;/code&gt; and
uniquely identifies a specific file.  Decomposing the query parameters
(everything after &lt;code&gt;?&lt;/code&gt;), &lt;code&gt;xt&lt;/code&gt; refers to the &lt;em&gt;exact topic&lt;/em&gt; identifying a specific
file, which is simply the sha256 hash of the encrypted manifest (which may be a
raw object as discussed below).  &lt;code&gt;ek&lt;/code&gt; is the encryption key used to encrypt the
file.  &lt;code&gt;es&lt;/code&gt; is the encryption suite used for encryption.  The Guile port of
Magenc uses AES in Galois/Counter Mode (AES-GCM) for encryption; the Racket
version uses AES in Counter Mode (AES-CTR).  Of this information, only the exact
topic is ever known by the server.  Together, the information in a magnet URL is
both all that is needed and everything that is required to identify, access, and
decrypt a file on a given server.  This makes a magnet URL a &lt;em&gt;capability&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The server only has access to encrypted binary data a client sends.  When the
server receives a &lt;code&gt;POST&lt;/code&gt; request with attached binary data, it hashes the
provided data using sha256, encodes the unhashed data using base64, and stores
the encoded data in a key-value store where the hash ‒ the &lt;em&gt;exact topic&lt;/em&gt; ‒ is
the key.  It then converts the exact topic into a URI object from Guile's &lt;code&gt;(web uri)&lt;/code&gt; module before sending the URI back in the &lt;code&gt;content-location&lt;/code&gt; field of the
response object.  As a safety redundancy, the client checks to make sure that
the exact topic it gets back is what it expects.  When the server receives a
&lt;code&gt;GET&lt;/code&gt; request with an exact topic in the &lt;code&gt;content-location&lt;/code&gt; field, it looks up
the associated data, decodes the base64 into binary data, and sends that back as
the body of a response.  That's all the server knows and does; everything else
is handled by the client.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;magenc put&lt;/code&gt; handles the encryption and generation of cryptographic inputs.
When passed a file (and, optionally, server URL), Magenc first chunks the file
as necessary, encrypting each chunk to transmit separately so that the server
does not necessarily know the chunks are related (though correlation of
connections and network traffic by a malicious server or observer could be used
to reliably guess interrelation).  Then, the client prints a magnet URL which
identifies the entrypoint to retrieve the file ‒ a &amp;quot;raw&amp;quot; object if the file fits
in one chunk or a &amp;quot;manifest&amp;quot; if it doesn't.  As discussed above, the magnet URL
also embeds cryptographic information.&lt;/p&gt;
&lt;p&gt;A file's magnet URL can be used with &lt;code&gt;magenc get&lt;/code&gt; to retrieve that file.  When
passed a magnet URL (and, if necessary, server URL), &lt;code&gt;magenc get&lt;/code&gt; first
retrieves and decrypts the object identified by the exact topic.  If the object
is a &amp;quot;raw&amp;quot; object, Magenc extracts the binary data, decrypts it, and writes it
to either standard output or a given file.  If the object is a &amp;quot;manifest&amp;quot;
object, Magenc extracts the exact topics for each chunk from the manifest,
retrieves and decrypts each chunk in order, then writes out the complete file in
the same way.&lt;/p&gt;
&lt;p&gt;The original version of Magenc includes &lt;a href=&quot;gitlab.com/dustyweb/magenc/-/blob/master/magenc/scribblings/intro.org&quot;&gt;a more thorough
write-up&lt;/a&gt;.
Aside from the differing encryption suites and commandline interfaces, that
write-up also holds for the Guile version.&lt;/p&gt;
&lt;h3&gt;A simple example&lt;/h3&gt;
&lt;p&gt;Some things are easier to understand in action, so let's walk through a
trivially simple example of storing and retrieving a file using Magenc.  We will
skip build instructions, which are provided &lt;a href=&quot;https://codeberg.org/spritely/magenc&quot;&gt;in the
repository&lt;/a&gt;.  For ease of demonstration,
we will use the default server configuration which launches a process listening
at &lt;code&gt;http://127.0.0.1:8118&lt;/code&gt; (&lt;code&gt;http://localhost:8118&lt;/code&gt;) and stores data in memory
rather than writing it to disk ‒ Magenc does include a backend relying on
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.17.0/Bloblin-Store.html&quot;&gt;Goblins' bloblin persistence
store&lt;/a&gt;
to efficiently store files on disk.  We will use what is called &lt;em&gt;convergent
encryption&lt;/em&gt;, which uses part of the unencrypted file itself as a cryptographic
input, to ensure that our example file produces the same magnet URL every time
it is stored.  (If you are reading this in the future and Magenc's cryptography
has changed, the magnet URLs may no longer match.)&lt;/p&gt;
&lt;p&gt;First, let's start a server:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;magenc serve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should print the address where the server is listening, like:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;Server running at: http://127.0.0.1:8118
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can leave that running in one terminal session and use a different one for
everything else.  Now let's create our example file:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;echo &amp;quot;Hello! I'm an example file!&amp;quot; &amp;gt; example.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, we'll store the file with &lt;code&gt;magenc put example.txt --convergent&lt;/code&gt;.  This
prints the magnet URL
&lt;code&gt;magnet:?xt=urn%3Asha256d%3A4a2TJXrPx83v1DGnOJyPa5b678AkVsPaplsx_LcT06I&amp;amp;ek=_TrAfpNRRLQb7gutF8KKMtj-tPWk8_AapsJQgu6sDeo&amp;amp;es=AES-256-GCM&lt;/code&gt;.
(Note that argument ordering is relevant; to simplify the implementation of the
CLI, Magenc expects option arguments ‒ anything starting with &lt;code&gt;-&lt;/code&gt; or &lt;code&gt;--&lt;/code&gt; ‒
after positional arguments.)  Finally, we can retrieve the file:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;magenc get &amp;quot;magnet:?xt=urn%3Asha256d%3A4a2TJXrPx83v1DGnOJyPa5b678AkVsPaplsx_LcT06I&amp;amp;ek=_TrAfpNRRLQb7gutF8KKMtj-tPWk8_AapsJQgu6sDeo&amp;amp;es=AES-256-GCM&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to quote the magnet URL otherwise the shell will interpret &lt;code&gt;&amp;amp;&lt;/code&gt; as a
command.  This command produces the output:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;Hello! I'm an example file!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it!  Simple!  There are a few more options available for the various
subcommands, each explained with &lt;code&gt;magenc --help&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;GoblinShare&lt;/h2&gt;
&lt;p&gt;In addition to being an application, Magenc is also a library.  GoblinShare is
implemented using Magenc in this capacity.  All it adds is a purpose-built UI
and a wrapper around the in-memory store backend which makes it easier to use
through a Goblins actor.  The sending peer launches a &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.17.0/Tor-Onion-Services.html&quot;&gt;Tor
netlayer&lt;/a&gt;,
generates a sturdyref ‒ a persistent object reference which can be shared
out-of-band ‒ for the store actor, and adds that to the magnet URL with the
additional &lt;em&gt;acceptable source&lt;/em&gt; (&lt;code&gt;as&lt;/code&gt;) field.  The receiving peer sets up its own
netlayer, &amp;quot;enlivens&amp;quot; the sturdyref, and downloads the file.  Once the file is
retrieved, the sending peer terminates, removing the associated data from
memory.&lt;/p&gt;
&lt;h3&gt;An example&lt;/h3&gt;
&lt;p&gt;We will perform essentially the same tasks for this example as we did for the
Magenc example.  There are a few differences to note.  First, GoblinShare does
not provide an option for convergent encryption.  This simply wouldn't make
sense for an ephemeral file-sharing tool.  For our present purposes, this means
that you will almost certainly get a different magnet URL than is produced here.
Second, it is necessary to manually run the tor daemon.  Build and usage
instructions in &lt;a href=&quot;https://codeberg.org/spritely/goblinshare&quot;&gt;the GoblinShare
repository&lt;/a&gt; cover that so the
following example assumes a running daemon.&lt;/p&gt;
&lt;p&gt;We will reuse the same example file as above, so feel free to reuse the previous
command to create it.  Then, all we have to do is send it:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;goblinshare send example.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to get the magnet URL.  The sending process will wait for the file to be
retrieved, so switch to another terminal session to retrieve the file:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;goblinshare receive &amp;quot;magnet://?xt=...&amp;amp;ek=...&amp;amp;es=...&amp;amp;as=...&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we're using Tor as our network layer, it can take a few seconds for the
connection to be established and the data to be sent, even though we are
connecting to a local server.  Since our example file is small enough to fit
into a single chunk, the delay isn't very long, but a larger file can take quite
a while to transfer.  Eventually, you will receive the expected output:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;Hello! I'm an example file!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, both the sending and receiving processes will terminate, and
that's that!  You've successfully shared a file with Goblins!  Just like Magenc,
GoblinShare has a (very) few options you can see with &lt;code&gt;goblinshare --help&lt;/code&gt;.
Unlike Magenc, option arguments can be supplied before or after positional
arguments as long as they follow the associated subcommand.&lt;/p&gt;
&lt;h3&gt;A note on Magic Wormhole&lt;/h3&gt;
&lt;p&gt;I mentioned that I took initial inspiration for this project from &lt;a href=&quot;https://github.com/magic-wormhole/magic-wormhole&quot;&gt;Magic
Wormhole&lt;/a&gt;.  In practice,
though, the only similarity wound up being the names of the subcommands &lt;code&gt;send&lt;/code&gt;
and &lt;code&gt;receive&lt;/code&gt;.  Magic Wormhole relies on a central relay server because it uses
&amp;quot;wormhole codes&amp;quot; to facilitate oral communication of the relevant capabilities.
These codes seem to be keys mapping to a fuller capability and therefore
requiring a coordination point.  GoblinShare, by contrast, chooses to instead
provide less-human-friendly magnet URLs which allow fully peer-to-peer file
transfer because they encode all necessary information.&lt;/p&gt;
&lt;h2&gt;How easy was it, really?&lt;/h2&gt;
&lt;p&gt;As I mentioned near the beginning of this post, the most surprising part of
implementing GoblinShare was how easy it was.  The core functionality was
implemented in about half a day, though a failed attempt to get GoblinShare to
manage the tor daemon stretched the initial implementation out to about a day
and a half.  In the end, the UI and business logic of GoblinShare together
require 250 lines of code including module headers, whitespace, docstrings, and
inline comments (but not license headers).  That's very little code!  Magenc
took a bit more work, but a lot of that was getting the cryptography and URL
abstractions playing nicely ‒ and, admittedly, there's still room to make Magenc
more approachable as a library.  Still, the port took a little under a week all
told, including time to implement tests, and came out to somewhere in the
neighborhood of 1400 lines of code with similar caveats.&lt;/p&gt;
&lt;p&gt;Numbers are one thing, but the simplicity of GoblinShare really comes through in
the code itself.  The core logic comes down to three procedures, two in the
&lt;code&gt;send&lt;/code&gt; logic (with an additional helper) and one in the &lt;code&gt;receive&lt;/code&gt; logic.  Let's
break these down and walk through them to build up the relevant logic.&lt;/p&gt;
&lt;p&gt;As a general note, the UI layers of GoblinShare and Magenc use
&lt;a href=&quot;https://srfi.schemers.org/srfi-37/srfi-37.html&quot;&gt;SRFI-37&lt;/a&gt; to convert commandline
arguments into an association list of options and arguments.  The &lt;code&gt;main&lt;/code&gt;
procedures in &lt;code&gt;(goblinshare)&lt;/code&gt; and &lt;code&gt;(magenc)&lt;/code&gt; simply parse the commandline into
the appropriate arguments which they pass to the procedure associated with a
given subcommand.&lt;/p&gt;
&lt;p&gt;Now, on to the code walkthrough!&lt;/p&gt;
&lt;h3&gt;send&lt;/h3&gt;
&lt;p&gt;First, we'll briefly discuss the relevant helper procedure, &lt;code&gt;add-sref-to-magnet-url&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;add-sref-to-magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;magnet-url&amp;gt;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;xt&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ek&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;es&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#f&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;xt&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ek&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;es&lt;/span&gt;
                      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;uri-encode&lt;/span&gt;
                       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ocapn-id-&amp;gt;string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All this procedure does is decompose the magnet URL we get from Magenc then
build a new magnet URL with an additional query parameter holding the sturdyref
to the Goblins actor.&lt;/p&gt;
&lt;p&gt;Most of the important logic happens in &lt;code&gt;connect-to-goblinshare-server-store&lt;/code&gt;,
named to match the convention of Magenc's store abstraction:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-goblinshare-server-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-backend&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:backend-type&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'memory&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;store-backend-data&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;exact-topic&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-comment&quot;&gt;;; CapTP doesn't support records so we turn exact topics into strings
&lt;/span&gt;    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;retrieve-backend-data&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;backend&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-&amp;gt;exact-topic&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;exact-topic&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-backend&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;signal-condition!&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-store*&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'goblinshare-send&lt;/span&gt;
                     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This procedure does a few interesting things.  First, it accepts a &lt;code&gt;done&lt;/code&gt;
parameter.  This is a &lt;a href=&quot;https://codeberg.org/guile/fibers&quot;&gt;Fibers&lt;/a&gt; condition so
that we can wait on a remote peer to collect the shared file before exiting,
which is handled elsewhere.&lt;/p&gt;
&lt;p&gt;Next, this procedure creates a Magenc memory backend.  Backends are simply data
stores wrapped in a Scheme record so they can be used with an abstract
interface.  They provide three procedures associated with three fields of the
record type: &lt;code&gt;backend-get&lt;/code&gt;, &lt;code&gt;backend-put&lt;/code&gt;, and &lt;code&gt;backend-close&lt;/code&gt;, accessed using
&lt;code&gt;retrieve-backend-data&lt;/code&gt;, &lt;code&gt;store-backend-data&lt;/code&gt;, and &lt;code&gt;close-backend&lt;/code&gt;,
respectively.  Here we wrap each of the underlying backend's procedures in a new
procedure so we can massage our inputs to work over &lt;a href=&quot;https://ocapn.org&quot;&gt;OCapN&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;put&lt;/code&gt; calls the memory backend's &lt;code&gt;put&lt;/code&gt; procedure unmodified.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;get&lt;/code&gt; does a bit more.  Because we are using OCapN to communicate between peers,
and because OCapN doesn't have a dedicated type for generic records, we convert
exact topics into strings when sending messages between Goblins actors.  &lt;code&gt;get&lt;/code&gt;
thus converts exact topic strings back into exact topic records.  We could
instead have written a marshaller to convert our record into an OCapN tagged
value, but there was little reason to do so.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;close&lt;/code&gt; calls the underlying backend's &lt;code&gt;close&lt;/code&gt; then signals the &lt;code&gt;done&lt;/code&gt;
condition.&lt;/p&gt;
&lt;p&gt;Finally, the last call in this procedure is to Magenc's &lt;code&gt;connect-to-store*&lt;/code&gt;
helper which constructs a store interface (similar in shape and function to a
backend interface) to be passed to &lt;code&gt;chunk-and-store-data&lt;/code&gt;.  As you can see, it
takes two arguments: a symbol identifying its type, and a thunk which returns
three values: &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;put&lt;/code&gt;, and &lt;code&gt;close&lt;/code&gt;.  (The higher-level &lt;code&gt;connect-to-store&lt;/code&gt;
supports keyword arguments and is used to construct Magenc's built-in backends.)&lt;/p&gt;
&lt;p&gt;The last and likely most interesting piece of GoblinShare's send logic is the
part that actually deals with Goblins.  All it does is wrap a store in an actor,
spawn a Tor netlayer, and return a sturdyref to the wrapper actor:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-client-sref&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^client&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;methods&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;retrieve-data&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;cannot put with client capability&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;mycapn&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-mycapn&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^onion-netlayer&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;mycapn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^client&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'onion&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The actor is &lt;code&gt;^client&lt;/code&gt;.  As you can see, we override the backing store's &lt;code&gt;put&lt;/code&gt;
procedure to prevent remote peers from writing unexpectedly.  Otherwise, we use
Magenc's store interface to manipulate the store normally.&lt;/p&gt;
&lt;p&gt;The last two lines pack a lot of logic.  First, we spawn an &lt;code&gt;^onion-netlayer&lt;/code&gt;
representing a Tor netlayer.  We immediately pass this into &lt;code&gt;spawn-mycapn&lt;/code&gt;,
creating a new &lt;code&gt;^mycapn&lt;/code&gt; object populated with that netlayer.  Then, we spawn a
new &lt;code&gt;^client&lt;/code&gt;, immediately passing it to our &lt;code&gt;^mycapn&lt;/code&gt; object's &lt;code&gt;register&lt;/code&gt;
method to get a promise to a sturdyref referencing the object.  Whew!  All that
in only two lines!&lt;/p&gt;
&lt;p&gt;We bring all of the send logic together in &lt;code&gt;gs-send&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define*&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;gs-send&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;filename&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:optional&lt;/span&gt;
                  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;current-output-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let*&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-condition&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-goblinshare-server-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;call-with-input-file&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;filename&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;in-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;chunk-and-store-data&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;in-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-keyword&quot;&gt;#:binary&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:name&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'goblinshare-server&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-client-sref&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;~a~%&amp;quot;&lt;/span&gt;
                    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url-&amp;gt;string&lt;/span&gt;
                     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;add-sref-to-magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;wait&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;done&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, &lt;code&gt;gs-send&lt;/code&gt; requires a filename and optionally accepts an output
port where it will write the resulting magnet URL.  This is to facilitate tests
and is not exposed in the commandline client.&lt;/p&gt;
&lt;p&gt;The procedure starts by creating a Fibers condition which it passes to
&lt;code&gt;connect-to-goblinshare-server-store&lt;/code&gt; to create a store.&lt;/p&gt;
&lt;p&gt;Next, it passes the resulting store to Magenc's &lt;code&gt;chunk-and-store-data&lt;/code&gt; with
default parameters ‒ so, using AES-GCM encryption and generating a new key
appropriate for that cipher ‒ and a port for reading the input file.  It
assigns the returned magnet URL to a new variable.&lt;/p&gt;
&lt;p&gt;Then, &lt;code&gt;gs-send&lt;/code&gt; spawns a vat and enters a vat context.  There, it spawns a
client actor to wrap the store and resolves the sturdyref to the client actor.
It adds the resolved sturdyref to the magnet URL and writes out the result.&lt;/p&gt;
&lt;p&gt;Finally, &lt;code&gt;gs-send&lt;/code&gt; waits for the &lt;code&gt;done&lt;/code&gt; condition to be signaled.&lt;/p&gt;
&lt;h3&gt;receive&lt;/h3&gt;
&lt;p&gt;The receive command relies on only one procedure for most of its logic:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-goblinshare-client-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;client-sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:name&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'goblinshare-client&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;mycapn&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-mycapn&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^onion-netlayer&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;::&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;mycapn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'enliven&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;client-sref&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;cannot put with client capability&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;get&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-channel&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;exact-topic&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt;
          &lt;span class=&quot;syntax-comment&quot;&gt;;; CapTP doesn't support records so we turn exact topics into strings
&lt;/span&gt;          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'get&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;exact-topic-&amp;gt;string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;exact-topic&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;syscaller-free-fiber&lt;/span&gt;
                 &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,val&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;syntax-keyword&quot;&gt;#:catch&lt;/span&gt;
              &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;exn&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;syscaller-free-fiber&lt;/span&gt;
                 &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ch&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,exn&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-symbol&quot;&gt;#f&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;get-message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ch&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'ok&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'error&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;exn&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;raise-exception&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;exn&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-np&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-store*&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'goblinshare-receive&lt;/span&gt;
                     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;put&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are more lines here than in &lt;code&gt;connect-to-goblinshare-server-store&lt;/code&gt;, but the
gist is simple.  &lt;code&gt;connect-to-goblinshare-client-store&lt;/code&gt; takes a sturdyref, which
may be a promise thanks to promise pipelining.&lt;/p&gt;
&lt;p&gt;It starts by spawning a vat, spawning a &lt;code&gt;^mycapn&lt;/code&gt; with a new &lt;code&gt;^onion-netlayer&lt;/code&gt;,
and enlivening the client actor.  These steps mirror the steps used to spawn the
client sturdyref above.  Rather than registering an actor to get a sturdyref,
here we enliven a sturdyref to get an actor.&lt;/p&gt;
&lt;p&gt;Next, &lt;code&gt;connect-to-goblinshare-client-store&lt;/code&gt; wraps each method of the client
actor in a regular Scheme procedure for Magenc's store interface.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;put&lt;/code&gt; and &lt;code&gt;close&lt;/code&gt; are quite simple.  The former errors out to avoid unneeded
network access should it be called.  The latter sends the &lt;code&gt;close&lt;/code&gt; message to the
remote actor.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;get&lt;/code&gt; looks complicated, but most of the code is there to resolve a promise into
a concrete Scheme value.  The core functionality is &lt;code&gt;(&amp;lt;- client 'get (exact-topic-&amp;gt;string exact-topic))&lt;/code&gt;.  This line encodes the exact topic as a
string, messages the client actor's &lt;code&gt;get&lt;/code&gt; method with that string, and returns
the resulting promise.  When that promise fulfills or breaks, the surrounding
logic propagates the result as normal.&lt;/p&gt;
&lt;p&gt;Finally, &lt;code&gt;connect-to-goblinshare-client-store&lt;/code&gt; creates a new store with these
wrapper procedures using &lt;code&gt;connect-to-store*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The resulting store is used by &lt;code&gt;gs-receive&lt;/code&gt; to get the desired file:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define*&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;gs-receive&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:optional&lt;/span&gt;
                     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;current-output-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let*&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;connect-to-goblinshare-client-store&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;string-&amp;gt;ocapn-id&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;uri-decode&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url-acceptable-source&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;result&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;call-with-output-bytevector&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;retrieve-and-unchunk-data&lt;/span&gt;
              &lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;
              &lt;span class=&quot;syntax-keyword&quot;&gt;#:exact-topic&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url-exact-topic&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;syntax-keyword&quot;&gt;#:key&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url-encryption-key&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;syntax-keyword&quot;&gt;#:cipher&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url-encryption-suite&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;magnet-url&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;write-bytevector&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;out-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This procedure requires the magnet URL to retrieve, and optionally accepts an
output port where it will write the resulting data.  This is exposed through
&lt;code&gt;goblinshare receive&lt;/code&gt;'s &lt;code&gt;--output&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;gs-receive&lt;/code&gt; creates a store as discussed above, extracting the sturdyref
string from the magnet URL and converting it into the appropriate Scheme type.
Then, it decomposes the magnet URL into its components and passes them, along
with the store and a port receiving the result, to Magenc's
&lt;code&gt;retrieve-and-unchunk-data&lt;/code&gt;.  Finally, it writes the result and informs the
remote store that it's done.&lt;/p&gt;
&lt;p&gt;And that's it!  All the other code in GoblinShare is for the UI.&lt;/p&gt;
&lt;p&gt;I told you it was simple!&lt;/p&gt;
&lt;h2&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;It's worth mentioning that neither Magenc nor GoblinShare are intended as
finished, production software.  Notably, the underlying cryptography has not
been audited.  Additionally, there are some improvements I'd like to make to
Magenc's API which would make GoblinShare simpler.&lt;/p&gt;
&lt;p&gt;That's okay, though.  These projects are &lt;em&gt;demonstrations&lt;/em&gt;.  Magenc is intended
to demonstrate the basic concepts of capability-secure distributed, encrypted,
content-addressed data storage.  GoblinShare, for its part, is supposed to show
how easy it is to implement otherwise-complex functionality with Goblins, which
I think it does.  As a rough comparison, Magic Wormhole is about 11,500 lines of
Python code, counted with similar caveats as those for Magenc and GoblinShare,
and relies on a much longer list of dependencies.&lt;/p&gt;
&lt;p&gt;All told, I am incredibly happy with how things turned out.  GoblinShare is
beautifully simple and useful.  I hope it shows others how easy Goblins makes
networked applications and inspires more neat software.&lt;/p&gt;
&lt;p&gt;Happy hacking!&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alex Sassmannshausen&lt;/li&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brian Neltner&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Danny OBrien&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Bavier&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Jonathan Wright&lt;/li&gt;
&lt;li&gt;Joshua Simmons&lt;/li&gt;
&lt;li&gt;Justin Sheehy&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Shane Redman&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Alexander Poylisher&lt;/li&gt;
&lt;li&gt;Aria Stewart&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;James Smith&lt;/li&gt;
&lt;li&gt;Jamie Baross&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Rodion Goritskov&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stefan Magdalinski&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;a b&lt;/li&gt;
&lt;li&gt;chee rabbits&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
</content></entry><entry><title>Hoot 0.7.0 released!</title><id>https://spritely.institute/news/hoot-0-7-0-released.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-10-22T12:00:00Z</updated><link href="https://spritely.institute/news/hoot-0-7-0-released.html" rel="alternate" /><content type="html">&lt;p&gt;We are excited to announce the release of &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; 0.7.0!  Hoot
is a Scheme to WebAssembly compiler backend for
&lt;a href=&quot;https://gnu.org/software/guile&quot;&gt;Guile&lt;/a&gt;, as well as a general purpose
WebAssembly toolchain.  In other words, &lt;em&gt;Scheme in the browser!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This release contains bug fixes and a few new features added since the
&lt;a href=&quot;/news/hoot-0-6-1-released.html&quot;&gt;0.6.1 release&lt;/a&gt; back in May.  Just in
time for the upcoming Lisp Game Jam!&lt;/p&gt;
&lt;h2&gt;Make a game with Hoot&lt;/h2&gt;
&lt;p&gt;The 2025 edition of the &lt;a href=&quot;https://itch.io/jam/autumn-lisp-game-jam-2025&quot;&gt;Autumn Lisp Game
Jam&lt;/a&gt; starts on Friday,
10/31!  Halloween jam! 🎃&lt;/p&gt;
&lt;p&gt;For those who are unfamiliar, the Lisp Game Jam is a casual, 10-day
event where participants build small games using the Lisp
implementation of their choice.  We encourage you to join the jam and
make a game with Hoot!  Browser games are both easy to distribute and
easy for other people to play, which makes Hoot a great choice!
&lt;a href=&quot;https://itch.io&quot;&gt;itch.io&lt;/a&gt;, where the jam is hosted, has built-in
support for browser games.&lt;/p&gt;
&lt;p&gt;To make it easy to jump right in and start jamming, we have a
&lt;a href=&quot;https://codeberg.org/spritely/hoot-game-jam-template&quot;&gt;ready-to-use template repository on
Codeberg&lt;/a&gt; that
you can clone.  It takes care of all the boilerplate and provides some
useful bindings to essential web APIs like HTML5 Canvas.&lt;/p&gt;
&lt;p&gt;Okay, onto the full release notes!&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Added Guile’s source module directory to Hoot’s load path by
default.  It is now easy to import any module from Guile that Hoot
does not override in its core implementation.  Of course, any Guile
module that relies on a C extension or as-of-yet unsupported
&lt;code&gt;(guile)&lt;/code&gt; procedure/macro will not work with Hoot.  Contributors are
wanted to help maximize our Guile compatibility!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Removed stub SRFI-1 module in favor of loading it from Guile’s
source module directory.  Guile now has a pure Scheme implementation
of SRFI-1 (no more C!) usable by Hoot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hoot modules can now be loaded from JavaScript typed arrays and
&lt;code&gt;ArrayBuffer&lt;/code&gt; objects in methods such as &lt;code&gt;Scheme.load_main&lt;/code&gt; in
&lt;code&gt;reflect.js&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Top-level programs must now start with a &lt;code&gt;use-modules&lt;/code&gt; or &lt;code&gt;import&lt;/code&gt;
form.  The implicit import of &lt;code&gt;(scheme base)&lt;/code&gt; caused more confusion
for users than the convenience was worth.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;import&lt;/code&gt; form in a top-level program is now interpreted using
R7RS syntax rules whereas previously it used R6RS syntax rules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SRFI library names such as &lt;code&gt;(srfi :1)&lt;/code&gt; are now canonicalized as
&lt;code&gt;(srfi srfi-1)&lt;/code&gt; in R6RS &lt;code&gt;library&lt;/code&gt; forms to match Guile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for integer components in R7RS &lt;code&gt;define-library&lt;/code&gt; names.
Example: &lt;code&gt;(define-library (srfi 1) ...)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for &lt;code&gt;#:renamer&lt;/code&gt; in &lt;code&gt;define-module&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;filter!&lt;/code&gt;, &lt;code&gt;reverse!&lt;/code&gt;, and &lt;code&gt;with-fluids*&lt;/code&gt; procedures to
&lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added printer for record type descriptors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Documented &lt;code&gt;HOOT_LOAD_PATH&lt;/code&gt; environment variable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Performance improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Added optimized implementation of &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Explicitly set file permissions for reflection library files copied
by &lt;code&gt;guild compile-wasm --bundle&lt;/code&gt;.  This problem was especially
noticeable on Guix where the files were read-only, breaking
successive bundle invocations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;hash&lt;/code&gt; to return the accumulated hash value when the recursion
depth limit is reached.  Previously, the result of &lt;code&gt;hashv&lt;/code&gt; was
returned, causing objects what were &lt;code&gt;equal?&lt;/code&gt; to have different hash
codes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;define-record-type&lt;/code&gt; to raise a syntax error when an invalid
field is specified as a constructor argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed R6RS &lt;code&gt;rename&lt;/code&gt; syntax rules in &lt;code&gt;library&lt;/code&gt; &lt;code&gt;export&lt;/code&gt; forms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed implementation of &lt;code&gt;#:replace&lt;/code&gt; in &lt;code&gt;define-module&lt;/code&gt; syntax.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;put-string&lt;/code&gt; which was not respecting the &lt;code&gt;port&lt;/code&gt; argument.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed docstring for &lt;code&gt;,hoot-run-file&lt;/code&gt; meta-command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed link to Git repository URL in the manual.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replaced obsolete reference to &lt;code&gt;parse-wat&lt;/code&gt; in the manual with
&lt;code&gt;wat-&amp;gt;wasm&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed compilation of &lt;code&gt;unquote-splicing&lt;/code&gt; in non-tail position.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;string-trim&lt;/code&gt; in the case where the result should be the empty
string.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;simple-format&lt;/code&gt; now returns the unspecified value rather than zero values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;select&lt;/code&gt; instruction handling in Wasm toolchain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Browser compatibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Safari 26! 🎉 At long last, Hoot modules now work
reliably in Safari and other sufficiently fresh WebKit-based
browsers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Firefox 121 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Chrome 119 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Get Hoot 0.7.0&lt;/h2&gt;
&lt;p&gt;Hoot is already available in GNU Guix:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile-next guile-hoot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Hoot currently requires a bleeding-edge version of Guile, hence
&lt;code&gt;guile-next&lt;/code&gt; above.)&lt;/p&gt;
&lt;p&gt;Otherwise, Hoot can be built from source via our release tarball.  See
the &lt;a href=&quot;/hoot&quot;&gt;Hoot homepage&lt;/a&gt; for a download link and GPG signature.&lt;/p&gt;
&lt;p&gt;Documentation for Hoot 0.7.0, including build instructions, can be
found
&lt;a href=&quot;https://spritely.institute/files/docs/guile-hoot/0.7.0/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/hoot&quot;&gt;Hoot project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build something cool with Hoot (for the upcoming game jam or
otherwise), let us know on our &lt;a href=&quot;https://community.spritely.institute&quot;&gt;community
forum&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Thanks to our supporters&lt;/h2&gt;
&lt;p&gt;Your support makes our work possible!  If you like what we do, please
consider &lt;a href=&quot;/donate&quot;&gt;becoming a Spritely supporter today&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Diamond tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Aeva Palecek&lt;/li&gt;
&lt;li&gt;Daniel Finlay&lt;/li&gt;
&lt;li&gt;David Anderson&lt;/li&gt;
&lt;li&gt;Holmes Wilson&lt;/li&gt;
&lt;li&gt;Lassi Kiuru&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Gold tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Juan Lizarraga Cubillos&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Silver tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Adam King&lt;/li&gt;
&lt;li&gt;Austin Robinson&lt;/li&gt;
&lt;li&gt;Brian Neltner&lt;/li&gt;
&lt;li&gt;Brit Butler&lt;/li&gt;
&lt;li&gt;Charlie McMackin&lt;/li&gt;
&lt;li&gt;Dan Connolly&lt;/li&gt;
&lt;li&gt;Danny OBrien&lt;/li&gt;
&lt;li&gt;Deb Nicholson&lt;/li&gt;
&lt;li&gt;Eric Schultz&lt;/li&gt;
&lt;li&gt;Evangelo Stavro Prodromou&lt;/li&gt;
&lt;li&gt;Evgeni Ku&lt;/li&gt;
&lt;li&gt;Glenn Thompson&lt;/li&gt;
&lt;li&gt;James Luke&lt;/li&gt;
&lt;li&gt;Jonathan Frederickson&lt;/li&gt;
&lt;li&gt;Joshua Simmons&lt;/li&gt;
&lt;li&gt;Justin Sheehy&lt;/li&gt;
&lt;li&gt;Michel Lind&lt;/li&gt;
&lt;li&gt;Mikayla Maki&lt;/li&gt;
&lt;li&gt;Mike Ledoux&lt;/li&gt;
&lt;li&gt;Nathan TeBlunthuis&lt;/li&gt;
&lt;li&gt;Nia Bickford&lt;/li&gt;
&lt;li&gt;Noah Beasley&lt;/li&gt;
&lt;li&gt;Shane Redman&lt;/li&gt;
&lt;li&gt;Steve Sprang&lt;/li&gt;
&lt;li&gt;Travis Smith&lt;/li&gt;
&lt;li&gt;Travis Vachon&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bronze tier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Alan Zimmerman&lt;/li&gt;
&lt;li&gt;Alex Poylisher&lt;/li&gt;
&lt;li&gt;BJ Bolender&lt;/li&gt;
&lt;li&gt;Ben Hamill&lt;/li&gt;
&lt;li&gt;Benjamin Grimm-Lebsanft&lt;/li&gt;
&lt;li&gt;Brooke Vibber&lt;/li&gt;
&lt;li&gt;Brooklyn Zelenka&lt;/li&gt;
&lt;li&gt;Carl A&lt;/li&gt;
&lt;li&gt;Crazypedia No&lt;/li&gt;
&lt;li&gt;François Joulaud&lt;/li&gt;
&lt;li&gt;Grant Gould&lt;/li&gt;
&lt;li&gt;Gregory Buhtz&lt;/li&gt;
&lt;li&gt;Ivan Sagalaev&lt;/li&gt;
&lt;li&gt;Jason Wodicka&lt;/li&gt;
&lt;li&gt;Jeff Forcier&lt;/li&gt;
&lt;li&gt;Louis Jackman&lt;/li&gt;
&lt;li&gt;Marty McGuire&lt;/li&gt;
&lt;li&gt;Mason DeVries&lt;/li&gt;
&lt;li&gt;Neil Brudnak&lt;/li&gt;
&lt;li&gt;Nelson Pavlosky&lt;/li&gt;
&lt;li&gt;Philipp Nassua&lt;/li&gt;
&lt;li&gt;Robin Heggelund Hansen&lt;/li&gt;
&lt;li&gt;Rodion Goritskov&lt;/li&gt;
&lt;li&gt;Ron Welch&lt;/li&gt;
&lt;li&gt;Stephen Herrick&lt;/li&gt;
&lt;li&gt;Steven De Herdt&lt;/li&gt;
&lt;li&gt;Sébastien Rey-Coyrehourcq&lt;/li&gt;
&lt;li&gt;Tamara Schmitz&lt;/li&gt;
&lt;li&gt;Thomas Talbot&lt;/li&gt;
&lt;li&gt;William Murphy&lt;/li&gt;
&lt;li&gt;r g&lt;/li&gt;
&lt;li&gt;terra tauri&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Until next time, happy hooting! 🦉&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.17.0: Persistence is better than ever!</title><id>https://spritely.institute/news/spritely-goblins-v0-17-0-persistence-is-better-than-ever.html</id><author><name>Jessica Tallon</name><email>contact@spritely.institute</email></author><updated>2025-10-16T12:00:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-17-0-persistence-is-better-than-ever.html" rel="alternate" /><content type="html">&lt;video title=&quot;Goblins' mascot Aurie serializes into a crystal and bursts back out again, symbolizing the cycle of persisting / restoring&quot; src=&quot;https://files.spritely.institute/videos/aurie-crystal-cycle.loop.mp4&quot; autoplay=&quot;true&quot; loop=&quot;true&quot; muted=&quot;true&quot;&gt;&lt;/video&gt;
&lt;p&gt;We’re excited to announce the release of &lt;a href=&quot;/goblins/&quot;&gt;Spritely Goblins&lt;/a&gt;
0.17.0!  This release features a new lightning-fast persistence store
called &lt;em&gt;Bloblin&lt;/em&gt;, improved performance for persistence, and many bug
fixes!&lt;/p&gt;
&lt;h2&gt;Bloblin — a fast streaming file store&lt;/h2&gt;
&lt;p&gt;Bloblin is a new persistence store designed to be both fast and
efficient.  We recommend using it as the store of choice for your
persistent vats.&lt;/p&gt;
&lt;p&gt;To initialize the store, the full object graph is written to a new
file, but further updates are then streamed in a compressed delta form
upon churn of the vat.  It is essentially a binary log of
Syrup-encoded data.  To keep disk usage under control, a new file is
created after a configurable number of churns upon which the full
object graph is written out once again and the process repeats.  Old
log files are automatically deleted.&lt;/p&gt;
&lt;p&gt;Prior to Blobin, our main viable store was the Syrup store, which
would serialize the entire object graph and write it to disk after
each vat churn.  This kept the implementation fairly simple for
testing purposes, but at the expense of performance.
Bloblin is so fast that the store backend is in no way any longer the
limitation on persistence performance, and Bloblin plus Goblins can
stream many thousands of deltas per second to disk as-is.&lt;/p&gt;
&lt;p&gt;Let’s see it in action:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;use-modules&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;actor-lib&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ring-buffer&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;persistence-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bloblin&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-values&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ring-buffer&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-persistence-vat&lt;/span&gt;
   &lt;span class=&quot;syntax-symbol&quot;&gt;ring-buffer-env&lt;/span&gt;
   &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^ring-buffer&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-bloblin-store&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;my-bloblin-store&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above spawns a persistent vat with just one object as the root and
all the data is stored in a directory named &lt;code&gt;my-bloblin-store&lt;/code&gt;.  The
Bloblin store is easy to use and the speed boost to existing,
persistent Goblins applications is significant.&lt;/p&gt;
&lt;p&gt;The above Bloblin store is using the default settings for how often it
starts a new log file (every 1000 churns) and how many log files are
retained (no limit).  Changing these settings is as easy as changing
the &lt;code&gt;make-bloblin-store&lt;/code&gt; line to something like:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-bloblin-store&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;my-bloblin-store&amp;quot;&lt;/span&gt;
                    &lt;span class=&quot;syntax-keyword&quot;&gt;#:deltas-per-file&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;500&lt;/span&gt;
                    &lt;span class=&quot;syntax-keyword&quot;&gt;#:max-bloblin-files&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Two new actors in actor-lib&lt;/h2&gt;
&lt;p&gt;Goblins standard library of actors has two new members to the family:
&lt;code&gt;^vector&lt;/code&gt; and &lt;code&gt;^ring-buffer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;^vector&lt;/code&gt; actor, as you may have guessed, provides an actor
interface to Guile’s
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Vectors.html&quot;&gt;vector&lt;/a&gt;
type.  Unlike regular vectors, the &lt;code&gt;^vector&lt;/code&gt; actor is also resizable.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;^ring-buffer&lt;/code&gt; actor provides a &lt;a href=&quot;https://en.wikipedia.org/wiki/Circular_buffer&quot;&gt;ring
buffer&lt;/a&gt; (AKA circular
buffer) data structure which comes in handy for queues or logs with a
maximum length.&lt;/p&gt;
&lt;h2&gt;A short detour in delta-efficient actor design&lt;/h2&gt;
&lt;p&gt;This is a Goblins release post, not a deep dive on actor design, but
hopefully this short detour is worth it.  When designing these two
actors we wanted them to persist efficiently with Bloblin (writing
lots of data takes time).  This meant thinking a little bit on
ensuring changes to these actors produced small deltas when they
change.&lt;/p&gt;
&lt;p&gt;When an actor changes its behavior (via the &lt;code&gt;bcom&lt;/code&gt; capability),
Goblins’ persistence system will serialize that actor’s state (what we
refer to as a self-portrait) and commit it to a persistence store.
For most actors, their self-portrait will just be the arguments passed
to its constructor, as is the case for our new &lt;code&gt;^vector&lt;/code&gt; actor.  A
simple implementation might just receive a vector as an argument and
use &lt;code&gt;bcom&lt;/code&gt; when there are changes.  However, this would mean that the
entire vector and all of its contents would be serialized and sent to
the store every time a single element is updated.&lt;/p&gt;
&lt;p&gt;Instead, the new &lt;code&gt;^vector&lt;/code&gt; actor uses an underlying vector where each
element is an instance of a
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/latest/Cell.html&quot;&gt;&lt;code&gt;^cell&lt;/code&gt;&lt;/a&gt;.
Updating a single vector element updates a single cell, which means
only that cell is persisted rather than the entire vector and thus the
size of each delta is kept small.&lt;/p&gt;
&lt;p&gt;Hopefully this detour to look at implementation will help you in
design your own persistent actors to run fast on Bloblin and future
stores which take advantage of delta writes.&lt;/p&gt;
&lt;h2&gt;Store conversion&lt;/h2&gt;
&lt;p&gt;Hopefully by this point you’ve been sold on Bloblin being the new
hotness and want to take advantage of it in your project!  But perhaps
you’ve already have a store with data in it and you’re concerned that
the upgrade path to migrate to the new store will be a difficult and
messy task.  Worry not!  This release also contains a new procedure
which can migrate data between two persistence stores!&lt;/p&gt;
&lt;p&gt;Here’s how to convert from the Syrup store to the new Bloblin store:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;use-modules&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;persistence-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;syrup&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;goblins&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;persistence-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bloblin&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;syrup-store&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-syrup-store&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;data.syrup&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bloblin-store&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-bloblin-store&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;bloblin-data&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;persistence-store-copy!&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;syrup-store&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bloblin-store&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s that simple!  &lt;code&gt;persistence-store-copy!&lt;/code&gt; takes care of the hard
work.&lt;/p&gt;
&lt;h2&gt;Persistence speedups and fixes&lt;/h2&gt;
&lt;p&gt;The persistence system in Goblins should be not only faster because of
the new Bloblin store, but the actual machinery of the persistence
layer has been optimized.  There have also been a whole bunch of
smaller fixes to the persistence system and &lt;code&gt;define-actor&lt;/code&gt;. Two fixes
worth highlighting are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Actors which
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/Upgrading-through-persistence.html&quot;&gt;upgrade&lt;/a&gt;
during restoration now have their upgraded state persisted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Actors are now restored by traversing the graph from its roots,
preventing orphaned objects being unnecessarily (and maybe
problematically) spawned on restore.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting the release&lt;/h2&gt;
&lt;p&gt;This release includes all the features detailed above as well as many
bug fixes.  See the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.17.0/NEWS&quot;&gt;NEWS&lt;/a&gt; for
more information about all of the changes.&lt;/p&gt;
&lt;p&gt;As usual, Guix users can upgrade to 0.17.0 by running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;guix pull
guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the tarball on our &lt;a href=&quot;https://spritely.institute/goblins/&quot;&gt;release
page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Oh, and made it this far? We have an extra little treat for you. If
you like the animation of our persistence flame-and-crystal mascot
Aurie, we have the
&lt;a href=&quot;https://files.spritely.institute/videos/aurie-crystal-cycle.blend&quot;&gt;Blender source file&lt;/a&gt;
if you happen to be a Blender enthusiast or generally curious how
the animation works!&lt;/p&gt;
&lt;p&gt;If you’re making something with Goblins or want to contribute to
Goblins itself, be sure to join our community at
&lt;a href=&quot;https://community.spritely.institute/&quot;&gt;community.spritely.institute&lt;/a&gt;!
We also host regular office hours where you can come and ask questions
or discuss our projects.  Information about office hours is available
on the forum.  Thanks for following along and hope to see you there!&lt;/p&gt;
</content></entry><entry><title>Shepherd × Goblins update</title><id>https://spritely.institute/news/shepherd-goblins-update.html</id><author><name>Juli Sims &amp; David Thompson</name><email>contact@spritely.institute</email></author><updated>2025-09-10T12:00:00Z</updated><link href="https://spritely.institute/news/shepherd-goblins-update.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;video title=&quot;Animation of multiple Guix logos connecting as a distributed system&quot; src=&quot;https://files.spritely.institute/videos/guix-multi-logo.mp4&quot; autoplay=&quot;true&quot; loop=&quot;true&quot; muted=&quot;true&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gnu.org/software/shepherd/&quot;&gt;The Shepherd&lt;/a&gt; is an init
system and process manager initially built for &lt;a href=&quot;https://www.gnu.org/software/hurd/&quot;&gt;GNU
Hurd&lt;/a&gt; and now used by
&lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt;.  It can run with either root or user
privileges to launch daemons, execute tasks, and manage processes.  As
we’ve &lt;a href=&quot;https://spritely.institute/news/spritely-nlnet-grants-december-2023.html&quot;&gt;discussed
previously&lt;/a&gt;,
Spritely has been working to port the Shepherd to Goblins.  We’ve been
a bit quiet since that announcement, so what’s the buzz?&lt;/p&gt;
&lt;p&gt;First, as a quick refresher, the Shepherd is a great project to port
to Goblins because it’s already built on the actor model.  By
switching to Goblins, we can bring the following benefits to the
project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streamline the codebase by replacing the Shepherd’s ad-hoc &lt;a href=&quot;https://en.wikipedia.org/wiki/Actor_model&quot;&gt;actor
model&lt;/a&gt; implementation.&lt;/li&gt;
&lt;li&gt;Reduce the likelihood of &lt;em&gt;concurrency bugs&lt;/em&gt; caused by the existing
actor model implementation that exposes too much of its
&lt;a href=&quot;https://en.wikipedia.org/wiki/Communicating_sequential_processes&quot;&gt;CSP&lt;/a&gt;
foundation using &lt;a href=&quot;https://codeberg.org/fibers/fibers/&quot;&gt;Fibers&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Transform services (and other actors) into &lt;em&gt;object capabilities&lt;/em&gt; for
fine-grained management of privileges, which will (eventually) make
it to possible to unify the currently separate worlds of “system”
Shepherds that run as root under PID 1 and “user” Shepherds that run
as an unprivileged user.&lt;/li&gt;
&lt;li&gt;Enable Shepherd to use the &lt;a href=&quot;https://ocapn.org&quot;&gt;Object Capability Network
(OCapN)&lt;/a&gt; to open the door for distributed
networks of “Communicating Shepherd Processes” in the future.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since our last post, we’ve done the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wrote Goblins versions of the core actors like the service
controller, service registry, and process monitor.&lt;/li&gt;
&lt;li&gt;Added unit tests for all of the core actors (there were none
before).&lt;/li&gt;
&lt;li&gt;Rewrote the public API as a &lt;em&gt;compatibility layer&lt;/em&gt; on top of a new
Goblins actor API.  This new API is private for now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What this means is that all of the extant Shepherd functionality will
soon be available in the Goblins port.  We’re currently working out
the remaining sneaky, tricky, subtle bugs in order to have a full 1:1
port that passes the existing test suite.  We’re getting very close,
so we felt it was time to share this update!&lt;/p&gt;
&lt;h2&gt;What we’ve been up to&lt;/h2&gt;
&lt;p&gt;To better explain the work we've been doing, we need to discuss some
Shepherd internals, particularly how actors work.  Shepherd includes
an ad-hoc actor model that has notable differences with Goblins.
Shepherd actors are implemented as an event loop running in a fiber (a
lightweight thread) and send messages to each other over channels.  In
contrast, Goblins is also built on Fibers but mostly hides this behind
an abstraction barrier.  Rather than each actor managing its own event
loop, many Goblins actors share an event loop known as a
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/Vats.html&quot;&gt;vat&lt;/a&gt;.
Each Goblins actor is mapped to its current “behavior”, a procedure
that is called when the actor receives a message.  The differences
between these two actor model implementations means that porting an
actor from one to the other isn’t as straightforward as it might seem.&lt;/p&gt;
&lt;p&gt;The central unit of abstraction is the service, which represents
something managed by the Shepherd.  This could be an external process,
a one-off task (known as a “one-shot” service), a timer, or whatever
the user wants it to be.  Internally, services are represented by a
record holding immutable configuration information and a &lt;em&gt;service
controller&lt;/em&gt; actor which manages the running state.  The service
controller (named &lt;code&gt;^service&lt;/code&gt; in the code) was the first target of our
porting efforts as it helped elucidate the shape of the new
architecture.&lt;/p&gt;
&lt;p&gt;Going hand-in-hand with the service controller actor is the &lt;em&gt;service
registry&lt;/em&gt; (&lt;code&gt;^service-registry&lt;/code&gt;).  The service registry is responsible
for mapping the names of services to their associated service
controller.  Porting the service registry was one of the simplest
parts of the project; most of the original actor logic was copied over
with minimal changes.&lt;/p&gt;
&lt;p&gt;The Shepherd makes heavy use of dynamically scoped variables known as
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Parameters.html&quot;&gt;parameters&lt;/a&gt;
to pass around shared state like the current registry, the current
service, the current client socket, etc.  This created an issue for
us, however, as vats introduce a continuity barrier for parameters.
In the existing actor system, actors inherit the dynamic environment
in which they are spawned because each actor is a new fiber spun off
the current fiber.  In Goblins, actors are spawned within a vat’s
event loop which has an entirely separate dynamic state from the
caller.  Furthemore, Goblins discourages the use of parameters because
they are inherently &lt;em&gt;ambient&lt;/em&gt; and thus not capability-safe.&lt;/p&gt;
&lt;p&gt;Removing these parameters would be a backwards incompatible change, so
instead we capture the current state of relevant parameters in the
compatibility layer before passing those values off to Goblins actors.
The most obvious use of this technique is for I/O handling.  The
Shepherd uses a custom soft port for logging to standard output, a
client socket, and/or the system log.  A little named actor
tentatively named &lt;code&gt;^writer&lt;/code&gt; handles these concerns now.&lt;/p&gt;
&lt;p&gt;The most significant change is the introduction of a coordinating
actor called simply &lt;code&gt;^shepherd&lt;/code&gt;.  This actor is where we pushed all of
the logic related to starting, stopping, respawning, etc.  Procedures
such as &lt;code&gt;start-service&lt;/code&gt; and &lt;code&gt;stop-service&lt;/code&gt; are now thin wrappers
around calls to this actor.&lt;/p&gt;
&lt;p&gt;Related to process orchestration, the Shepherd has a &lt;em&gt;process monitor&lt;/em&gt;
actor, whose job is to watch for the termination of processes
associated with services and notify other actors about it.  This was
also relatively simple to port to a Goblins &lt;code&gt;^process-monitor&lt;/code&gt; actor
once all of the shared state was properly captured.  Goblins
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/Promises.html&quot;&gt;promises&lt;/a&gt;
somewhat simplified the logic involved in responding to these changing
states.&lt;/p&gt;
&lt;p&gt;Perhaps the trickiest part of the port was logging.  Loggers read from
an input port and write timestamped log lines to some destination,
perhaps a file or the system log.  Port I/O requires some
understanding how Fibers and Goblins interact.  If you’re not careful,
you can suspend a vat’s fiber, potentially stalling the program.
Goblins provides the &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/IO.html&quot;&gt;&lt;code&gt;^io&lt;/code&gt;
actor&lt;/a&gt;
to handle many common I/O needs safely, but the needs of Shepherd were
beyond what that actor could provide.  A first attempt at porting this
logic proved too buggy to rely upon, so we’ve recently reworked
logging actors into something more robust (and more similar to the
original logging actors, too).&lt;/p&gt;
&lt;p&gt;Finally, we have made a variety of smaller changes so existing code
plays nicely with new Goblins actors.  For example, the Shepherd
provides a collection of helpers for things like starting and stopping
processes (&lt;code&gt;make-forkexec-constructor&lt;/code&gt;, for example).  The shift to
Goblins required the introduction of Goblins message passing and
promise handling to keep some of these working as expected.  A lot of
time has been spent devising ways to keep the public API the same so
that existing user code will continue to function as expected, as if
nothing has really changed.  To support all of this work, we’ve
introduced a few of our own helper procedures and macros, and we’ve
modified some existing ones to be Goblins-friendly.&lt;/p&gt;
&lt;p&gt;Whew, that’s a lot!  It’s the culmination of over a year of work, so
it can be difficult to take in.  If you’d like to try, you can see the
current state of the port in our &lt;a href=&quot;https://codeberg.org/shepherd/shepherd/pulls/36&quot;&gt;WIP pull request on
Codeberg&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Demo time&lt;/h2&gt;
&lt;p&gt;Okay, but does it work?  We’re so glad you asked!&lt;/p&gt;
&lt;p&gt;In addition to using Shepherd for its init system (PID 1), Guix
provides helpful facilities for running user-level Shepherd daemons
through the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Shepherd-Home-Service.html#index-home_002dshepherd_002dservice_002dtype&quot;&gt;&lt;code&gt;home-shepherd-service-type&lt;/code&gt;&lt;/a&gt;
in &lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Home-Configuration.html&quot;&gt;&lt;code&gt;guix home&lt;/code&gt;&lt;/a&gt;.
This is the same kind of user Shepherd daemon mentioned before; Guix
just provides a nice, declarative interface to configure and launch
the daemon when defining a user's &lt;code&gt;home-environment&lt;/code&gt;.  We used this
functionality to swap in our Goblins Shepherd and manage an Emacs
background daemon with it.  Here's an actual session running in the
context of &lt;code&gt;guix home container&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;juli shepherd λ guix home container home-shepherd.scm
substitute: recherche des substituts sur « https://substitutes.nonguix.org »… 100.0%
substitute: recherche des substituts sur « https://bordeaux.guix.gnu.org »…   0.0%guix substitute: avertissement : bordeaux.guix.gnu.org : la connexion à échouée : Connexion refusée
substitute:
substitute: recherche des substituts sur « https://ci.guix.gnu.org »… 100.0%
Les dérivations suivantes seront compilées :
  /gnu/store/ixpviqjakf55j24ag523pdl5g9k8xld7-provenance.drv
  /gnu/store/ynykwmi237h4jxrgdgkwqs4sgvf1h3cc-home.drv

substitute: recherche des substituts sur « https://bordeaux.guix.gnu.org »…   0.0%
construction de /gnu/store/ixpviqjakf55j24ag523pdl5g9k8xld7-provenance.drv...
construction de /gnu/store/ynykwmi237h4jxrgdgkwqs4sgvf1h3cc-home.drv...
WARNING: (guile-user): imported module (guix build utils) overrides core binding `delete'
WARNING: (guile-user): imported module (guix build utils) overrides core binding `delete'
Symlinking /home/juli/.bash_profile -&amp;gt; /gnu/store/9vidh7q8sp353rb1jnrndyif9wl2fjna-bash_profile... done
Symlinking /home/juli/.profile -&amp;gt; /gnu/store/jjvk66x9wwzxw38byk796y9b6kvi21b0-shell-profile... done
Symlinking /home/juli/.bashrc -&amp;gt; /gnu/store/mdp6zf77631kqr8cw26p4m3vvbr7vk01-bashrc... done
Symlinking /home/juli/.config/shepherd/init.scm -&amp;gt; /gnu/store/nag703p683l66s2adad719810xfrhx3w-shepherd.conf... done
Symlinking /home/juli/.config/fontconfig/fonts.conf -&amp;gt; /gnu/store/bqhrpq7na79bxm3sbpmnana10g6sc4d5-fonts.conf... done
 done
Finished updating symlinks.

Comparing /gnu/store/non-existing-generation/profile/share/fonts and
          /gnu/store/yyyn7zy4lx8z9qsb41imkbxb11wrrqqc-home/profile/share/fonts... done (same)
Evaluating on-change gexps.

On-change gexps evaluation finished.

juli@sordidus ~$ herd status
Started:
 + emacs
 + root
juli@sordidus ~$ herd status emacs
Status of emacs:
  It is running since 19:40:05 (9 seconds ago).
  Main PID: 40
  Command: /gnu/store/b6f34g5rsz35z40fc0myimw9zgj654xj-emacs-no-x-30.1/bin/emacs --fg-daemon
  It is enabled.
  Provides: emacs
  Will be respawned.

Recent messages (use '-n' to view more or less):
  2025-09-05 19:40:06 Starting Emacs daemon.
juli@sordidus ~$ emacsclient -c
juli@sordidus ~$ herd stop emacs
juli@sordidus ~$ herd status emacs
Status of emacs:
  It is stopped since 19:40:31 (2 seconds ago).
  Process exited with code 15.
  It is enabled.
  Provides: emacs
  Will be respawned.
juli@sordidus ~$ herd start emacs
Service emacs has been started.
juli@sordidus ~$ herd status emacs
Status of emacs:
  It is running since 19:40:38 (2 seconds ago).
  Main PID: 144
  Command: /gnu/store/b6f34g5rsz35z40fc0myimw9zgj654xj-emacs-no-x-30.1/bin/emacs --fg-daemon
  It is enabled.
  Provides: emacs
  Will be respawned.

Recent messages (use '-n' to view more or less):
  2025-09-05 19:40:38 Starting Emacs daemon.
juli@sordidus ~$ herd restart emacs
Service emacs has been started.
juli@sordidus ~$ herd status emacs
Status of emacs:
  It is running since 19:40:45 (2 seconds ago).
  Main PID: 180
  Command: /gnu/store/b6f34g5rsz35z40fc0myimw9zgj654xj-emacs-no-x-30.1/bin/emacs --fg-daemon
  It is enabled.
  Provides: emacs
  Will be respawned.

Recent messages (use '-n' to view more or less):
  2025-09-05 19:40:45 Starting Emacs daemon.
juli@sordidus ~$ ls -al $(command -v herd)
lrwxrwxrwx 1 65534 overflow 80 Jan  1  1970 /home/juli/.guix-home/profile/bin/herd -&amp;gt; /gnu/store/4l4b2qb91bq3djj9ldg66jx6p98hxvin-goblins-shepherd-1.0.99-git/bin/herd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As simple as this demo is, it demonstrates that the Goblins Shepherd
can already handle its basic job, despite the work left to achieve
parity with mainline.  If you’d like to try this out for yourself,
first ensure you have Guix installed and up-to-date, then run the
following commands:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;shell&quot;&gt;git clone https://codeberg.org/spritely/shepherd
cd shepherd
git checkout -b goblins-shepherd-guix-home-demo
guix home container home-shepherd.scm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also look on Codeberg to see the &lt;a href=&quot;https://codeberg.org/spritely/shepherd/src/branch/goblins-shepherd-guix-home-demo/home-shepherd.scm&quot;&gt;home config
itself&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;“That’s so exciting!” we hear you saying; “When will this be shipping
in a Guix distribution near me?  When can I use Goblins to boot my
operating system?”  As exciting as this is, we’re not quite ready for
prime time, as we’ll explain below.&lt;/p&gt;
&lt;h2&gt;Remaining work&lt;/h2&gt;
&lt;p&gt;Before we deploy Shepherd onto our own systems, and especially before
we try it in PID 1, we want to ensure that we reliably pass Shepherd’s
existing suite of shell-based tests.  Somewhere between 4 and 7 tests
fail as of writing; some tests fail every time, others only
intermittently, indicating there may be some subtle race conditions
lurking.&lt;/p&gt;
&lt;p&gt;One key component that remains unsupported is system log support, the
lack of which accounts for a considerable chunk of the remaining test
failures.  Nonetheless, our branch is passing &lt;em&gt;nearly all&lt;/em&gt; of the
existing tests, which is great progress!  Once the test suite issues
have been sorted out, we’ll try using our Shepherd build on a real
Guix system and see how stable it is over time.&lt;/p&gt;
&lt;p&gt;There is also plenty of code to clean up.  We've left all the original
actor code in place during development to make rebasing on upstream
less prone to conflicts, but the time has come to start removing it.
There are also numerous refactors that can be done to improve the code
style and readability.&lt;/p&gt;
&lt;p&gt;Beyond a direct port, though, this work will empower the Shepherd with
everything Goblins and OCapN have to offer.  So how could those powers
be used?  Well, we've got some ideas!&lt;/p&gt;
&lt;h2&gt;Single-system unification&lt;/h2&gt;
&lt;p&gt;On Guix systems, where Shepherd serves as the init system in PID 1, it
is common to run additional Shepherd instances for unprivileged users.
One option is to use &lt;code&gt;guix home&lt;/code&gt;, as mentioned above.  These Shepherd
instances are entirely separate from each other.  Only users with
access to the root user (likely via &lt;code&gt;sudo&lt;/code&gt;) can interact with the
system Shepherd; it’s all-or-nothing.  It would be nice to be able to
give unprivileged users access to a subset of the system services,
following the &lt;em&gt;principle of least authority&lt;/em&gt;.  The object capability
security model provided by Goblins makes this possible!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;herd&lt;/code&gt; clients communicate with &lt;code&gt;shepherd&lt;/code&gt; daemons using a custom
protocol over a Unix domain socket.  If the client were to be modified
to speak the OCapN protocol instead, users of &lt;code&gt;herd&lt;/code&gt; would only be
able to interact with services for which they hold a capability.
Consider a shared server: the system administrator could give
capabilities to other users of the machine that grant access to just a
subset of the available system services — and perhaps to only a
subset of the available service actions.  The fine-grained nature of
object capabilities means that access can be scoped to the minimum
necessary for each user to do what they need.&lt;/p&gt;
&lt;p&gt;As a first step in this direction, we’ve added a prerequisite
component to Goblins, a &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/Unix-domain-socket.html&quot;&gt;Unix domain socket
netlayer&lt;/a&gt;
for OCapN.  This is necessary to have interconnected machines running
Shepherd communicate over OCapN at the system layer.  Read on to see
an example of what this might look like!&lt;/p&gt;
&lt;h2&gt;Fleet orchestration&lt;/h2&gt;
&lt;p&gt;Moving on from Shepherd on a single machine, an OCapN-enabled Shepherd
will allow for &lt;em&gt;orchestration of entire server fleets&lt;/em&gt;.  To
demonstrate, let’s walk through an example scenario.  Carol, a DevOps
engineer, is responsible for the web servers running on a small fleet
of machines named A and B.  Each machine is running Shepherd with a
&lt;code&gt;web-server&lt;/code&gt; service registered.  To model this scenario on a single
machine, we’ll use three Goblins vats:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:name&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;Server A&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:name&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;Server B&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-vat&lt;/span&gt; &lt;span class=&quot;syntax-keyword&quot;&gt;#:name&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;Carol&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we’ll setup some loggers to distinguish which “machine” logged
which line:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-actor&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^prefix-logger&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;prefix&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;~a: ~a\n&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;prefix&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-output&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^prefix-logger&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;A&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-output&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^prefix-logger&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;B&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-output&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^prefix-logger&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;C&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Servers A and B have identical configuration with a &lt;code&gt;web-server&lt;/code&gt;
service that depends on the &lt;code&gt;networking&lt;/code&gt; service:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-networking-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;networking&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-keyword&quot;&gt;#:start-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-keyword&quot;&gt;#:stop-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#f&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-web-server-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;web-server&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-keyword&quot;&gt;#:requirement&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;networking&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-keyword&quot;&gt;#:start-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-keyword&quot;&gt;#:stop-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#f&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-registry&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-networking&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-networking-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-web-server&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-web-server-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-vat&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;all-of&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-networking&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-web-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-registry&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-networking&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-networking-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-web-server&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-web-server-service&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-vat&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;all-of&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-networking&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-web-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Carol would like to issue a single command to start or stop all of the
web servers.  To do this, Carol first acquires references to the
&lt;code&gt;web-server&lt;/code&gt; service actors on each machine.  At first glance this
might seem to cause a name collision problem as both services have the
same name, but fear not!  Carol can assign &lt;em&gt;locally meaningful&lt;/em&gt; names
to these remote services in her local Shepherd.  On Carol’s machine,
the remote services are registered as &lt;code&gt;web-server-a&lt;/code&gt; and
&lt;code&gt;web-server-b&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; Naive, but enough for demo purposes.
&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-actor&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;^exported-service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;bcom&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;provision&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;extend-methods&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;canonical-name&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;car&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;provision&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;provision&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;provision&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;requirement&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;start&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;let-on&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'status&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
         &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;match&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;status&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'stopped&lt;/span&gt;  &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;started&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'start&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'starting&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;starting&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'running&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'stopping&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;stopping&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'running&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;'running&lt;/span&gt;  &lt;span class=&quot;syntax-symbol&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;running&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'running&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;stop&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;
       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'stop&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-registry&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-registry&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-a&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^exported-service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-output&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;a-web-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;web-server-a&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-b&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^exported-service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-output&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;b-web-server&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;web-server-b&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These &lt;em&gt;exported services&lt;/em&gt; are actually &lt;em&gt;proxy objects&lt;/em&gt; that you can
think of like micro-herd clients that only control a single service.
In this simplistic example, Carol can only start or stop the exported
services, but it would also be possible to allow other actions to be
invoked.&lt;/p&gt;
&lt;p&gt;To conveniently orchestrate all of the remote &lt;code&gt;web-server&lt;/code&gt; services
with a single command, Carol binds them together with her own local
&lt;code&gt;web-server-fleet&lt;/code&gt; service that depends on both &lt;code&gt;web-server-a&lt;/code&gt; and
&lt;code&gt;web-server-b&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-fleet&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;^service&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;web-server-fleet&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-keyword&quot;&gt;#:requirement&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;web-server-a&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;web-server-b&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-keyword&quot;&gt;#:start-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#t&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-keyword&quot;&gt;#:stop-handler&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#f&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;with-vat&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-vat&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;let-on&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-a&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-b&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
           &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'register&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-fleet&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-shepherd&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'start&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-web-server-fleet&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;c-output&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all Carol has to do is run &lt;code&gt;herd start web-server-fleet&lt;/code&gt; (which we
simulate above with the &lt;code&gt;start&lt;/code&gt; method call) and her local Shepherd
will report the success or failure of starting all the remote web
servers in the fleet!  Assembling the logs from all three machines,
the event log would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;A: Service networking has been started.
B: Service networking has been started.
A: Service web-server has been started.
C: Service web-server-a has been started.
B: Service web-server has been started.
C: Service web-server-b has been started.
C: Service web-server-fleet has been started.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Neat, huh?&lt;/p&gt;
&lt;h2&gt;Guix deployment over OCapN&lt;/h2&gt;
&lt;p&gt;One final idea we’ll share is for a new Guix feature: a &lt;code&gt;guix deploy&lt;/code&gt;
agent.  This would be a capability-safe take on the modern DevOps
practice of deploying through dedicated agents instead of generic SSH.
To make this work, there would be a &lt;code&gt;guix-deploy&lt;/code&gt; Shepherd service
that runs on the target machine with a special &lt;code&gt;deploy&lt;/code&gt; action to
start the deployment process.  The workstation that is invoking &lt;code&gt;guix deploy&lt;/code&gt; would receive a capability to that service, perhaps in
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.16.1/Using-the-CapTP-API.html&quot;&gt;sturdyref&lt;/a&gt;
form, and associate it with a Guix &lt;code&gt;machine&lt;/code&gt; declaration.  That code
might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;my-server&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;machine&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;operating-system&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;my-os&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;environment&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ocapn-environment-type&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;machine-ocapn-configuration&lt;/span&gt;
                    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;sturdyref&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;ocapn://pubkey.tcp-tls/s/swissnum?host=example.com&amp;amp;port=8888&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;x86_64-linux&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any volunteers interested in building this?&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Porting Shepherd to Goblins has been a long time coming, but we’re
starting to see encouraging results!  If you’d like to discuss this
blog post, help us make some of the ideas described above a reality,
or talk about anything else Spritely related, consider joining our
&lt;a href=&quot;https://community.spritely.institute/&quot;&gt;community forum&lt;/a&gt;!&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.16.1 released!</title><id>https://spritely.institute/news/spritely-goblins-v0-16-1-released.html</id><author><name>Jessica Tallon</name><email>contact@spritely.institute</email></author><updated>2025-09-03T12:00:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-16-1-released.html" rel="alternate" /><content type="html">&lt;p&gt;Today we're happy to announce the release of Goblins 0.16.1. This is a
small patch release for the &lt;a href=&quot;https://spritely.institute/news/spritely-goblins-v0-16-0-released.html&quot;&gt;0.16.0
release&lt;/a&gt;
from a few weeks ago. Unfortunately, shortly after releasing it we
discovered some issues which prevented using OCapN (the peer-to-peer
networking element of Goblins) with
&lt;a href=&quot;https://spritely.institute/news/spritely-goblins-v0-15-1-released.html&quot;&gt;Hoot&lt;/a&gt;. This
release resolves that along with a couple of minor bug fixes.&lt;/p&gt;
&lt;p&gt;For more details about the changes in the release, see the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.16.1/NEWS&quot;&gt;NEWS&lt;/a&gt;
file.&lt;/p&gt;
&lt;h2&gt;Bug Fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed an issue where &lt;code&gt;(goblins actor-lib io)&lt;/code&gt;, which is heavily used
by our netlayers, used &lt;code&gt;current-scheduler&lt;/code&gt; from Fibers.  This
procedure is however not present in Hoot's fibers API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed an issue where multiple connections between two OCapN peers
could exist if the OCapN Locator's hints differed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed an issue where multiple connections would occur between two
OCapN peers due to a record hashing bug.  This could not actually be
reached due to the above IO actor bug.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed an issue where resizing the vat event log would lose data.
This was due to a bug in &lt;code&gt;ring-buffer-resize!&lt;/code&gt; from &lt;code&gt;(goblins utils ring-buffer)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting the release&lt;/h2&gt;
&lt;p&gt;As usual, if you're using Guix, you can upgrade to 0.16.1 by running:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the source download links on the &lt;a href=&quot;https://spritely.institute/goblins/&quot;&gt;Goblins
homepage&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch!&lt;/h2&gt;
&lt;p&gt;If you're making something with Goblins or want to contribute to
Goblins itself, be sure to join &lt;a href=&quot;https://community.spritely.institute&quot;&gt;our community
forum&lt;/a&gt;!  We also host regular
office hours where you can come and ask questions or discuss projects.
For more information, see the forum.  Thanks for following along and
hope to see you there!&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.16.0 released!</title><id>https://spritely.institute/news/spritely-goblins-v0-16-0-released.html</id><author><name>Christine Lemmer-Webber</name><email>contact@spritely.institute</email></author><updated>2025-08-07T10:45:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-16-0-released.html" rel="alternate" /><content type="html">&lt;p&gt;We are excited to announce &lt;a href=&quot;/goblins/&quot;&gt;Spritely Goblins&lt;/a&gt; v0.16.0!  This release of
Goblins is faster than ever, with two major core speedups benefiting
all Goblins-using programs! Furthermore, we have a brand new Unix
Domain Socket netlayer, which means our OCapN protocol is now usable
for efficient machine-local inter-process communication!&lt;/p&gt;
&lt;h1&gt;A new Unix Domain Sockets netlayer&lt;/h1&gt;
&lt;p&gt;Another new netlayer has come to Goblins, this time one based on Unix
Domain Sockets! Unix domain sockets are ideal for communication
between multiple processes running on the same machine. We think being
able to use Goblins and OCapN to wire together a kind of efficient
local inter-process communication is pretty neat!&lt;/p&gt;
&lt;p&gt;Many users might be familiar with using Unix domain sockets using file
paths on the system, however since the file system on Unix(-like)
systems use ACLs, this can lead to security vulnerabilities via
&lt;a href=&quot;https://en.wikipedia.org/wiki/Confused_deputy_problem&quot;&gt;confused deputy attacks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Our implementation uses a feature of Unix domain sockets which allow
sockets to be sent and received over other sockets. We built an
introduction server which you run on your system. You can think of it
as a little
&lt;a href=&quot;https://en.wikipedia.org/wiki/Object-capability_model&quot;&gt;OCaps&lt;/a&gt; kernel
in amongst the modern ACL sea that our systems are built on today! The
Unix domain socket netlayer can connect to one or multiple of these
introduction servers and so long as two netlayers share the same
introduction server, they can securely communicate with one another.&lt;/p&gt;
&lt;p&gt;We look forward to seeing what you all use this new netlayer for... we
have several exciting uses planned ourselves we hope to show off soon!&lt;/p&gt;
&lt;h1&gt;Speeding, speeding, speeding ahead!&lt;/h1&gt;
&lt;p&gt;When we're talking speedups in this release, we're not talking in mere
single digit percentage speed boosts. No, not even double
digit... keep going! Each of our two core speed-boosts bring make
Goblins each improve the speed of common Goblins operations 10-20x,
benefiting &lt;em&gt;all Goblins-using programs!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The short version is, &lt;code&gt;spawn&lt;/code&gt; has gotten faster, and &lt;code&gt;bcom&lt;/code&gt; and
promises have also gotten faster, all of which are core to all Goblins
programs. For the interested reader, we explain more in further detail
below. (This may go into more detail than many readers care for; feel
free to skip past!)&lt;/p&gt;
&lt;h2&gt;Speeding up spawn by bypassing the elfs&lt;/h2&gt;
&lt;p&gt;Once upon a time, when Goblins was being created, a decision was made:
debugging programs is important, and so all objects shall carry a
debug name, and that debug name shall be, by default, the name of the
procedure that constructed the actor! This was a sensible decision,
and we believe, generally the correct one: it has served us well.&lt;/p&gt;
&lt;p&gt;This decision was made long ago, in early days when Goblins was a
Racket library, and we began to focus on speed much more after the
port to Guile. But unfortunately, in Guile, asking a procedure &amp;quot;what
is your name?&amp;quot; resulted in a journey to the land of elfs.&lt;/p&gt;
&lt;p&gt;Or rather, that is all to say, calling &lt;code&gt;procedure-name&lt;/code&gt; in Guile on
every &lt;code&gt;spawn&lt;/code&gt;, which we did for debuggability purposes, turns out to
be &lt;em&gt;painfully&lt;/em&gt; slow. And the reason it is slow is that normally
&lt;code&gt;procedure-name&lt;/code&gt; is only called when experimenting at the REPL or when
printing a backtrace. While optimizing Goblins programs, we found that
tracing an ordinary &lt;code&gt;spawn&lt;/code&gt; (using Guile's lovely &lt;code&gt;,trace&lt;/code&gt; tool) was
printing reams and reams of pages of lines of code. Guile's internal
object file format is (perhaps surprising to some readers!) the very
same ELF as, yes, the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&quot;&gt;Executable and Linkable Format&lt;/a&gt;
used by Linux executables! However, Guile uses this for different
purposes; it turns out this format is just very well thought through,
and Guile's lead dev Andy Wingo has a nice blogpost
&lt;a href=&quot;https://wingolog.org/archives/2014/01/19/elf-in-guile&quot;&gt;explaining why ELF was chosen&lt;/a&gt;.
What this effectively meant is that ELF-parsing code would be executed
all the time when simply trying to grab the name of a procedure while
spawning an object.&lt;/p&gt;
&lt;p&gt;What to do? Many paths were considered: We could try to optimize this
code intended to be rarely-used in Guile itself, or cache the result
and attach to the constructor somehow, or evaluate lazily. But each of
these had problems: it was slow or otherwise complicated.&lt;/p&gt;
&lt;p&gt;We could change &lt;code&gt;spawn&lt;/code&gt; to be a macro, and grab the name referred to
by the constructor at compile time. Alas, this had its own pitfall:
this would break any case where &lt;code&gt;spawn&lt;/code&gt; was already being used with
&lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The solution is to support both cases! Here is the new code for
&lt;code&gt;spawn&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; When an actor is spawned and a name is not specified, we default to
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; the name of its constructor.  However, 'procedure-name' is very
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; slow and can involve parsing ELF for compiled code.  To speed
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; things up, we take advantage of the fact that actor constructors
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; are typically specified as identifiers in the source, so we can
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; simply use that identifier as the name.  To preserve the illusion
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; that 'spawn' is just a regular ol' procedure, there is identifier
&lt;/span&gt;&lt;span class=&quot;syntax-comment&quot;&gt;;; syntax.
&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define-syntax&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;spawn&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;stx&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;syntax-case&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;stx&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;          &lt;span class=&quot;syntax-comment&quot;&gt;; fast path
&lt;/span&gt;       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;identifier?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#'constructor&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-symbol&quot;&gt;#'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;spawn-named&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;'constructor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;          &lt;span class=&quot;syntax-comment&quot;&gt;; slow path
&lt;/span&gt;       &lt;span class=&quot;syntax-symbol&quot;&gt;#'&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;%spawn&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;constructor&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;id&lt;/span&gt;                               &lt;span class=&quot;syntax-comment&quot;&gt;; identifier syntax; also slow
&lt;/span&gt;       &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;identifier?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#'id&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-symbol&quot;&gt;#'%spawn&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this means is that when a Goblins program is compiled, most
invocations of &lt;code&gt;spawn&lt;/code&gt; will cleverly use the name of the constructor
being passed in at compile time. But if this cannot be determined
simply at compile time, or if &lt;code&gt;spawn&lt;/code&gt; is to be invoked via &lt;code&gt;apply&lt;/code&gt; or
passed around as if it were a function, we fall back to using &lt;code&gt;spawn&lt;/code&gt;
as an ordinary procedure (ie, fall back to the internal &lt;code&gt;%spawn&lt;/code&gt;
procedure, which calls &lt;code&gt;procedure-name&lt;/code&gt; as normal).&lt;/p&gt;
&lt;p&gt;We still love our friends the elfs, and upon occasion, some Goblins
programs might journey into &lt;code&gt;elf&lt;/code&gt; land, should they need their help to
provide a simple debugging name. But most of the time, we can be much
faster now, by looking around where we are at compile time!&lt;/p&gt;
&lt;h2&gt;Become your new you, faster than ever&lt;/h2&gt;
&lt;p&gt;Previously we discussed how Goblins actors got much faster with
&lt;code&gt;spawn&lt;/code&gt;, but this is only part of an actor's journey.  First, we are
born, and then, we grow and change based upon experience. So it is too
with Goblins actors!&lt;/p&gt;
&lt;p&gt;When an actor is &lt;code&gt;spawn&lt;/code&gt;ed, its constructor returns what will be its
first behavior.  But actors may change their behavior based upon
experience: in response to a message, a Goblins actor may choose to
&lt;code&gt;bcom&lt;/code&gt; (pronounced &amp;quot;become&amp;quot;) a new version of its behavior.&lt;/p&gt;
&lt;p&gt;An actor having many experiences (receiving many messages) may
experience a large amount of change, and thus may invoke &lt;code&gt;bcom&lt;/code&gt; a lot.
The way &lt;code&gt;bcom&lt;/code&gt; was implemented used pretty much the same
&lt;a href=&quot;https://files.spritely.institute/papers/spritely-core.html#appendix-implementing-sealers-unsealers&quot;&gt;sealers/unsealers technique&lt;/a&gt;
from the appendix of
&lt;a href=&quot;https://files.spritely.institute/papers/spritely-core.html&quot;&gt;The Heart of Spritely&lt;/a&gt;,
itself a technique borrowed from
&lt;a href=&quot;https://github.com/jar398/w7/blob/master/w7-fun.scm#L37&quot;&gt;W7&lt;/a&gt;,
the very security kernel from
&lt;a href=&quot;http://mumble.net/~jar/pubs/secureos/secureos.html&quot;&gt;A Security Kernel Based on the Lambda Calculus&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;This is a cool technique, and takes advantage of being able to
construct new types at runtime. However, constructing new types at
runtime turns out to have some overhead. The details are unimportant,
but we moved to a new implementation of sealers which are functionally
equivalent but use an encapsulated &amp;quot;cookie&amp;quot; comparison, which turns
out to be dramatically faster... about as fast as two accessor calls
and an identity-comparison invocation of &lt;code&gt;eq?&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;In other words, actors can now change their behavior with &lt;code&gt;bcom&lt;/code&gt;
quite quickly! And several other aspects of Goblins have gotten faster
too with this new sealers technique, in particular several aspects of
promises! Zoom zoom!&lt;/p&gt;
&lt;h1&gt;Getting the release&lt;/h1&gt;
&lt;p&gt;This release includes all the features detailed above as well as many bug fixes.
See the &lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.16.0/NEWS&quot;&gt;NEWS&lt;/a&gt; for more information.&lt;/p&gt;
&lt;p&gt;As usual, if you're using Guix you can upgrade to 0.16 by using the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;guix pull
guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the tarball on our &lt;a href=&quot;https://spritely.institute/goblins/&quot;&gt;release page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The above features and speedups spoken about in this blogpost refer to
the Guile version of Goblins, which is nowadays the primary version of
Spritely Goblins. However, we do maintain our older Racket version,
which has now also gotten updated to maintain OCapN
compatibility with Guile Goblins. Racket users can run the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raco pkg install goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're making something with Goblins or want to contribute to Goblins itself,
be sure to join our community at &lt;a href=&quot;https://community.spritely.institute/&quot;&gt;community.spritely.institute&lt;/a&gt;! We also host
regular office hours where you can come and ask questions or discuss our
projects, you can find information about those on our community forum. Thanks for
following along and hope to see you there!&lt;/p&gt;
</content></entry><entry><title>Spritely presented spirited speeches spanning the planet</title><id>https://spritely.institute/news/spritely-presented-spirited-speeches-spanning-the-planet.html</id><author><name>Amy Pillow</name><email>contact@spritely.institute</email></author><updated>2025-07-31T00:00:00Z</updated><link href="https://spritely.institute/news/spritely-presented-spirited-speeches-spanning-the-planet.html" rel="alternate" /><content type="html">&lt;p&gt;Over the past 6 months, Spritely has been busy bringing our message to
new audiences. I thought it might be nice to compile a list for
everyone to watch our talks. Christine Lemmer-Webber, the Executive
Director of Spritely, has been busy giving most of these
presentations, but the entire team has helped as well. The talks cover
our technology, our values, our past, and our vision.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5139-org-mode-witchcraft-at-spritely/&quot;&gt;Org mode Witchcraft at Spritely&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In January, five Spritely members went to the annual FOSDEM conference
to talk about the organization and how we all contribute to it. The
first talk was actually by me, talking about how we organize our
organization, and how we manage the many whitepapers we have put
together using Org Mode. I also gave a sneak-peak of the trans-bean
program which can use a Magit-style menu to edit plaintext accounting
ledgers.&lt;/p&gt;
&lt;p&gt;An update from that video is that &lt;a href=&quot;https://codeberg.org/spritely/trans-bean&quot;&gt;trans-bean is now available on
Codeberg&lt;/a&gt; if you want to try
it out!&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5128-today-s-fediverse-a-good-start-but-there-s-more-to-do/&quot;&gt;Today's fediverse: a good start, but there's more to do&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unfortunately, this talk is one of those things you had to experience
in person. The camera didn't capture the performance. It is still
worth a listen though! Our founder, Christine Lemmer-Webber, and the
Chief Technologist at Spritely, Jessica Tallon, talk about their view
of the fediverse, from their perspective as two of the primary authors
to the AcitivityPub spec.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5312-consent-based-secure-collaboration-with-spritely-goblins-object-capabilities/&quot;&gt;Object-Capability Security with Spritely Goblins for Secure Collaboration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Juliana gave a beautiful presentation of how our shared values
regarding individual rights and consent led naturally to the technical
choices Spritely has made. She also gives a great overview of Ocaps
which is worth watching even if you are familiar with object
capability security already.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5140-minimalist-web-application-deployment-with-scheme/&quot;&gt;Minimalist web application deployment with Scheme&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dave is fighting an uphill battle against dependency hell, and he
needs your help. Part of the solution is, of course, Guile Scheme!
Spritely's scheme-to-webassembly compiler, &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt;, is now
mature enough to utilize in your next web application, and Dave thinks
you should try it. What you will get in return is true reproducibility
and a good bootstrapping story, along with all the web APIs you're
used to. He even goes on to show how reactive programming can work
through webassembly and Scheme, and plenty of other goodies.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5239-goblins-the-framework-for-your-next-project-/&quot;&gt;Goblins: The framework for your next project!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Jessica is the lead technologist at Spritely and brings a lot of
experience to the table when it comes to defining networking
standards. She is a co-author of the
&lt;a href=&quot;https://activitypub.rocks&quot;&gt;ActivityPub&lt;/a&gt;. Now, working on Goblins at
Spritely, she believes the Goblins library can be used for much more
than just social media.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5315-shepherd-with-spritely-goblins-for-secure-system-layer-collaboration/&quot;&gt;Shepherd with Spritely Goblins for Secure System Layer Collaboration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Juliana's second talk at FOSDEM this year was about her work on
bringing the distributed networking power of Goblins to the Shepherd,
which is responsible for coordinating services on a Guix system. With
this project well underway, system administration, across the
internet, can soon be done in a capability-secure way. This talk
covers the current status of the project as well as how the Plan 9
system inspired her to start.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5123-spritely-and-a-secure-collaborative-distributed-future/&quot;&gt;Spritely and a secure, collaborative, distributed future&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Christine gave the last talk from Spritely at FOSDEM this year, and
walked through the larger concepts of Spritely and how they come
together, and why we decided to make mascots for all the different
components. The Spritely project plan from 6 years ago is still the
current plan, despite all the work that was done in between. As more
and more characters have been coming to life, we have been getting
closer to fulfilling our promise of peer-to-peer application
development made easy and secure.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://c-tube.c-base.org/w/2vHZ69an4Rck3mMfEs4w92&quot;&gt;c-base Fireside chat&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Christine had an intimate conversation at Berlin's famous c-base space
station with Volker Grassmuck, ranging from topics about her personal
life, her experience working on the
&lt;a href=&quot;https://activitypub.rocks&quot;&gt;ActivityPub&lt;/a&gt;, and the work she is doing
now at Spritely. She ends it with a powerful and hopeful message about
the future of decentralized networking.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;https://fediforum.org/2025-06/#friday-june-6&quot;&gt;Fediforum keynote&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Christine again gave an amazing talk about the values that led to
Spritely, most importantly including fun and enjoyment. She talks
about the differences between the fediverse and Bluesky, and how each
can learn from each other, as well as our current battle against
surveillance capitalism. Throughout all of this, she gives an
optimistic view of what can be accomplishe through community activism.&lt;/p&gt;
&lt;h2&gt;What the future holds&lt;/h2&gt;
&lt;p&gt;We are all putting our heads down to work on delivering the promises
we talked about this year so far. In the current environment, the
tools we are building are more important than ever. We hope that these
talks inspire you to try out our technology and read our papers, maybe
even donate! And each month, you can come listen to more of us talk at
our monthly
&lt;a href=&quot;https://community.spritely.institute/tag/office-hours&quot;&gt;Office Hours&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Have a great rest of the Summer!&lt;/p&gt;
</content></entry><entry><title>Goblinville: A Spring Lisp Game Jam 2025 retrospective</title><id>https://spritely.institute/news/goblinville-a-spring-lisp-game-jam-2025-retrospective.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-06-04T12:00:00Z</updated><link href="https://spritely.institute/news/goblinville-a-spring-lisp-game-jam-2025-retrospective.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;video title=&quot;Recording of a Goblinville session&quot; src=&quot;https://files.spritely.institute/videos/2025-06-03-goblinville.mp4&quot; autoplay=&quot;true&quot; loop=&quot;true&quot; muted=&quot;true&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Spritely participates in the Lisp Game Jam to make interactive
artifacts demonstrating our progress building out our tech stack.  The
2025 edition of the &lt;a href=&quot;https://itch.io/jam/spring-lisp-game-jam-2025&quot;&gt;Spring Lisp Game
Jam&lt;/a&gt; recently wrapped
up and this time around we were &lt;em&gt;finally&lt;/em&gt; able to show off using both
Hoot and Goblins &lt;em&gt;together&lt;/em&gt; to create a multiplayer virtual world
demo!  Now that we’ve had a moment to breathe, it’s time to share what
we built and reflect on the experience.&lt;/p&gt;
&lt;p&gt;But first, some stats about the jam overall.&lt;/p&gt;
&lt;h2&gt;Jam stats&lt;/h2&gt;
&lt;p&gt;Out of 26 total entries, 7 were made with Guile Scheme, including
ours.  Of those 7, all but one used &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt;, our Scheme to
WebAssembly compiler.  Guile tied for first place with Fennel as the
most used Lisp implementation for the jam.  We’re thrilled to see that
Guile and Hoot have become popular choices for this jam!&lt;/p&gt;
&lt;p&gt;Though many entries used Hoot, our entry was the only one that used
&lt;a href=&quot;/goblins&quot;&gt;Goblins&lt;/a&gt;, our distributed programming framework.  However,
David Wilson of &lt;a href=&quot;https://systemcrafters.net/&quot;&gt;System Crafters&lt;/a&gt; gets an
honorable mention because he streamed
&lt;a href=&quot;https://www.youtube.com/watch?v=zfapNfOY8ac&quot;&gt;several&lt;/a&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=Xq2uMjWJWE0&quot;&gt;times&lt;/a&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=d8lcOUiDEPE&quot;&gt;throughout&lt;/a&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=EUsBLBsbAqw&quot;&gt;the&lt;/a&gt;
&lt;a href=&quot;https://www.youtube.com/watch?v=YwKYuqsFFPQ&quot;&gt;jam&lt;/a&gt; while working on a
MUD built with Goblins that was ultimately unsubmitted.&lt;/p&gt;
&lt;p&gt;Our entry was &lt;a href=&quot;https://davexunit.itch.io/goblinville&quot;&gt;Goblinville&lt;/a&gt; and
it was rated the 7th best game in the jam overall.  Not bad!&lt;/p&gt;
&lt;h2&gt;About Goblinville&lt;/h2&gt;
&lt;p&gt;Goblinville is a 2D, multiplayer, virtual world demo.  During last
year’s &lt;a href=&quot;https://itch.io/jam/spring-lisp-game-jam-2024&quot;&gt;Spring Lisp Game
Jam&lt;/a&gt; we made
&lt;a href=&quot;https://davexunit.itch.io/cirkoban&quot;&gt;Cirkoban&lt;/a&gt; with a restricted
subset of Goblins that had no network functionality.  Since then,
we’ve made a lot of progress porting Goblins to Hoot, culminating with
the &lt;a href=&quot;/news/spritely-goblins-v0-15-0-goblins-in-the-browser.html&quot;&gt;Goblins 0.15.0
release&lt;/a&gt;
in January that featured &lt;a href=&quot;https://ocapn.org&quot;&gt;OCapN&lt;/a&gt; working in the web
browser using WebSockets.&lt;/p&gt;
&lt;p&gt;Given all of this progress, we &lt;em&gt;really&lt;/em&gt; wanted to show off a networked
game this time.  Making a multiplayer game for a jam is generally
considered a bad idea, but Spritely is all about building networked
communities so that’s what we set out to do.  Our goal was to make
something of a spiritual successor to the &lt;a href=&quot;/news/growing-a-networked-garden-with-spritely-goblins.html&quot;&gt;community garden
demo&lt;/a&gt; I
made when I first joined Spritely.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-05-29-goblinville.png&quot; alt=&quot;Screenshot of Jessica Tallon inGoblinville&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;h2&gt;What went well&lt;/h2&gt;
&lt;p&gt;First, let’s reflect on the good stuff.  Here’s what went well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Having participated in this jam a number of times, we have gotten
pretty good at &lt;em&gt;scoping projects down&lt;/em&gt; into something achievable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Goblins made it easy to describe the game world as a &lt;em&gt;collection of
actors that communicate asynchronously&lt;/em&gt;.  Initially, the entire
world was hosted inside a single web browser tab.  Once enough
essential actors were implemented it was a simple task to push most
of those actors into a separate server process.  Since sending a
message to a Goblins actor is the same whether it is local or
remote, this change required little more than setting up an OCapN
connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Communicating with actors over OCapN really helped with creating an
architecture that &lt;em&gt;separated server state from client-side input and
rendering concerns&lt;/em&gt;.  This was harder to think about with Cirkoban
because there was no network separation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;a href=&quot;https://codeberg.org/spritely/hoot-game-jam-template&quot;&gt;Hoot game jam
template&lt;/a&gt; made
it easy to &lt;em&gt;get started quickly&lt;/em&gt;.  It had been a year since we made
our last game, so having a small template project was useful while
we were refreshing our memory about the various Web APIs we needed
to use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The vast amount of freely licensed &lt;a href=&quot;https://lpc.opengameart.org/&quot;&gt;Liberated Pixel
Cup&lt;/a&gt; (something our Executive Director
Christine Lemmer-Webber organized back in her days at Creative
Commons) assets allowed us to &lt;em&gt;focus on the code&lt;/em&gt; while still having
pleasing graphics that felt unified.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a bonus, David Wilson gave Goblinville a shout out on a &lt;a href=&quot;https://youtu.be/zi99nFfNT-k?t=1035&quot;&gt;System
Crafters stream&lt;/a&gt; and a bunch of
people joined the server while I was online!  It was a really cool
moment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-05-23-goblinville.png&quot; alt=&quot;Screenshot of six Goblinville players on screen at once&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;h2&gt;What didn’t go so well&lt;/h2&gt;
&lt;p&gt;Game jams are fast paced (even though the Lisp Game Jam is more
relaxed than the average jam) and not everything goes according to
plan.  A big part of the game jam experience is to practice adjusting
project scope as difficulties arise.  Issues with the project
included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Time pressure&lt;/em&gt;.  Unfortunately, we didn’t have as much time to
dedicate to this project that we would have liked.  We weren’t able
to start work until the Monday after the jam started, so we only had
7 days instead of 10. Also, I came down with a cold at the end of
the week which didn’t help my productivity.  Making something that
felt as polished as Cirkoban simply wasn’t possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Lack of persistence&lt;/em&gt; for the game world.  There’s still some amount
of pre-planning that goes into writing actors that can persist that
we didn’t have time for.  Furthermore, while our persistence system
is written to support incremental updates, we don’t have a storage
backend that supports it yet.  Each tick of the game world would
trigger a full re-serialization and we felt that was too much of a
performance penalty.  We hope that by the next jam this will no
longer be an issue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As predicted, &lt;em&gt;multiplayer increased overall complexity&lt;/em&gt;.  What felt
like a stable enough world during local testing was quickly shown to
have several performance issues and bugs once it was released to the
public and other people started using it.  We had to restart the
server once every day or so during the jam rating period (though we
have resolved these issues in a post-jam update).  Since we weren’t
persisting the game world, each restart wiped out all registered
players and the state of the map.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;No client-side prediction&lt;/em&gt; to mask lag.  For example, when you
press an arrow key to move, you won’t see the player sprite move in
the client until it receives a notification from the server that the
move was valid.  In other words, how responsive the controls feel is
directly tied to server lag.  A production game client would move
the player immediately and fix things up later if it receives
contradictory information from the server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-05-19-goblinville.png&quot; alt=&quot;Screenshot of 3 Goblinville players online, user “djm” is saying“hi!”&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;h2&gt;Post-jam updates&lt;/h2&gt;
&lt;p&gt;We did a bit of additional work after the jam was over to sand some of
the roughest edges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Re-architected the server update loop&lt;/em&gt; to greatly reduce message
volume.  Because it was simple to implement, actors in the game
world were being sent a &lt;code&gt;tick&lt;/code&gt; message at 60Hz to update their
internal state.  Most of the time, the actors would simply do
nothing.  A plant that is done growing has nothing left to do, so
that’s 60 wasteful messages per second per plant.  Instead, a timer
system was added to schedule things to happen after so many ticks of
the game world and the &lt;code&gt;tick&lt;/code&gt; method was removed from all game
objects.  This greatly improved server stability, especially for
worlds with lots of live objects.  As of writing, we’ve had a server
running for six days without any noticeable increase in lag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added a &lt;em&gt;server event log&lt;/em&gt;.  It was hard to see what was going on in
the world during the jam rating period without being connected to
the graphical client.  Now the server process emits a timestamped
log of every event to standard output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;em&gt;character sprite selection&lt;/em&gt;.  This feature just barely missed
the jam submission deadline, but it’s in now!  Instead of all
players being the same sprite, there are now six to choose from.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;Took down the public server&lt;/em&gt;.  For the jam submission version, we
had baked a URI into the itch.io client to a public server we were
hosting so the game would “just work”.  This was particularly
important for the other participants who were rating the submitted
games and giving feedback.  Since the jam rating period is now over,
we took down the public server.  If you’re interested in trying out
Goblinville, you can follow the instructions in the
&lt;a href=&quot;https://codeberg.org/spritely/goblinville#readme&quot;&gt;README&lt;/a&gt; to host
your own server.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, Spritely co-founder Randy Farmer stopped by our updated
Goblinville world!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-06-03-goblinville.png&quot; alt=&quot;Screenshot of current Spritely staff plus co-founder Randy Farmer inGoblinville&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;Goblinville turned out to be more of a tech demo than a true game, but
we’re quite happy with the result.  We think it’s a good demonstration
of what can be built with Goblins and Hoot in a short amount of time.
We hope to build on this success to create even more engaging,
featureful demos in the future!&lt;/p&gt;
</content></entry><entry><title>Functional hash tables explained</title><id>https://spritely.institute/news/functional-hash-tables-explained.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-05-21T13:00:00Z</updated><link href="https://spritely.institute/news/functional-hash-tables-explained.html" rel="alternate" /><content type="html">&lt;h2&gt;Prologue: The quest for a functional hash table for Goblins&lt;/h2&gt;
&lt;p&gt;For those of us that use the Lisp family of programming languages, we
have much appreciation for the humble pair.  Using pairs, we can
construct singly-linked lists and key/value mappings called
&lt;em&gt;association lists&lt;/em&gt;.  Lists built from pairs have the pleasant
property of being &lt;em&gt;immutable&lt;/em&gt; (if you abstain from using setters!) and
&lt;em&gt;persistent&lt;/em&gt;: extending a list with a new element creates a new list
that shares all the data from the original list.  Adding to a list is
a constant time operation, but lookup is &lt;em&gt;linear time&lt;/em&gt;.  Thus, lists
are not appropriate when we need constant time lookup.  For that, we
need hash tables.&lt;/p&gt;
&lt;p&gt;The classic hash table is a mutable data structure and one that our
Lisp of choice
(&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Hash-Table-Reference.html&quot;&gt;Guile&lt;/a&gt;,
a Scheme implementation) includes in its standard library, like most
languages.  Hash tables are neither immutable nor persistent; adding
or removing a new key/value pair to/from a hash table performs an
in-place modification of the underlying memory.  Mutable data
structures introduce an entire class of bug possibilities that
immutable data structures avoid, and they’re particularly tricky to
use successfully in a multi-threaded program.&lt;/p&gt;
&lt;p&gt;Fortunately, there exists an immutable, persistent data structure that
is suitable: the &lt;a href=&quot;https://en.wikipedia.org/wiki/Hash_array_mapped_trie&quot;&gt;Hash Array Mapped
Trie&lt;/a&gt; or HAMT,
for short.  This data structure was introduced by Phil Bagwell in the
paper &lt;a href=&quot;https://lampwww.epfl.ch/papers/idealhashtrees.pdf&quot;&gt;“Ideal Hash Trees”
(2001)&lt;/a&gt;.  HAMTs
were popularized in the Lisp world by
&lt;a href=&quot;https://clojure.org/reference/data_structures#Maps&quot;&gt;Clojure&lt;/a&gt; over a
decade ago.  Unfortunately, Guile does not currently provide a
HAMT-based functional hash table in its standard library.&lt;/p&gt;
&lt;p&gt;Instead, Guile comes with the
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/VLists.html&quot;&gt;VList&lt;/a&gt;,
another one of Phil Bagwell’s creations which can be used to build a
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/VHashes.html&quot;&gt;VHash&lt;/a&gt;.
Goblins currently uses VLists for its functional hash table needs, but
HAMTs are better suited for the task.&lt;/p&gt;
&lt;p&gt;There are various implementations of HAMTs in Scheme floating about,
but none of them seem to have notable adoption in a major Guile
project.  So, we thought we’d write our own that we could ensure meets
the needs of &lt;a href=&quot;/goblins&quot;&gt;Goblins&lt;/a&gt;, compiles on &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt;, and that
might just be useful enough to send upstream for inclusion into Guile
after it’s been battle tested.  To that end, we recently added the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/branch/main/goblins/utils/hashmap.scm&quot;&gt;&lt;code&gt;(goblins utils hashmap)&lt;/code&gt;&lt;/a&gt;
module.  This will become the base for a future re-implementation of
the &lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.15.0/Common.html#index-_005eghash&quot;&gt;&lt;code&gt;^ghash&lt;/code&gt;
actor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Okay, enough context.  Let’s talk about HAMTs!&lt;/p&gt;
&lt;h2&gt;What the hoot is a HAMT anyway?&lt;/h2&gt;
&lt;p&gt;From the outside, HAMTs can seem rather mysterious and intimidating.
That’s how I felt about them, at least.  However, once I dug into the
topic and started writing some code, I was pleasantly surprised that
the essential details were not too complicated.&lt;/p&gt;
&lt;p&gt;As mentioned previously, the HAMT is an immutable, persistent data
structure that associates keys with their respective values, just like
a regular hash table.  By utilizing a special kind of tree known as a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Trie&quot;&gt;“trie”&lt;/a&gt; plus some nifty bit
shifting tricks, HAMTs achieve &lt;em&gt;effectively&lt;/em&gt; constant time insertion,
deletion, and lookup despite all operations being logarithmic time on
paper.&lt;/p&gt;
&lt;p&gt;A trie differs from a tree in the following way: tries only store keys
in their leaf nodes.  If you’re familiar with binary search trees,
tries aren’t like that.  Instead, the key itself (or the hash thereof,
in our case) encodes the path through the trie to the appropriate leaf
node containing the associated value.  Tries are also called “prefix
trees” for this reason.&lt;/p&gt;
&lt;p&gt;A binary tree node can have at most two children, but HAMTs have a
much larger branching factor (typically 32).  These tries are wide and
shallow, so few nodes need to be traversed to find the value for any
given key.  This is why the logarithmic time complexity of HAMT
operations can be treated as if it were constant time in practice.&lt;/p&gt;
&lt;h2&gt;Trie representation&lt;/h2&gt;
&lt;p&gt;To explain, let’s start by defining a trie node using a branching
factor of 32.  At its simplest, we can think of a trie node as a
32-element array.  Each element of the trie can contain either a &lt;em&gt;leaf
node&lt;/em&gt; (a key/value pair) or a pointer to a &lt;em&gt;subtrie&lt;/em&gt; (another 32
element array).&lt;/p&gt;
&lt;p&gt;For example, a HAMT with 3 entries might look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt0.png&quot; alt=&quot;Example trie visualization&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;This is nice and simple, but it’s a little &lt;em&gt;too&lt;/em&gt; simple.  With such a
large branching factor, it’s likely that many boxes in the trie will
have nothing in them, like in the above example.  This is a waste of
space.  The issue is further compounded by immutability: adding or
removing a single element requires allocating a new 32 element array.
For the sake of efficiency, we need to do better.&lt;/p&gt;
&lt;p&gt;Instead, we’ll use a sparse array that only stores the &lt;em&gt;occupied&lt;/em&gt;
elements of the trie node.  To keep track of which elements of the
theoretical 32 element array are occupied, we’ll use a bitmap.  Below
is an example trie:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt1.png&quot; alt=&quot;Example trie visualization&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;In the above example, bits 4 and 10 (starting from the right) are set.
This means that of the 32 possible elements, only 2 are currently
occupied.  Thus, the size of the underlying array is 2.&lt;/p&gt;
&lt;p&gt;To get the number of occupied elements in the trie node, we simply
count the number of 1s in the bitmap.  This is known as the
“population count”.  (We could also just check the size of the array,
but this bit counting idea is about to have another important use.)&lt;/p&gt;
&lt;p&gt;To retrieve the value at index 10, we need to perform a translation to
get an index into the underlying array.  To do this, we compute the
population count of the bits set &lt;em&gt;to the right&lt;/em&gt; of bit 10.  There is 1
bit set: bit 4.  Thus, the value we’re looking for is stored at index
1 in the underlying array.  If we take a peek, we find &lt;code&gt;bar&lt;/code&gt; → 2
there.&lt;/p&gt;
&lt;p&gt;Thanks to this sparse storage technique, insertion/deletion operations
will allocate less memory overall.&lt;/p&gt;
&lt;h2&gt;Insertion algorithm&lt;/h2&gt;
&lt;p&gt;With the trie representation out of the way, let’s walk through the
algorithm to insert a new key/value pair.  The insertion algorithm
covers all the essential aspects of working with a HAMT.&lt;/p&gt;
&lt;p&gt;Let’s say we want insert the mapping &lt;code&gt;foo&lt;/code&gt;→42 into an empty HAMT.  The
empty HAMT consists of a single node with an empty bitmap and an empty
array:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt2.png&quot; alt=&quot;Empty trie&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;To figure out where to store the key &lt;code&gt;foo&lt;/code&gt;, we first need to compute
the hash code for it.  For ease of demonstration, we’ll use 10-bit
hash codes.  (In practice, 32 bits or more is ideal.)&lt;/p&gt;
&lt;p&gt;Let’s say our fictitious hash function produces the hash bits
&lt;code&gt;1000010001&lt;/code&gt; for &lt;code&gt;foo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each trie node has 32 possible branches. Thus, the range of indices
for a node can be represented using a 5-bit unsigned integer.  What if
we took the 5 most significant bits of the hash code and used that as
our index into the trie?  Wow, that sounds like a clever idea!  Let’s
do that!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt3.png&quot; alt=&quot;Hash bits for foo&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;So, &lt;code&gt;foo&lt;/code&gt; gets inserted at index 16.  The original trie is empty, so
we’ll make a new trie with a single leaf node.  To do this, we need to
set bit 16 in our bitmap and create an array with just one key/value
pair in it.  Our output trie looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt4.png&quot; alt=&quot;Single level trie with one leaf node&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Note that we only examined 5 bits of the hash code.  We only need to
examine as many bits in the hash as it takes to find an empty element.&lt;/p&gt;
&lt;p&gt;Let’s insert another key/value pair, this time for key &lt;code&gt;bar&lt;/code&gt; and
value 17.  Our made-up hash code for &lt;code&gt;bar&lt;/code&gt; is &lt;code&gt;0100100001&lt;/code&gt;.  Repeating
the process above, the most significant 5 bits are &lt;code&gt;01001&lt;/code&gt;, so our
index is 9, another unoccupied index.  Our new trie looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt5.png&quot; alt=&quot;Single level trie with two leaf nodes&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Because 9 &amp;lt; 16, the entry for &lt;code&gt;bar&lt;/code&gt; is stored in array index 0,
followed by &lt;code&gt;foo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As a last example, let’s insert a key/value pair where the most
significant bits of the hash code collide with an existing entry.
This time, the key is &lt;code&gt;baz&lt;/code&gt; and the value is 66.  Our made-up hash
code for &lt;code&gt;baz&lt;/code&gt; is &lt;code&gt;1000000001&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The most significant 5 bits are &lt;code&gt;10000&lt;/code&gt;, so our index is 16.  Now
things get interesting because 16 is already occupied; the first 5
bits are not enough to distinguish between the keys &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;baz&lt;/code&gt;!
To resolve this, we’ll replace the leaf node with a &lt;em&gt;subtrie&lt;/em&gt; that
uses the next 5 bits when calculating indices.  The resulting root
trie node will look sort of like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt6.png&quot; alt=&quot;Subtrie placeholder&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, &lt;em&gt;subtrie&lt;/em&gt; is a placeholder for the new trie we
need to construct to hold the mappings for &lt;code&gt;foo&lt;/code&gt; and &lt;code&gt;baz&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To create the subtrie, we just recursively apply the same algorithm
we’ve already been using but with different bits.  Now we’re looking
at these the least significant 5 bits of the hash codes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt7.png&quot; alt=&quot;Hash bits for foo and bar&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;The index for &lt;code&gt;foo&lt;/code&gt; is 17, and the index for &lt;code&gt;baz&lt;/code&gt; is 1.  So, our new
subtrie will have bits 1 and 17 set, and contain 2 leaf nodes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt8.png&quot; alt=&quot;Subtrie with two leaf nodes&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Putting it all together, the complete trie looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/hamt-explainer/hamt9.png&quot; alt=&quot;Complete trie with a subtrie and three total leaf nodes&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Each insertion creates a new trie. The new trie shares &lt;em&gt;nearly&lt;/em&gt; all of
the data with the original trie.  Only the root node and the visited
subtries need to be allocated afresh.  This makes insertion even into
large HAMTs quite efficient.&lt;/p&gt;
&lt;p&gt;The partial hash collision described above could happen recursively,
in which case the trie will grow yet another level deeper upon each
iteration.  The worst case scenario is that two or more keys have the
same exact hash code.  A good hash function and lots of hash bits will
make this a very rare occurrence, but a robust insertion algorithm
needs to account for this case.  We’ll gloss over this edge case here,
but hash collisions can be handled by “bottoming out” to a special
leaf node: a linked list of the colliding key/value pairs.  These
lists need to be traversed linearly when necessary upon lookup or
future insertions that cause more collisions.  In practice, these
collision lists tend to be quite short, often only of length 2.&lt;/p&gt;
&lt;h2&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;The insertion algorithm explains all of the essential elements of a
HAMT and how to work with its sparse storage structure, but we’ll
touch upon lookup and deletion very briefly.&lt;/p&gt;
&lt;p&gt;Looking up the value for a key follows the same basic process as
insertion, but in a read-only manner.  If the hash bits point to an
unoccupied element of a trie node then the search fails right then and
there.  If the bits point to a leaf node with a matching key, the
search succeeds.  If the key doesn’t match, the search fails.
Finally, if the bits point to a subtrie, we recursively search that
subtrie with the next set of bits.&lt;/p&gt;
&lt;p&gt;Deletion is the most complicated operation as it involves path
compression when subtries become empty.  If you’ve been successfully
nerd sniped by this blog post then consider this a homework
assignment.  Give Phil Bagwell’s paper a read! 🙂&lt;/p&gt;
&lt;p&gt;Thanks for following along!  Hope this was fun!&lt;/p&gt;
</content></entry><entry><title>Hoot 0.6.1 released!</title><id>https://spritely.institute/news/hoot-0-6-1-released.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-05-06T12:00:00Z</updated><link href="https://spritely.institute/news/hoot-0-6-1-released.html" rel="alternate" /><content type="html">&lt;p&gt;We are excited to announce the release of &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; 0.6.1!  Hoot
is a Scheme to WebAssembly compiler backend for
&lt;a href=&quot;https://gnu.org/software/guile&quot;&gt;Guile&lt;/a&gt;, as well as a general purpose
WebAssembly toolchain.  In other words, &lt;em&gt;Scheme in the browser!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This small patch release contains bug fixes and a few new features
added since the &lt;a href=&quot;/news/guile-hoot-0-6-0-released.html&quot;&gt;0.6.0 release&lt;/a&gt;
back in January.  Just in time for the upcoming Lisp Game Jam!&lt;/p&gt;
&lt;h2&gt;Make a game with Hoot!&lt;/h2&gt;
&lt;p&gt;The 2025 edition of the &lt;a href=&quot;https://itch.io/jam/spring-lisp-game-jam-2025&quot;&gt;Spring Lisp Game
Jam&lt;/a&gt; starts on Friday,
5/9!  The Lisp Game Jam is a casual, 10-day long event where
participants build small games using the Lisp implementation of their
choice.  We’d like to encourage you to join the jam and try making a
game with Hoot!  Hoot is a great choice for game jams because
web-based games are both easy to distribute and easy for other people
to play.&lt;/p&gt;
&lt;p&gt;To make it easy to jump right in and make a game with Hoot, we have a
&lt;a href=&quot;https://codeberg.org/spritely/hoot-game-jam-template&quot;&gt;ready-to-go template repository on
Codeberg&lt;/a&gt; that
you can clone.  It takes care of all the boilerplate and provides some
useful bindings to essential web APIs like HTML5 Canvas.&lt;/p&gt;
&lt;p&gt;Okay, game jam promotion over!  Read on for the full release notes.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;New &lt;code&gt;--bundle&lt;/code&gt; flag for &lt;code&gt;guild compile-wasm&lt;/code&gt; that will automatically
install runtime libraries alongside the generated binary
(&lt;code&gt;reflect.js&lt;/code&gt;, &lt;code&gt;reflect.wasm&lt;/code&gt;, &lt;code&gt;wtf8.wasm&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Integrated psyntax-based macro expander &lt;code&gt;(hoot expander)&lt;/code&gt; into
&lt;code&gt;eval&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;-g&lt;/code&gt; flag for controlling compiler debug level.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changed &lt;code&gt;current-module&lt;/code&gt; to require compilation with &lt;code&gt;-g&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Changed &lt;code&gt;(hoot environments)&lt;/code&gt; to use run-time module registry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;logcount&lt;/code&gt; and &lt;code&gt;vector-move-left!&lt;/code&gt; procedures, part of Guile's
default environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added experimental &lt;code&gt;(hoot time)&lt;/code&gt; module for date/time calculations
(currently undocumented).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Internal &lt;code&gt;make-port&lt;/code&gt; procedure now uses keyword arguments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keyword error constructors are now exported from Hoot's Wasm ABI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;external-function?&lt;/code&gt; and &lt;code&gt;call-external&lt;/code&gt; to &lt;code&gt;(hoot ffi)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;quote-syntax&lt;/code&gt; to &lt;code&gt;(hoot core-syntax)&lt;/code&gt; and &lt;code&gt;(hoot syntax)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Documented &lt;code&gt;(fibers streams)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;inexact-&amp;gt;exact&lt;/code&gt; conversion for a subset of flonums.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;number-&amp;gt;string&lt;/code&gt; for radix 16.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;current-time&lt;/code&gt; which was mistakenly using jiffies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;string-split&lt;/code&gt; when delimiter is the first character in the
string.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;string-utf8-length&lt;/code&gt; opcode emission.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;define-values&lt;/code&gt; macro that was causing compilation errors on
newer Guile Git builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;cond-expand&lt;/code&gt; error condition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed Wasm linker handling of &lt;code&gt;call_ref&lt;/code&gt; and &lt;code&gt;return_call_ref&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed Wasm linker handling of &lt;code&gt;catch&lt;/code&gt; bodies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;,wasm-eval&lt;/code&gt; metacommand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed NodeJS example in the manual.  Thanks to Jelle Licht for the
patch!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;External references now have a printed representation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exported &lt;code&gt;sleep&lt;/code&gt; from &lt;code&gt;(fibers)&lt;/code&gt; to match upstream Fibers API.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Performance improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Optimized Wasm validation pass (&lt;code&gt;validate-wasm&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optimized Wasm linker (&lt;code&gt;add-stdlib&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optimized Wasm resolver (&lt;code&gt;resolve-wasm&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optimized Wasm emission in compiler backend
(&lt;code&gt;high-level-cps-&amp;gt;wasm&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Browser compatibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Firefox 121 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Google Chrome 119 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatibility with Safari/WebKit is still a bit complicated,
unfortunately.  We thought everything was in place when Hoot 0.6.0
was released, but then we found &lt;a href=&quot;https://bugs.webkit.org/show_bug.cgi?id=288722&quot;&gt;a bug in WebKit’s Wasm tail call
implementation&lt;/a&gt;.
This bug was fixed nearly two months ago but the fix has not yet
propagated out to stable Safari and iOS releases, as far we’re
aware.  We verified that some of our larger Hoot programs are now
working on a Git build of WebKit, so we are optimistic that Hoot
will &lt;em&gt;finally&lt;/em&gt; be supported on all major browsers soon.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Get Hoot 0.6.1!&lt;/h2&gt;
&lt;p&gt;Hoot is already available in GNU Guix:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile-next guile-hoot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Hoot currently requires a bleeding-edge version of Guile, hence
&lt;code&gt;guile-next&lt;/code&gt; above.)&lt;/p&gt;
&lt;p&gt;Otherwise, Hoot can be built from source via our release tarball.  See
the &lt;a href=&quot;/hoot&quot;&gt;Hoot homepage&lt;/a&gt; for a download link and GPG signature.&lt;/p&gt;
&lt;p&gt;Documentation for Hoot 0.6.1, including build instructions, can be
found
&lt;a href=&quot;https://spritely.institute/files/docs/guile-hoot/0.6.1/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch!&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/hoot&quot;&gt;Hoot project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build something cool with Hoot (for the upcoming game jam or
otherwise), let us know on our &lt;a href=&quot;https://community.spritely.institute&quot;&gt;community
forum&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Special thanks to the &lt;a href=&quot;https://metamask.io/news/developers/meta-mask-grants-dao-funds-spritely-foundation/&quot;&gt;MetaMask
folks&lt;/a&gt;
for &lt;a href=&quot;https://spritely.institute/news/guile-on-web-assembly-project-underway.html&quot;&gt;funding this
work&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Until next time, happy hooting! 🦉&lt;/p&gt;
</content></entry><entry><title>Announcing Spritely Oaken</title><id>https://spritely.institute/news/announcing-spritely-oaken.html</id><author><name>Daphne Preston-Kendal</name><email>contact@spritely.institute</email></author><updated>2025-05-05T00:00:00Z</updated><link href="https://spritely.institute/news/announcing-spritely-oaken.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;/static/images/sprites/oaken-square.png&quot; alt=&quot;Spritely's Oaken mascot&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;Hi! I’m Daphne Preston-Kendal! You may remember me from such programming languages as the &lt;a href=&quot;https://r7rs.org/&quot;&gt;Revised⁷ Report on the Algorithmic Language Scheme&lt;/a&gt;. In this guest post, I’m going to explain the work I’m doing for Spritely, funded by NLnet: developing a new secure Scheme sublanguage for running untrusted code, like modifications to an existing app, in a safe way!&lt;/p&gt;
&lt;p&gt;Usually, loading some random third-party code into your program is considered a security risk. Games are probably the most popular category of app these days where it’s normal for them to be moddable by adding more code, but we can see what a security disaster this is. Hardly a week goes by without yet another story of some rascal sharing a Minecraft mod pack on a forum or uploading a mod to the Steam Workshop which, in reality, exists to steal browser cookies or passwords.&lt;/p&gt;
&lt;p&gt;If fun things like games can’t stop malicious mods, what hope do we have to build more serious moddable apps which get deployed on major server systems? What if a malicious mod got onto servers with sensitive data about thousands or millions of people?&lt;/p&gt;
&lt;p&gt;Obviously, though, not all code is evil: probably most code isn’t, at least not deliberately. Do we really have to throw out the entire idea of running third-party modifications to our software just because some small amount of it might do bad stuff?&lt;/p&gt;
&lt;h2&gt;What code can you trust?&lt;/h2&gt;
&lt;p&gt;Here’s a pop quiz! What potential security problems could the following Scheme code cause? (Assume that we are working on a fully-conformant implementation of the R6RS, and this is evaluated with just the &lt;a href=&quot;https://www.r6rs.org/corrected/html/r6rs/r6rs-Z-H-14.html#node_chap_11&quot;&gt;&lt;code&gt;(rnrs base (6))&lt;/code&gt; library&lt;/a&gt; imported.)&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go ahead – it’s not a trick question!&lt;/p&gt;
&lt;p&gt;The answer, obviously, is none! Evaluating this expression can’t access any system resources; once evaluated, you get a procedure, and the worst thing that can happen then is that someone gives it something that’s not a number (which will just safely raise an exception). This code will never steal your bank account information, eat all the cheese in your fridge, cause demons to fly out of your nose, etc.&lt;/p&gt;
&lt;p&gt;So obviously, we can trust &lt;em&gt;some&lt;/em&gt; code. For a simple procedure like this, we can audit it and see what it does! But for more complex code, it’s not reasonable to carefully read thousands of lines to try and find anything that looks nefarious. Attackers also keep finding new ways to hide malicious code from human auditors, from weird tricks with Unicode control characters to &lt;a href=&quot;https://github.com/orgs/community/discussions/151605&quot;&gt;just putting loads of spaces at the start of lines so you have to scroll horizontally to see the evil code.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What if we could &lt;em&gt;automatically&lt;/em&gt; audit code to be sure that it doesn’t do anything dodgy? Could that avoid human errors (like failing to notice a horizontal scrollbar)?&lt;/p&gt;
&lt;p&gt;To do that, we have to go a level further and consider how we came to the conclusion that procedures like the one above are safe. How do we know? Well, &lt;code&gt;+&lt;/code&gt; is a pure function: it takes some input value and returns a new, completely different output value. It doesn’t change anything about the input value: if you call &lt;code&gt;((lambda (x) (+ x 1)) sensitive-number)&lt;/code&gt;, the value of &lt;code&gt;sensitive-number&lt;/code&gt; itself won’t have changed when the add-one procedure is done with it. It doesn’t open any files or connect to any other remote computers, so your &lt;code&gt;sensitive-number&lt;/code&gt; won’t be leaked to any bad guys. Also, it doesn’t allocate any memory or loop. Even a pure-functional program can do that – but at scale, that causes resource exhaustion, which could make an important service inaccessible until the tied-up resources are released. Well, we can treat any pure functional program as safe, so pretty much everything in the &lt;code&gt;(rnrs base (6))&lt;/code&gt; library is okay – and maybe when we run it we’ll be sure to watch it to make sure it doesn’t use too many CPU cycles.&lt;/p&gt;
&lt;p&gt;So we’ve got our first idea – but, sorry, I’ve gotta interrupt this blog post now because Christine just sent me some code she thinks I should put in my next program. It looks pretty nifty – it’s a square root procedure written in just Scheme! Hey, you know what, let’s take a look together:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;dustyweb&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good so far!&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;rnrs&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;rnrs&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;io&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;ports&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uh-oh. Why does a square root procedure need port I/O? &lt;code&gt;Square-root&lt;/code&gt; should be a pure function, but &lt;a href=&quot;https://www.r6rs.org/corrected/html/r6rs-lib/r6rs-lib-Z-H-9.html#node_sec_8.2&quot;&gt;that library&lt;/a&gt; gives Christine’s code complete access to my filesystem. What’s she up to? Is she trying to read my private data? Or has thinking about all those evil Minecraft mods and how to detect them made me paranoid??&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-enough?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;abs&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;improve-guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-symbol&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;call-with-port&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;open-file-output-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;sqrt-log.txt&amp;quot;&lt;/span&gt;
                               &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;file-options&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;no-fail&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                               &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;buffer-mode&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                               &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;native-transcoder&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;finding square root of: &amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-datum&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-char&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#\newline&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;guess: &amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-datum&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-char&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#\newline&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;cond&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-enough?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;good enough, ship it!\n&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                 &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;else&lt;/span&gt;
                 &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;improve-guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, actually, that looks perfectly innocent as far as uses of the filesystem go. Christine probably wanted to log the guesses so she could debug her code. Still, if I already have any file called &lt;code&gt;sqrt-log.txt&lt;/code&gt;, this will inadvertently delete and write over it. Also, I wanted to use this procedure in a program to work out how much &lt;a href=&quot;https://en.wikipedia.org/wiki/Perspective_distortion&quot;&gt;perspective distortion&lt;/a&gt; I should expect with various lenses on some of the weird and wacky film formats I photograph on. (The amount of perspective distortion is proportional to a function of the difference between the focal length and the corner-to-corner diagonal of the image plane.) but what weird and wacky film formats I use is my own private business, and I don’t necessarily want it sent to Christine’s server: imagine if instead of &lt;code&gt;(rnrs io ports (6))&lt;/code&gt;, she’d imported &lt;a href=&quot;https://srfi.schemers.org/srfi-106/srfi-106.html&quot;&gt;&lt;code&gt;(srfi :106)&lt;/code&gt; (socket library)&lt;/a&gt; and sent all that data off to her server to spy on the square roots I’m calculating! I can’t just trust any code Christine sends me.&lt;/p&gt;
&lt;p&gt;Hmm. Maybe we’ve learned something here, though – something we can use to improve our intuition about what code we can trust in general. I’m fine with recording what square roots I take, and what steps were taken to calculate them – &lt;em&gt;on my own computer&lt;/em&gt;. Can we reformulate Christine’s library so that she can still have her debugging information, but it still only imports the &lt;code&gt;(rnrs base (6))&lt;/code&gt; library? Then we know we can trust her code just by looking at the import list!&lt;/p&gt;
&lt;p&gt;How about something like this?&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;library&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;dustyweb&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;rnrs&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-enough?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;abs&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;improve-guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;syntax-symbol&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;debug-log&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;debug-log&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;finding square root of&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;debug-log&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;guess&amp;quot;&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;cond&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;close-enough?&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;debug-log&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;good enough, ship it!&amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;else&lt;/span&gt;
             &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;loop&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;improve-guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;guess&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when I write my perspective-distortion program, I call &lt;code&gt;square-root&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;square-root&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;square&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;image-width&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;square&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;image-height&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
             &lt;span class=&quot;syntax-symbol&quot;&gt;ignore&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;ignore&lt;/code&gt; is a procedure I wrote and trust that accepts any number of arguments, does nothing with them, and returns nothing of interest. In other words, the logging calls do nothing for me because they’re not very interesting to me. Meanwhile, when Christine is debugging her program, she can write a &lt;code&gt;debug-log&lt;/code&gt; procedure that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-debug-log-procedure&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;case-lambda&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-char&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#\newline&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-string&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;: &amp;quot;&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-datum&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;put-char&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;log-port&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;#\newline&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;debug-log&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;make-debug-log-procedure&lt;/span&gt;
                   &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;open-file-output-port&lt;/span&gt; &lt;span class=&quot;syntax-string&quot;&gt;&amp;quot;sqrt-log.txt&amp;quot;&lt;/span&gt;
                                          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;file-options&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;no-fail&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                                          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;buffer-mode&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
                                          &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;native-transcoder&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What happened here? &lt;code&gt;Square-root&lt;/code&gt; can still do I/O, but suddenly we’re able to trust it because we know that the library it’s in only has access to procedures we trust. In other words, we only have to look at that one line in the program to know that we can completely trust the whole program! But, somehow, it can still do the I/O for the debug log Christine wants. The reason is that when &lt;em&gt;we&lt;/em&gt; call it, we explicitly &lt;em&gt;give&lt;/em&gt; it the ability to do I/O in a limited way: we write code ourselves – code we trust to access libraries like &lt;code&gt;(rnrs io ports (6))&lt;/code&gt; or &lt;code&gt;(srfi :106)&lt;/code&gt; – and pass in a procedure from outside that exposes the capabilities of those libraries in a specific, limited way. Even if I did want to log what &lt;code&gt;square-root&lt;/code&gt; is doing, using Christine’s &lt;code&gt;debug-log&lt;/code&gt; procedure would only let it write to one specific file, and in a very specific way. It couldn’t use that ability to start reading my browser cookies like it could if I gave it the full &lt;code&gt;(rnrs io ports (6))&lt;/code&gt; library. Even better, if you &lt;em&gt;do&lt;/em&gt; want to get the log, you can change where it goes just by passing a different port to &lt;code&gt;make-debug-log-procedure&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Make-debug-log-procedure&lt;/code&gt; uses a technique called closure so that its own argument, the &lt;code&gt;log-port&lt;/code&gt;, is available to the &lt;code&gt;debug-log&lt;/code&gt; procedure even though it isn’t an argument to it. (We say that the &lt;code&gt;debug-log&lt;/code&gt; procedure is &lt;em&gt;closed over&lt;/em&gt; the &lt;code&gt;log-port&lt;/code&gt; variable – or more informally, that it’s closed over the port itself.) Once &lt;code&gt;make-debug-log-procedure&lt;/code&gt; returns, the &lt;code&gt;debug-log&lt;/code&gt; procedure it creates has access to that port and exposes it in a limited way, which means the &lt;code&gt;(dustyweb square-root)&lt;/code&gt; library doesn’t need to import &lt;code&gt;(rnrs io ports (6))&lt;/code&gt; any more. But Christine can still use &lt;code&gt;open-file-input-port&lt;/code&gt; when she writes a debugging procedure like &lt;code&gt;make-debug-log-port&lt;/code&gt;, because she obviously trusts herself to use it responsibly. In fact, anyone who actually uses her library can trust themselves to access their own files.&lt;/p&gt;
&lt;p&gt;It would still be nice to be able to use &lt;code&gt;square-root&lt;/code&gt; as if it were a pure function, not having to pass in this ugly &lt;code&gt;debug-log&lt;/code&gt; argument. That gives me an idea: could we close the whole library over a procedure like &lt;code&gt;debug-log&lt;/code&gt;? That would mean Christine could also extend her library to include all sorts of useful functions, like cube roots or inverse square roots – all of them safely &lt;code&gt;debug-log&lt;/code&gt;ging away – and I would only need to pass in the &lt;code&gt;debug-log&lt;/code&gt; argument once when I import her library.&lt;/p&gt;
&lt;p&gt;Now we’ve actually arrived at a much better idea than trying to audit all the third-party code we might possibly want to use. Instead of doing that, we just make sure it only uses safe procedures or ones we’ve given it!&lt;/p&gt;
&lt;p&gt;Unfortunately, neither R6RS nor R7RS Scheme provides the ability to make an entire library into a closure. But maybe we can design a system that does have this ability – that’s Oaken!&lt;/p&gt;
&lt;h2&gt;Taming libraries to stop them eating resources&lt;/h2&gt;
&lt;p&gt;In programming security research, this kind of trick of using language features to turn an unsafe library into a safer one is called ‘taming’. Like humans tamed wolves into dogs to stop them eating their sheep programmers can tame libraries to stop them providing access to the procedures that could create a security vulnerability.&lt;/p&gt;
&lt;p&gt;I can go down to the pet shop and buy a dog and train it to recognize me as its owner. As its owner, I’m in charge and I can do all the things the dog isn’t allowed to do in my household – sleep on the human bed, eat food from the dinner table, dig holes in the garden, etc. In security terminology, this kind of general permission is called ‘ambient authority’. But, as a treat, I could let the dog do some of those things sometimes, but only when I allow it. The dog doesn’t get the ambient authority to do these things all of the time: it’s only allowed to do it in the way I let it and when I say so.&lt;/p&gt;
&lt;p&gt;We are in charge of our own programs and our own computers. We get to do everything, but the code we pull in from some library should only be able to do things we allow it to do. We’ve seen how we could do this by closing those libraries over procedures giving more limited access to a resource, but we should really have a standard library for systematically controlling access to the resources which could cause security problems if evil code uses them in evil ways.&lt;/p&gt;
&lt;p&gt;We’re not going to support every use case in the initial version of Oaken: full taming is a really hard problem. (Like &lt;a href=&quot;https://xkcd.com/1831/&quot;&gt;any red-blooded programmer&lt;/a&gt;, I initially thought it was going to be easy, and then all the individual little difficulties slowly started to dawn on me. In fact, I still keep realizing new problems almost every day.) But we’ll provide some very basic tamings that should be good enough to start off with: the idea of Oaken is that the library system supports taming as a general concept, so more advanced controls can follow later.&lt;/p&gt;
&lt;h3&gt;Filesystem and network abuse&lt;/h3&gt;
&lt;p&gt;It should be obvious that giving untrusted code unrestricted read/write access to the whole computer filesystem is a bad idea. In combination with unrestricted ability to connect to other computers over the internet, it’s an even worse idea.&lt;/p&gt;
&lt;p&gt;We already saw a simple idea for taming filesystem access in the square root example, but real-world libraries might legitimately want to create and read their own files to provide some useful functionality to the trusted code which uses those libraries.&lt;/p&gt;
&lt;p&gt;We already have a real world example of how this could work. When you upload a file to a website in your browser, the web site can’t directly read the files on your computer. Instead, your &lt;em&gt;browser&lt;/em&gt;, which does have that unrestricted filesystem access, pops up a file selection dialogue box and asks you to choose a file. The browser then makes that file available to the website by giving it a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/File&quot;&gt;&lt;code&gt;File&lt;/code&gt; object&lt;/a&gt; which it can use to read that file – and &lt;em&gt;only&lt;/em&gt; that file. This pattern of passing limited access to some restricted resource to untrusted code is called the &lt;a href=&quot;https://wiki.c2.com/?PowerBox&quot;&gt;‘powerbox pattern’&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just as you give a website access to one file, with Oaken you can close a library over a version of the file-system access library which, for example, only gives access to one directory. That means the code could never go snooping outside that directory and steal or destroy information that doesn’t belong to it. Simple!&lt;/p&gt;
&lt;p&gt;Unfortunately, file access is really tricky to fully tame. We can easily make it so that a tamed library can only access one directory on the disk, but it’s not so easy to stop that library from writing files in that directory which would eventually eat up all space on the drive. Ideally, you’d be able to limit how much disk space untrusted code can use and not just access to individual files. But that would require looking at what special support the operating system can give us and how to make use of the tools it has to restrict storage abuse, so full taming of filesystem access – beyond restricting code’s access to a single file or directory – is probably out of scope for the initial version of Oaken.&lt;/p&gt;
&lt;p&gt;But what about network access? Should we instantiate each library a different time, passing it in multiple times, for each server and port it wants to access? That sounds a bit annoying. Maybe it would be better to use a different mechanism for powerboxing than the one the library system provides. I’ll be thinking a lot about this and trying to come up with a workable design!&lt;/p&gt;
&lt;h3&gt;Timing attacks&lt;/h3&gt;
&lt;p&gt;Another surprising source of security issues in apps comes just from giving programs access to a simple system clock. Timing attacks can let untrusted code find out information about what’s going on inside of trusted procedures they shouldn’t be able to extract information from, or see the inner workings of; or they can let two untrusted bits of code communicate with one another when they shouldn’t. Mark S. Miller, an influential researcher in the area of secure programming systems, talks about this as ‘prisoners tapping on the pipes’ – sending messages to one another in their cells with Morse code or whatever. You really do have to be careful to ensure that &lt;em&gt;all&lt;/em&gt; potential back-channels for unwanted communication are closed off.&lt;/p&gt;
&lt;p&gt;Preventing this particular kind of attack seems quite simple – there’s no real taming to be done. It’s just a matter of making sure a library only has access to the system clock if you explicitly give it access when you instantiate it, which is an all-or-nothing proposition – unlike carefully controlling which individual files or directories a procedure could access and how. But something I’ve already learned in planning this project is that if something looks simple, I probably haven’t thought about it enough yet, so maybe there are more problems lurking beneath the surface here.&lt;/p&gt;
&lt;h3&gt;Exhaustion attacks&lt;/h3&gt;
&lt;p&gt;It’s not only external resources like files, the network, or the system clock which can pose a security risk. The computer’s CPU and memory are all you need for another kind of attack: a denial of service. If you tie up all the computer’s memory and processor time with nonsense, you can make a service inaccessible to people who actually need to use it. Typically, attackers find some request they can make to a service that takes massively, disproportionately more resources for the service to respond to than it does for them to make the request.&lt;/p&gt;
&lt;p&gt;What tools do you need for this kind of attack? Could we restrict those through the library system too? Unfortunately, that approach won’t work here. In fact, all you need to cause a denial-of-service attack is &lt;code&gt;lambda&lt;/code&gt;, as in this example that’s often shown while explaining how the &lt;a href=&quot;https://en.wikipedia.org/wiki/Fixed-point_combinator&quot;&gt;fixed-point combinator&lt;/a&gt; is derived:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;scheme&quot;&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-special&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;syntax-open&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;syntax-symbol&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;syntax-close&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By conjuring up two procedures which call each other forever, this cute little Scheme expression will peg one of your CPUs until you find a way to stop it. This isn’t good! But this kind of vulnerability to resource-exhaustion attack can happen entirely by accident: just the other day I was hacking on a procedure and accidentally made it loop infinitely on some input.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.lel.ed.ac.uk/~gpullum/loopsnoop.html&quot;&gt;It’s mathematically impossible to always detect this kind of bug in advance.&lt;/a&gt; Moreover, even if a program &lt;em&gt;eventually&lt;/em&gt; stops looping (and there is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Total_functional_programming&quot;&gt;useful subset of programs whose halting behaviour &lt;em&gt;is&lt;/em&gt; provable&lt;/a&gt;), it might still take too much CPU to return its result. (Even just a &lt;a href=&quot;https://en.wikipedia.org/wiki/ReDoS&quot;&gt;badly behaved regular expression implementation&lt;/a&gt; can make resource exhaustion attacks possible!) So the best we can do is put a limit on the amount of computation an untrusted procedure is allowed to do.&lt;/p&gt;
&lt;p&gt;Guile already includes a little library called &lt;a href=&quot;https://www.gnu.org/software/guile/manual/html_node/Sandboxed-Evaluation.html&quot;&gt;&lt;code&gt;(ice-9 sandbox)&lt;/code&gt;&lt;/a&gt; which lets you call code with limits on how much time it can take and how much memory it can allocate. Unfortunately, &lt;code&gt;(ice-9 sandbox)&lt;/code&gt; limits wall-clock time, not actual processor resources, which is not ideal. For example, wall-clock time includes time spent waiting for user input! You don’t want your procedure getting killed for DoSing the system when in fact it’s sitting there doing nothing while a hunt-and-peck typist is writing up their life story into a text box. Worse, there’s no way to powerbox time usage with &lt;code&gt;(ice-9 sandbox)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But there might be a solution: &lt;a href=&quot;https://legacy.cs.indiana.edu/~dyb/pubs/engines.pdf&quot;&gt;engines&lt;/a&gt;! Engines are a Scheme idea introduced by Kent Dybvig and Bob Hieb. The basic concept is that the system runtime keeps track of how many units of computation (‘ticks’) have been executed, and when that number exceeds a certain limit (the amount of ‘fuel’ in the engine), it pauses execution and returns the unfinished task back to whoever started it. They can then decide whether to stop running the computation, or give it more fuel and carry on.&lt;/p&gt;
&lt;p&gt;The ticks can be measured in any unit – with the right support from the OS, you could even make it actual CPU time – but Dybvig and Hieb suggest counting procedure calls. That works well in Scheme because, once you expand all your macros, everything you could do that might cause CPU exhaustion – in particular, looping – involves a procedure call.&lt;/p&gt;
&lt;p&gt;The Guile manual also describes a few problems with the memory limiter in &lt;code&gt;(ice-9 sandbox)&lt;/code&gt;. For now, we’ll probably have to live with those and a few more, like the fact that it’s also not possible to powerbox memory usage. Guile is getting a new garbage collector soon, thanks to Andy Wingo’s sterling work on &lt;a href=&quot;https://github.com/wingo/whippet&quot;&gt;Whippet&lt;/a&gt;; as the migration from BDW to Whippet progresses, maybe it will become possible to fix some of these issues in future. But here also, many operating systems (&lt;em&gt;cough&lt;/em&gt; Linux) make things difficult: we can’t really do anything about the kernel &lt;a href=&quot;https://linux-mm.org/OOM_Killer&quot;&gt;deciding that the Guile process has had quite enough memory for tonight and that it’s time to go home.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Capability attentuation&lt;/h2&gt;
&lt;p&gt;So far we’ve only considered one level of delegation of trust: you have your dog/program, and the dog/program is only allowed to sleep in the dog bed you gave it/use only the files you gave it. But in reality, software dependencies form a much more complex graph, and it should be possible for the things you trust in some limited way to place their trust in other things in even more limited ways.&lt;/p&gt;
&lt;p&gt;Let’s say I have my program, and within it I gave your library access to a key-value store where it gets to store up to 100 MB of data in its own namespace. Your library might want to import some other library, giving it some smaller slice of that data in turn, like 10 MB, and its own subnamespace. So your library needs to be able to take the version of the library you gave it, which is closed over the limit of 100 MB and one namespace, and close it over a different limit and a different, subordinate namespace.&lt;/p&gt;
&lt;p&gt;At the moment, I’m still in the early stages of designing the library system, and supporting this use case seems kind of hard! It’s easy to think about a one-level system where the main, trusted program gets to instantiate every (potentially untrusted) library with some fixed limitations. It’s a lot harder to design a system where it’s easy to start with an existing instantiated, untrusted library and impose even stricter limitations on it to create another library with the same interface but even tighter controls.&lt;/p&gt;
&lt;h2&gt;Onward&lt;/h2&gt;
&lt;p&gt;I’m looking forward to hacking on Oaken over the next couple of months! Oaken is one of the more intriguing parts of the Spritely Institute’s vision for decentralized internet infrastructure. Imagine spreadsheet macros that won’t make off with your sensitive financial data; imagine not just safe mods to existing games, but even something like the ability to send a whole new multiplayer game to your friends in a chat program, who could then just run it right in place on their computer with no security fears; imagine &lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5315-shepherd-with-spritely-goblins-for-secure-system-layer-collaboration/&quot;&gt;the Shepherd&lt;/a&gt; where each service ran in its own capability sandbox! Oaken will enable all of this by working alongside other Spritely projects like Hoot and Goblins.&lt;/p&gt;
&lt;p&gt;Let a thousand Oaken forests bloom!&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;The canonical paper on using Scheme variable scope for security is Jonathan Rees’s Ph.D. dissertation, &lt;a href=&quot;https://mumble.net/~jar/pubs/secureos/&quot;&gt;‘A Security Based on the Lambda Calculus’&lt;/a&gt;. That dissertation is a direct inspiration for Oaken, and Jonathan is advising me on the project.&lt;/p&gt;
&lt;p&gt;As far as I know, the idea of closing modules over things, in the way procedures can be closed over things, comes from the Standard ML programming language. David MacQueen’s paper &lt;a href=&quot;https://dl.acm.org/doi/10.1145/800055.802036&quot;&gt;‘Modules for Standard ML’&lt;/a&gt; explains how it was designed. We’re not the first people to try adapting this idea for Scheme: the &lt;a href=&quot;https://s48.org/1.9.3/manual/manual-Z-H-5.html#node_chap_4&quot;&gt;Scheme 48 module system&lt;/a&gt;, also designed by Jonathan Rees, worked this way. But Oaken will be the first such library system in Scheme specifically designed for security purposes, released for general use, and with a standard library for tamed access to important resources.&lt;/p&gt;
&lt;p&gt;The E programming language pioneered many aspects of capability-based security within a programming language, and the &lt;a href=&quot;http://wiki.erights.org/wiki/Walnut/Ordinary_Programming/Emakers&quot;&gt;Emaker&lt;/a&gt; library system is also an inspiration for Oaken. The same idea was proposed for JavaScript as &lt;a href=&quot;https://github.com/erights/proposal-frozen-realms&quot;&gt;FrozenRealms&lt;/a&gt; which evolved into &lt;a href=&quot;https://github.com/endojs/&quot;&gt;Endo&lt;/a&gt;.&lt;/p&gt;
</content></entry><entry><title>Successful supporter drive supplements Spritely's story</title><id>https://spritely.institute/news/2024-2025-supporter-drive-retrospective.html</id><author><name>Amy Pillow</name><email>contact@spritely.institute</email></author><updated>2025-04-08T00:00:00Z</updated><link href="https://spritely.institute/news/2024-2025-supporter-drive-retrospective.html" rel="alternate" /><content type="html">&lt;video title=&quot;Spritely's Porta-Bella characters jumping for joy! Success!&quot; src=&quot;https://files.spritely.institute/videos/2025-04-08-campaign-success-anim.mp4&quot; autoplay=&quot;true&quot; loop=&quot;true&quot; muted=&quot;true&quot;&gt;&lt;/video&gt;
&lt;p&gt;The Spritely team is overjoyed by &lt;a href=&quot;/donate&quot;&gt;the support&lt;/a&gt; we've received in our
&lt;a href=&quot;https://spritely.institute/news/spritely-launches-supporter-drive.html&quot;&gt;first ever supporter drive&lt;/a&gt;.
More than just donations, these are individual voices which inspire us
to deliver on our promise of a more secure future.&lt;/p&gt;
&lt;blockquote&gt;
    &lt;p&gt;
The world needs social media that isn't powered by centralized sources
that don't have our best interests in mind. Spritely's mission helps
make that more achievable. 🖤
    &lt;/p&gt;
    &lt;p&gt;- Josh Mock&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
    &lt;p&gt;
Spritely is painting a picture of a hopeful, human future for people
and their communication, and have a credible vision to get
there. That's a rare thing, and worthy of support.
    &lt;/p&gt;
    &lt;p&gt;- David Anderson&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
    &lt;p&gt;
My digital life is &lt;b&gt;my&lt;/b&gt; life.
&lt;br&gt;&lt;/br&gt;
Corporations and their centralized web services exist to turn a
profit. They’ll eventually do so by extracting what they can from
me. And by using their services. I’ve been giving them coercive
leverage!
&lt;br&gt;&lt;/br&gt;
To hold pieces of &lt;b&gt;me&lt;/b&gt; hostage.
&lt;br&gt;&lt;/br&gt;
No mas.
    &lt;/p&gt;
    &lt;p&gt;- Moto A&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;More than five hundred people have decided to &lt;a href=&quot;/donate&quot;&gt;donate to Spritely&lt;/a&gt; so
far, with more than three hundred new voices being heard during this campaign!&lt;/p&gt;
&lt;p&gt;We easily surpassed our original goal of $80,000 with weeks to spare!
Even though we fell short of our stretch goal we
&lt;a href=&quot;https://spritely.institute/news/2024-2025-supporter-drive-success.html&quot;&gt;introduced in January&lt;/a&gt;,
our final amount was $90,000, well in excess of what we expected.&lt;/p&gt;
&lt;p&gt;Also, if you donated at the Silver level or higher during the
campaign, your name has already been added to the
&lt;a href=&quot;https://files.spritely.institute/embeds/cirkoban/&quot;&gt;credits for Cirkoban&lt;/a&gt;
in the shiniest way we could think of. Our founder, Christine
Lemmer-Webber, is busy working on doodles to send out to the Diamond
tier supporters and those should be received in the mail in the near
future. As part of our appreciation for your support, we are keeping
the tiered reward system for future donations, regardless of funding
drive, and we hope to add even more rewards in the future.&lt;/p&gt;
&lt;p&gt;Just because the campaign is over doesn't mean we are slowing
down. Just the opposite; your support has given us a second
wind. Already, we've released new versions of
&lt;a href=&quot;/news/spritely-goblins-v0-15-0-goblins-in-the-browser.html&quot;&gt;goblins&lt;/a&gt;
and &lt;a href=&quot;/news/guile-hoot-0-6-0-released.html&quot;&gt;hoot&lt;/a&gt; and we have even more
in store for the near future. On top of this, &lt;a href=&quot;/news/spritely-is-going-to-guix-days-and-fosdem.html&quot;&gt;we gave seven
presentations at
FOSDEM&lt;/a&gt; which
laid out our vision and our progress and allowed us to meet with
partners and supporters in person.&lt;/p&gt;
&lt;p&gt;Despite the recent uncertainty in non-profit funding in the US, we are
continuing to work against centralized interests as hard as we can,
and all of your support makes that possible. Donor drive or not, we
always welcome more &lt;a href=&quot;/donate&quot;&gt;donations&lt;/a&gt; and we appreciate every
dollar and cent.&lt;/p&gt;
&lt;p&gt;Thank you to everyone who made this supporter drive a success. We
couldn't have done it without you!&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.15.1 released!</title><id>https://spritely.institute/news/spritely-goblins-v0-15-1-released.html</id><author><name>David Thompson</name><email>contact@spritely.institute</email></author><updated>2025-03-26T12:45:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-15-1-released.html" rel="alternate" /><content type="html">&lt;p&gt;We are happy to announce the release of Goblins 0.15.1!  This patch
release includes many bug fixes, documentation fixes, and
quality-of-life improvements made since the
&lt;a href=&quot;https://spritely.institute/news/spritely-goblins-v0-15-0-goblins-in-the-browser.html&quot;&gt;0.15.0 release&lt;/a&gt;
back in January.&lt;/p&gt;
&lt;p&gt;Thank you to the Spritely community for identifying and reporting many
of these issues!  We’re thrilled to see more people trying Goblins for
the first time.  Big thanks to Jessica Tallon, who has done nearly all
of the hard work sanding the rough edges for this release.&lt;/p&gt;
&lt;p&gt;For more detail about the changes in this release, see the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.15.1/NEWS&quot;&gt;NEWS&lt;/a&gt;
file.&lt;/p&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed sending ghashes over CapTP that have actor references or other
complex objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed sending messages to far references in persistent vats.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed thread-safety issues in CapTP garbage collection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed issue where severed connections to peers couldn’t be re-enlivened.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed CapTP crossed hellos mitigation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;call-with-vat&lt;/code&gt; blocking the current vat’s event loop.  It now
returns a promise when called in this context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed prelay netlayer not reconnecting after severance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed prelay severance breaking local references.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;on-sever&lt;/code&gt; for WebSocket netlayer on Hoot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed WebSocket error handling on Hoot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;(goblins ocapn netlayer base-port)&lt;/code&gt; and some of its
dependencies not compiling with Hoot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;make install&lt;/code&gt; installing utility libraries that are only for
the test suite.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Documentation fixes/improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed documentation for spawning &lt;code&gt;^onion-netlayer&lt;/code&gt; and registering
sturdyrefs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed the greeter example in the “Persistence Environments” section.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added additional explanation of cooperative multitasking, event
loops, and common vat pitfalls in response to user feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added example of passing an actor reference over OCapN.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Quality-of-life improvements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;race*&lt;/code&gt; variant of &lt;code&gt;race&lt;/code&gt; joiner in &lt;code&gt;(goblins actor-lib joiners)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vectors and non-list pairs (such as key/value pairs within
association lists) are now serializable over CapTP.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exported &lt;code&gt;^persistence-registry&lt;/code&gt; from &lt;code&gt;(goblins)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The fake netlayer can now be halted, which is useful when testing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;on-sever&lt;/code&gt; support to the prelay netlayer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added a new procedure, &lt;code&gt;timeout&lt;/code&gt;, for creating promises that resolve
after a certain amount of time has passed.  &lt;code&gt;timeout&lt;/code&gt; can be found
in the new &lt;code&gt;(goblins actor-lib timers)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Suppressed warning for the override of Guile core binding &lt;code&gt;spawn&lt;/code&gt;
when &lt;code&gt;(goblins)&lt;/code&gt; is imported.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting the release&lt;/h2&gt;
&lt;p&gt;As usual, if you're using Guix, you can upgrade to 0.15.1 by running:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;guix pull
guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the source tarball on our &lt;a href=&quot;/goblins/&quot;&gt;release
page&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch!&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/goblins&quot;&gt;Goblins project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you're making something with Goblins or want to contribute to
Goblins itself, be sure to join our community at
&lt;a href=&quot;https://community.spritely.institute&quot;&gt;community.spritely.institute&lt;/a&gt;!
Thanks for following along and hope to see you there!&lt;/p&gt;
</content></entry><entry><title>Spritely Goblins v0.15.0: Goblins in the browser!</title><id>https://spritely.institute/news/spritely-goblins-v0-15-0-goblins-in-the-browser.html</id><author><name>Christine Lemmer-Webber, Amy Grinn</name><email>contact@spritely.institute</email></author><updated>2025-01-24T12:45:00Z</updated><link href="https://spritely.institute/news/spritely-goblins-v0-15-0-goblins-in-the-browser.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-01-24-goblins-0.15.0-release-art.png&quot; alt=&quot;Goblins version 0.15.0 release art: a Spritely goblin flies on the Hoot owl's back, high in the sky!&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;We are thrilled to announce a long-awaited goal: Goblins in the browser!!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; can now be used to compile Goblins and Goblins-using
applications to WebAssembly! In fact, we even have support for our
distributed programming protocol, &lt;a href=&quot;https://ocapn.org&quot;&gt;OCapN&lt;/a&gt; working
in the browser with Goblins-on-Hoot! Goblins now compiles to
Webassembly in Hoot from the &lt;em&gt;same codebase&lt;/em&gt; used to run Goblins in
Guile's own virtual machine.  This is a major step forward toward
getting this software in the hands of everyday users. We hope it
unlocks a new wave of innovation about what secure collaboration can
look like.&lt;/p&gt;
&lt;p&gt;As part of this release, we are introducing the websocket netlayer
which will allow browser-based applications to easily connect with one
another!&lt;/p&gt;
&lt;p&gt;Also, a number of performance improvements makes this release the fastest
Goblins ever!&lt;/p&gt;
&lt;p&gt;Let's get into it!&lt;/p&gt;
&lt;h2&gt;Seeing is believing: Goblins in the browser IN ACTION!&lt;/h2&gt;
&lt;p&gt;Long-time readers may remember the
&lt;a href=&quot;https://codeberg.org/spritely/goblin-chat&quot;&gt;goblin-chat&lt;/a&gt; demo we've
&lt;a href=&quot;/news/spritely-goblins-v0-10-for-guile-and-racket.html&quot;&gt;shown off&lt;/a&gt;
several times before:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/news/spritely-goblins-v0-10-for-guile-and-racket.html&quot;&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/goblin-chat-interop.gif&quot; alt=&quot;3 goblin-chat programs; two Racket based apps communicate with one Guile-based app&quot;&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Above you can see two Guile goblin-chat programs talking with a Racket
based goblin-chat program, demonstrating two completely different
language runtimes communicating over OCapN via Goblins. The impressive
thing about &lt;code&gt;goblin-chat&lt;/code&gt; is that it's only 150 lines of Goblins code
to implement both the user code and chatroom code for a peer-to-peer
chat program which authenticates messages as they come in!&lt;/p&gt;
&lt;p&gt;Cool enough, but didn't we say Goblins works in the browser now?
Wouldn't it be cool if you could try two goblin-chat programs
communicating in your browser over OCapN... right here, on this page?!&lt;/p&gt;
&lt;p&gt;Well, behold!&lt;/p&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://files.spritely.institute/embeds/goblin-chat-lite/reflect.js&quot;&gt;()&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://files.spritely.institute/embeds/goblin-chat-lite/goblin-chat-lite.js&quot;&gt;()&lt;/script&gt;
&lt;h3&gt;Peer A&lt;/h3&gt;
&lt;div id=&quot;goblin-chat-client-a&quot; class=&quot;goblin-chat-lite&quot;&gt;&lt;/div&gt;
&lt;h3&gt;Peer B&lt;/h3&gt;
&lt;div id=&quot;goblin-chat-client-b&quot; class=&quot;goblin-chat-lite&quot;&gt;&lt;/div&gt;
&lt;p&gt;Try sending messages between Alice and Bob!&lt;/p&gt;
&lt;p&gt;If you don't see anything, note that this demo requires a recent
browser supporting all the WebAssembly extensions used by Hoot,
including Wasm GC and tail call support.  Firefox and Chrome ought to
just work.  Safari is not expected to work properly at this time.&lt;/p&gt;
&lt;p&gt;This is the VERY SAME 150 line goblin-chat program shown in the
previous gif, but compiled to Hoot and running in the browser!
(&lt;a href=&quot;https://files.spritely.institute/embeds/goblin-chat-lite/goblin-chat-lite.scm&quot;&gt;Source code&lt;/a&gt; for the running Webassembly code executing above;
only the &lt;code&gt;^chatroom&lt;/code&gt; and &lt;code&gt;spawn-user-controller-pair&lt;/code&gt; code are
responsible for the chat protocol, the rest of the code is to provide
a user interface.)&lt;/p&gt;
&lt;p&gt;These messages really are being delivered through OCapN too.  We have
a simplified abstract netlayer which simulates the network which we're
using here. However, all the ordinary OCapN protocol operations are
occuring with each message transmitted from one user to the
other. This isn't only real Goblins in the browser, this is real OCapN
in the browser too!&lt;/p&gt;
&lt;h2&gt;More about Goblins on Hoot&lt;/h2&gt;
&lt;p&gt;If you've been paying close attention, you may have noticed that this
release of Goblins was directly preceded by the
&lt;a href=&quot;/news/guile-hoot-0-6-0-released.html&quot;&gt;Hoot 0.6.0 release&lt;/a&gt;.
(It's also no coincidence that the Hoot 0.6.0 release art showed the
Goblins mascot and the Hoot mascot talking about going on an adventure
and this release's release art shows them flying together in the sky!)
Hoot 0.6.0 had a large amount of work put into it which is useful far
beyond Goblins, but Hoot 0.6.0 and Goblins 0.15.0 were actively
developed together. This has been many months of hard work from our
team, and we are proud to see it pay off. Goblins in the browser is
possible at last, opening the door for a future where Spritely's
technology is in the hands of &lt;em&gt;everyone!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Moreover, the foreign function interface feature of Hoot allows
developers to access any part of the enormous javascript ecosystem
within a Goblins app. That includes reactive frameworks, data
processing, and visualization libraries that we think will benefit
user experiences.&lt;/p&gt;
&lt;h2&gt;WebSocket Netlayer&lt;/h2&gt;
&lt;p&gt;We are also releasing a new netlayer specifically designed for
browsers and everyday use: WebSockets!&lt;/p&gt;
&lt;p&gt;The WebSocket NetLayer is the glue between Goblins applications
written for Guile and Hoot. It is currently the first NetLayer
available for both and will allow communication between them!&lt;/p&gt;
&lt;p&gt;Having two different users across two different browsers talk
to each other does require an intermediary. Thankfully our
&lt;a href=&quot;https://files.spritely.institute/docs/guile-goblins/0.14.0/Prelay.html&quot;&gt;prelay netlayer&lt;/a&gt;
works well together with the websockets netlayer, allowing users
across browser pages to talk to each other.&lt;/p&gt;
&lt;p&gt;However, our prelay netlayer is called &amp;quot;prelay&amp;quot; for a reason: it's the
&amp;quot;pre-relay&amp;quot; netlayer. The actual &amp;quot;relay&amp;quot; netlayer we would like to
provide for Goblins' OCapN code should be properly end-to-end
encrypted. The specification for how to do this still needs to be
written and agreed upon by the our team and others in the OCapN group
so it can be used widely across OCapN implementations.&lt;/p&gt;
&lt;p&gt;Because the prelay is &lt;em&gt;not&lt;/em&gt; end-to-end encrypted, prelay providers can
surveil and modify communication on the wire. This is a known concern,
and we are going to use this current implementation as a stepping
stone to derive the correct and fully end-to-end encrypted relay
netlayer we still have planned.&lt;/p&gt;
&lt;h2&gt;Speedup!&lt;/h2&gt;
&lt;p&gt;This release of Goblins has a major speedup: 1.2-2x performance
improvements for all Goblins code! Even more performance improvements
are planned for upcoming releases; this is just the beginning!&lt;/p&gt;
&lt;h2&gt;Your support is appreciated!&lt;/h2&gt;
&lt;p&gt;Excited about Goblins on Hoot, or any of our other projects?
Consider showing your appreciation with a &lt;a href=&quot;/donate&quot;&gt;donation&lt;/a&gt;!  We
recently hit our initial fundraising goal of $80,000 USD and
&lt;a href=&quot;/news/2024-2025-supporter-drive-success.html&quot;&gt;announced a stretch goal&lt;/a&gt;.  For the stretch
goal, we will use Hoot to build a learning environment for
&lt;a href=&quot;/goblins&quot;&gt;Goblins&lt;/a&gt; and advance Hoot’s live hacking abilities in the
process.&lt;/p&gt;
&lt;p&gt;Monthly supporters at the silver, gold, and diamond tiers will have
their names featured in the credits of
&lt;a href=&quot;https://davexunit.itch.io/cirkoban&quot;&gt;Cirkoban&lt;/a&gt;, a small web game built
with Hoot, as a display of our gratitude.&lt;/p&gt;
&lt;p&gt;Thank you to everyone who has supported us so far!&lt;/p&gt;
&lt;h2&gt;Getting the release&lt;/h2&gt;
&lt;p&gt;This release also includes a number of other bugfixes and minor
features. See the
&lt;a href=&quot;https://codeberg.org/spritely/goblins/src/tag/v0.15.0/NEWS&quot;&gt;NEWS&lt;/a&gt;
file for more information!&lt;/p&gt;
&lt;p&gt;As usual, if you're using Guix, you can upgrade to 0.15.0 by using:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;guix pull
guix install guile-goblins
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Otherwise, you can find the tarball on our &lt;a href=&quot;/goblins/&quot;&gt;release page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a little bonus, if you like the release artwork, you can get a
&lt;a href=&quot;https://files.spritely.institute/images/blog/2025-01-24-goblins-0.15.0-release-art-4k.png&quot;&gt;4k resolution version&lt;/a&gt;
to use as a wallpaper if you like! The artwork is made in Blender
and its Grease Pencil tool; you can also download the
&lt;a href=&quot;https://files.spritely.institute/images/blog/2025-01-24-goblins-0.15.0-release-art.blend&quot;&gt;.blend file&lt;/a&gt;!
(Observant readers of our blog may notice that this release art is
a sequel to the
&lt;a href=&quot;https://spritely.institute/news/guile-hoot-0-6-0-released.html&quot;&gt;Hoot 0.6.0 release art&lt;/a&gt;...
in the previous artwork, the characters discussed going on an adventure
together, and here they are!)&lt;/p&gt;
&lt;p&gt;If you're making something with Goblins or want to contribute to
Goblins itself, be sure to join our community at
&lt;a href=&quot;https://community.spritely.institute&quot;&gt;community.spritely.institute&lt;/a&gt;!
Thanks for following along and hope to see you there!&lt;/p&gt;
</content></entry><entry><title>Guile Hoot 0.6.0 released!</title><id>https://spritely.institute/news/guile-hoot-0-6-0-released.html</id><author><name>Dave Thompson</name><email>contact@spritely.institute</email></author><updated>2025-01-22T13:00:00Z</updated><link href="https://spritely.institute/news/guile-hoot-0-6-0-released.html" rel="alternate" /><content type="html">&lt;p&gt;&lt;img src=&quot;https://files.spritely.institute/images/blog/2025-01-22-hoot-0.6.0.png&quot; alt=&quot;Hoot version 0.6.0&quot;&gt;&lt;/img&gt;&lt;/p&gt;
&lt;p&gt;We are excited to announce the release of &lt;a href=&quot;/hoot&quot;&gt;Hoot&lt;/a&gt; 0.6.0!  Hoot
is a Scheme to WebAssembly compiler backend for
&lt;a href=&quot;https://gnu.org/software/guile&quot;&gt;Guile&lt;/a&gt;, as well as a general purpose
WebAssembly toolchain.  In other words, &lt;em&gt;Scheme in the browser!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;But first, a humble ask&lt;/h2&gt;
&lt;p&gt;If you like what Spritely is doing with Hoot or any of our other
projects, please consider showing your appreciation with a
&lt;a href=&quot;/donate&quot;&gt;donation&lt;/a&gt;!  We recently hit our initial fundraising goal of
$80,000 USD and &lt;a href=&quot;/news/2024-2025-supporter-drive-success.html&quot;&gt;announced a stretch
goal&lt;/a&gt;.  For the stretch
goal, we will use Hoot to build a learning environment for
&lt;a href=&quot;/goblins&quot;&gt;Goblins&lt;/a&gt; and advance Hoot’s live hacking abilities in the
process.&lt;/p&gt;
&lt;p&gt;Monthly supporters at the silver, gold, and diamond tiers will have
their names featured in the credits of
&lt;a href=&quot;https://davexunit.itch.io/cirkoban&quot;&gt;Cirkoban&lt;/a&gt;, a small web game built
with Hoot, as a display of our gratitude.&lt;/p&gt;
&lt;p&gt;Okay, back to the show!&lt;/p&gt;
&lt;h2&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Much improved weak hashtable support in &lt;code&gt;(hoot hashtables)&lt;/code&gt;.  Weak
key hashtables have been reimplemented in Scheme rather than using
JavaScript’s &lt;code&gt;WeakMap&lt;/code&gt; and are now
&lt;a href=&quot;https://wingolog.org/archives/2024/08/19/javascript-weakmaps-should-be-iterable&quot;&gt;iterable&lt;/a&gt;,
as they are in Guile.  Weak key hashtables now have company: Weak
value and doubly weak hashtables have been added.  The Guile legacy
hashtable API has likewise been updated to support all forms of weak
hashtables.  As a bonus, we also have weak vectors thanks to a
contribution from Vivianne Langdon.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hoot now implements enough of Guile (or provides sufficient
alternatives for APIs that are not web compatible) to compile nearly
all of the &lt;a href=&quot;/goblins&quot;&gt;Goblins&lt;/a&gt; source code.  Stay tuned for another
blog post about this progress!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;As of December 9th, 2024, WebKit now ships with &lt;a href=&quot;https://webkit.org/blog/16301/webkit-features-in-safari-18-2/#webassembly&quot;&gt;Wasm GC and tail
calls enabled by
default&lt;/a&gt;.
These were the remaining blocker issues for running Hoot programs on
Safari and other WebKit-derived browsers.  Previously, Hoot couldn’t
run on iOS because Safari is the &lt;em&gt;only&lt;/em&gt; browser available on that
platform.  That said, we think &lt;a href=&quot;https://codeberg.org/spritely/hoot/issues/358&quot;&gt;there might be an
issue&lt;/a&gt; with
WebKit’s Wasm implementation.  We’d like to gather more data so
please let us know how things work for you on WebKit!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Try Hoot&lt;/h2&gt;
&lt;p&gt;Can I interest you in some &lt;code&gt;eval&lt;/code&gt;?&lt;/p&gt;
&lt;iframe src=&quot;https://files.spritely.institute/embeds/hoot-0.6.0-repl/index.html&quot; frameborder=&quot;0&quot;&gt;
&lt;/iframe&gt;
&lt;p&gt;Read on for the full release notes.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot expander)&lt;/code&gt; module with a port of Guile’s &lt;code&gt;psyntax&lt;/code&gt;.
This was a major undertaking by Andy Wingo but we didn’t have time
to hook it up to &lt;code&gt;eval&lt;/code&gt; for this release.  Stay tuned!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot finalization)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot primitive-eval)&lt;/code&gt; module for evaluating macro-expanded,
lowered Tree-IL.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot environments)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot modules)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(hoot weak-refs)&lt;/code&gt; module.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;(ice-9 weak-vector)&lt;/code&gt; module.  Thanks to Vivianne Langdon.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reimplemented weak key and added weak value and doubly weak
hashtables to &lt;code&gt;(hoot hashtables)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added weak hashtable support to legacy Guile hashtable API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added initial implementation of applicable records, undocumented for
the moment while we figure out what the public API should be.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File ports now track their line/column.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;syntax-&amp;gt;datum&lt;/code&gt; and &lt;code&gt;datum-&amp;gt;syntax&lt;/code&gt; now work on the Wasm target.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;read-syntax&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;--user-imports&lt;/code&gt; flag to &lt;code&gt;guild compile-wasm&lt;/code&gt; for user code
that defines Wasm function imports.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added support for syntax transformers on the Wasm target.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;vector-binary-search&lt;/code&gt; to &lt;code&gt;(hoot vectors)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added partial, temporary SRFI-14 implementation (character sets).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added partial implementation of &lt;code&gt;inet-pton&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Switched &lt;code&gt;eval&lt;/code&gt; to use modules as representation of environments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added partial implementation of &lt;code&gt;while&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replaced &lt;code&gt;define-inlinable&lt;/code&gt; stub with real implementation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;string-rindex&lt;/code&gt;, &lt;code&gt;string-trim&lt;/code&gt;, &lt;code&gt;string-trim-both&lt;/code&gt;,
&lt;code&gt;string-trim-right&lt;/code&gt;, and &lt;code&gt;string-reverse&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;capture-stack&lt;/code&gt;, &lt;code&gt;stack-height&lt;/code&gt;, and &lt;code&gt;print-backtrace&lt;/code&gt; are now
exported from &lt;code&gt;(hoot error-handling)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;any&lt;/code&gt;, &lt;code&gt;every1&lt;/code&gt;, &lt;code&gt;every&lt;/code&gt;, &lt;code&gt;filter-map&lt;/code&gt;, and &lt;code&gt;find&lt;/code&gt; to temporary SRFI-1 module (eventually
you will just import the one from Guile).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;&amp;amp;external-error&lt;/code&gt; exception type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;const&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;issue-deprecation-warning&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;ceiling-quotient&lt;/code&gt;, &lt;code&gt;ceiling-remainder&lt;/code&gt;, &lt;code&gt;ceiling/&lt;/code&gt;,
&lt;code&gt;euclidean-quotient&lt;/code&gt;, &lt;code&gt;euclidean-remainder&lt;/code&gt;, &lt;code&gt;euclidean/&lt;/code&gt; procedures
to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;iota&lt;/code&gt; to &lt;code&gt;(guile)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;read-string&lt;/code&gt; implementation to &lt;code&gt;(ice-9 rdelim)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trimmed default set of imports to compiler to just &lt;code&gt;(scheme base)&lt;/code&gt;
to reduce compilation time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added target-side &lt;code&gt;procedure-property&lt;/code&gt; scaffolding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added &lt;code&gt;procedure-name&lt;/code&gt; procedure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Partially implement R7RS &lt;code&gt;exit&lt;/code&gt; procedure (it exits but doesn’t deal
with &lt;code&gt;dynamic-wind&lt;/code&gt;s in effect.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;map&lt;/code&gt; and &lt;code&gt;for-each&lt;/code&gt; now support more than two lists, at last.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added simple terminal REPL demo to the Git repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fixed auxilary modules not being added to &lt;code&gt;*all-instances*&lt;/code&gt; table
when running Wasm in Hoot’s VM.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Guile no longer terminates when a Hoot module calls its internal
quit function.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;(hoot ports)&lt;/code&gt; API is now usable at expansion time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;f32.copysign&lt;/code&gt; and &lt;code&gt;f64.copysign&lt;/code&gt; instructions in &lt;code&gt;(wasm vm)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;Fix define-exception-type&lt;/code&gt; when deriving exception types with
fields.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed &lt;code&gt;cond-expand&lt;/code&gt; in &lt;code&gt;define-library&lt;/code&gt; forms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed parsing of &lt;code&gt;br_on_cast&lt;/code&gt; and &lt;code&gt;br_on_cast_fail&lt;/code&gt; instructions in
&lt;code&gt;(wasm parse)&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;string-copy&lt;/code&gt; is now usable at expansion time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improved hashtable size lower bound calculation to avoid some bad
resizing behavior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed legacy Guile hashtable API to return the unspecified value for
&lt;code&gt;hash-set!&lt;/code&gt;, &lt;code&gt;hash-remove!&lt;/code&gt;, and &lt;code&gt;hash-clear!&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed Wasm generated for &lt;code&gt;s64-&amp;gt;f64&lt;/code&gt; primitive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed unbounded exception raising loop when exception handler prompt
is not on the current dynamic stack.  This could occur when an
exception handler is established before spawning a fiber, for
example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fix compilation of &lt;code&gt;%inline-wasm&lt;/code&gt; that returns 0 or 2+ values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;String comparison procedures are now usable at expansion time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed memory leak in library group expansion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed stream I/O implementation on Hoot VM.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Browser compatibility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Firefox 121 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Google Chrome 119 or later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible with Safari/WebKit 18.2 or later.  Finally!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Get Hoot 0.6.0!&lt;/h2&gt;
&lt;p&gt;Hoot is already available in GNU Guix:&lt;/p&gt;
&lt;pre&gt;&lt;code infostring=&quot;&quot;&gt;$ guix pull
$ guix install guile-next guile-hoot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Hoot currently requires a bleeding-edge version of Guile, hence
&lt;code&gt;guile-next&lt;/code&gt; above.)&lt;/p&gt;
&lt;p&gt;Otherwise, Hoot can be built from source via our release tarball.  See
the &lt;a href=&quot;/hoot&quot;&gt;Hoot homepage&lt;/a&gt; for a download link and GPG signature.&lt;/p&gt;
&lt;p&gt;Documentation for Hoot 0.6.0, including build instructions, can be
found
&lt;a href=&quot;https://spritely.institute/files/docs/guile-hoot/0.6.0/index.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Hoot at FOSDEM&lt;/h2&gt;
&lt;p&gt;I (Dave) will be presenting &lt;a href=&quot;https://fosdem.org/2025/schedule/event/fosdem-2025-5140-minimalist-web-application-deployment-with-scheme/&quot;&gt;“Minimalist web application deployment
with
Scheme”&lt;/a&gt;
on Sunday, February 2nd in the &lt;a href=&quot;https://fosdem.org/2025/schedule/track/declarative/&quot;&gt;Declarative and Minimalistic Computing
devroom&lt;/a&gt;.  Hoot
will be a major focus of the presentation.  This talk, like all FOSDEM
talks, will be livestreamed and a recording will be available sometime
after the conference.&lt;/p&gt;
&lt;p&gt;For all the details about Spritely’s presence at FOSDEM, see &lt;a href=&quot;/news/spritely-is-going-to-guix-days-and-fosdem.html&quot;&gt;our
recent blog
post&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Get in touch!&lt;/h2&gt;
&lt;p&gt;For bug reports, pull requests, or just to follow along with
development, check out the &lt;a href=&quot;https://codeberg.org/spritely/hoot&quot;&gt;Hoot project on
Codeberg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you build something cool with Hoot, let us know on our &lt;a href=&quot;https://community.spritely.institute&quot;&gt;community
forum&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks to Christine Lemmer-Webber for the lovely Hoot pixel art, and a
special thanks to the &lt;a href=&quot;https://metamask.io/news/developers/meta-mask-grants-dao-funds-spritely-foundation/&quot;&gt;MetaMask
folks&lt;/a&gt;
for &lt;a href=&quot;https://spritely.institute/news/guile-on-web-assembly-project-underway.html&quot;&gt;funding this
work&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Until next time, happy hooting! 🦉&lt;/p&gt;
</content></entry></feed>