SpuntoSpunto/@spunto/build

Formats

The wire contracts in @spunto/build — written by one program, read by another, and changed only through a migration.

Most of @spunto/build is ordinary code. If a function gets better, callers upgrade and nothing else has to happen. Some of it is not code at all. It is a format: bytes that one program writes and a different program reads back, often in a different language, in a different process, minutes or months later.

  • A shell script running inside a docker build prints ::spunto:step:start:feature:node, and a TypeScript tracker in the control plane turns it into a lit-up block in the UI.
  • A setup script writes ~/.mp-status.json inside a container, and a control plane cats it every 1.5 s to decide whether the worker is ready.
  • A worker created last month is called mp-worker-<id>, and today's code has to find it under that name.
  • A JSON file exported from Lite sits on someone's disk until they import it into Cloud.

The writer and the reader never ship together. A container keeps the name it was created with. A build log stored last week keeps the markers it was written with. A project file keeps the kind it was exported with. So changing a value in a format is a migration, not an edit. Each format gets its own page in this tab, with its exact bytes, so that reviewers can tell when a diff is one.

The tests in the package pin these formats to their exact strings, and they are meant to be annoying to change. If you find yourself updating one, you are changing a contract with things that already exist. Plan for both the old bytes and the new ones.

The formats

FormatDefined inWritten byRead by
Build log markers ::spunto:step:start:<id> / end:<id>./stepsThe generated image build script (echo), and agent-side code next to the build (stepMarker)BuildStepTracker on the live log, renderStoredBuildLog on replay
Extension failures [build] EXTENSION FAILED: <id> and /etc/mp-extension-failures./steps, ./namingAn image build script that cannot install an extension (not yet emitted by the package's own buildImageScript, see the page)parseFailedExtensions on the stored build log (Spunto Lite's project page)
Project spec kind: "spunto/project"./specbuildProjectSpec (Export)parseProjectSpec (Import), in either product
Worker setup status ~/.mp-status.json./types, ./namingThe generated setup and start scripts, inside the containerThe control plane, polling the file (or receiving the same SetupStatus from an orchestrating agent)
Names and paths mp-worker-<id>, mp-proj-<slug>:v<n>, /opt/mp-extensions…./namingWhatever creates the container, volume, network, image or fileWhatever looks it up later: Docker clients, teardown, disk accounting, the UI
Skills layout ~/.spunto/skills/<id>/, .links, [skills]./script (buildSkillsScript)The skills script, at setup and before each taskThe same script on its next run, the harness (through symlinks), and humans reading the log
Agent events TASK_EVENT_TYPES./agent-streamparseStreamLine adapters, or a harness speaking jsonl nativelyWhatever stores and renders a task's timeline

What is not a format

The generators in ./script produce scripts, but the script text itself is not a contract. Nobody parses it back. It can be refactored freely, as long as it keeps emitting the formats above. When an image recipe change must reach images that were already built, IMAGE_RECIPE_VERSION is bumped, and both products compare it at spawn and rebuild stale images.

The same applies to ./extensions and ./terminal. They are protocol clients (Open VSX, a VS Code gallery, dtach), not protocols of their own, with one exception: ./terminal builds shell commands whose layout (/tmp/spunto-term) is shared between the commands it builds. That layout is documented on the terminal module page.

Edit on GitHub

On this page