Pack registry¶
The registry is a static website. A folder of packs (packs/<owner>/<name>/versions/<n>/)
is turned into plain files that any web host can serve, and symbola pull reads
them. There is no server. Only the latest version of each pack is published;
older versions stay in the registry's folders and git history so that attestations
naming them can still be re-checked.
Making a pack¶
pack init writes a manifest, a BRIEF.md with the three required headings, an
obligations.toml with one open placeholder, a placeholder assumption, an empty
components/ and an AGENTS.md. It passes pack validate as it stands. The
generated AGENTS.md also tells the publisher's own coding agent to keep the pack
current while it builds: record each choice the user makes that a builder could
not have guessed as an obligation with a check, and each fact about the world as an
assumption.
Holdout tests must stay outside the pack folder. site build refuses a pack that
contains a holdout path.
Keeping a pack while you build¶
The repository ships a Claude Code skill, skills/symbolon-pack/SKILL.md.
Copy it into your project's .claude/skills/ (or point another coding tool at it
from your AGENTS.md). It tells the agent to update spec/ in the same change as
the code whenever you make a decision a builder could not have guessed, state a
fact about the world, or leave something open, and to run pack validate after
each change.
Publishing from symbola.sh¶
Sign in at https://symbola.sh/publish/ with GitHub, or from a terminal:
symbola login # GitHub device sign-in; saves a token in ~/.config/symbolon/
symbola publish spec # validates locally, uploads, and follows the request
Your GitHub login is your namespace, so you publish <login>/<name>; organisations
that installed the Symbola GitHub app (or where your membership is public) are
namespaces too. The registry's publish workflow runs release and site check on
the upload and opens a pull request as the Symbola bot. New versions of a pack
merge when CI passes; a namespace's first pack waits once for a moderator. In CI,
put a token in SYMBOLON_PUBLISH_TOKEN instead of running login.
The account page (https://symbola.sh/account/) lists your packs and requests,
edits your publisher profile and support links, revokes CLI tokens, and asks for
a pack to be removed. symbola logout forgets the local token.
Publishing by pull request, below, still works and needs no account.
Releasing¶
symbola release spec --registry ../symbola-registry/packs # in a clone of kausalflow/symbola-registry
release validates the pack, sets its manifest version to the next free number,
copies it to packs/<owner>/<name>/versions/<n>/, and appends the version's
digest to packs/<owner>/<name>/digests.txt. Releasing unchanged content does
nothing. Then commit and open a pull request.
There are no publisher keys. Who may release under an owner is decided by who
can merge into the registry; once packs move to their publishers' own
repositories as GitHub Releases, GitHub's provenance attestation will identify
the publisher and pull will check it (distribution design v0.5, section 5.2).
Ed25519 keys are only for runners that sign attestations.
Submission checks¶
CI runs this on every pull request. It builds the whole registry (so every latest
version must pass pack validate, match its manifest id, and hold no holdout
files) and checks that every version listed in a pack's digests.txt still has
that digest, so a released version cannot be edited in place. Packs without a
digests.txt get a warning.
Building the site¶
For each pack, it validates the latest version (the build fails if any pack is invalid) and writes:
index.json every pack: id, version, title, license, digest, tarball sha256, summary, files
llms.txt one line per pack, for agents, and where to find build steps
packs/<owner>/<name>/
latest.tar.gz the pack folder, byte-identical on every build
latest.sha256 sha256 of the tarball
pack.md brief, assumptions, obligations, manifest, and a list of the parts
parts/<component>-v<n>.md one component's contract, schemas and tests
parts/<component>-v<n>.tar.gz that component's contract folder, for `symbola attest`
llms.txt build steps for a coding agent, then the pack's files
files/… every file of the pack, raw
evidence.json every attestation beside the pack, checked at build time
attestations/… the attestation files and runner keys, to re-check
by-digest/<digest>.tar.gz each older version that some attestation names
index.json also carries each pack's evidence summary (components with a passing
build, distinct builds, distinct runners, invalid attestations) and its older
attested versions.
Static hosts cap the number of files per site (Cloudflare Pages: 20,000 on the
free plan), and the list above grows with every file of every pack. With
--files-out DIR --files-url URL, only index.json, llms.txt and each pack's
pack.md and llms.txt stay in --out; everything else is written to DIR, to
upload to a bucket served at URL. Links in pack.md, the parts and llms.txt
then point straight at URL, index.json names it as files_url, and
symbola pull downloads from there. The site can redirect the old paths to the
bucket so older CLIs keep working.
Evidence¶
Attestations sit beside the pack in packs/<owner>/<name>/attestations/, with
the runners' public keys as *.pub. For each one, site build checks the
signature, checks that the runner's key is published there, and finds the pack
version whose component has the attested contract digest. The pack page shows
an Evidence table: build, result, which version it covers, runner, and a link to
the file. An attestation that fails a check is shown as invalid, never dropped.
The catalog sorts packs by their evidence.
The pack digest is the contract framing (CONTRACT_FORMAT.md)
over the pack folder with the header symbolon-pack-v1\n.
The site¶
The packs live in kausalflow/symbola-registry.
The website is built from them by spikinglabs/symbolon-store and hosted on
Cloudflare Pages at https://symbola.sh/. It is an Astro
site over the files above, with Pagefind search built at the
same time, so it needs no server. It adds a catalog page with search, a page per
pack (brief, assumptions, obligations by status, evidence, publisher, download
links, file tree), and a rendered view of every file under
packs/<owner>/<name>/view/<file>/.
clones the registry, runs symbola site build over its packs, then astro build
and pagefind, and copies the result to OUT. Cloudflare runs it on every push to
the site's repository, and the registry's CI triggers it after every merge to its
main. Both repositories pin the CLI they use in .symbolon-version, so a CLI
change reaches the registry and the site when those pins move.
Pulling a pack¶
pull also takes the pack's page link (symbola pull https://symbola.sh/packs/acme/invoice-desk/),
and then pulls from the registry that serves the page. It prints the commands to start
building. pull takes no version: the registry serves only the latest. The one exception is
re-checking evidence: symbola pull <id> --digest <pack digest> downloads an
older version that some attestation names (the pack page links its digest). It downloads the
tarball, checks its sha256 against latest.sha256 and index.json, unpacks it,
and checks the unpacked folder's digest against index.json. These checks catch a
broken download, not a dishonest host: the publisher is not verified until packs
carry GitHub provenance. The registry defaults to $SYMBOLON_REGISTRY, or https://symbola.sh.
An agent that only needs to read a pack can fetch packs/<owner>/<name>/pack.md
instead and skip unpacking. It holds everything that applies to the whole pack and
links one parts/ file per component, so the agent reads only the components it
builds and no file is too big for one read. Data files such as examples.jsonl
show their first lines in the part and link the raw file under files/.
Publisher and support links¶
Every pack page says who publishes it and links their profile, the owner's GitHub page unless the manifest says otherwise. Publishers who want to be paid for their work can add up to five support links (Buy Me a Coffee, GitHub Sponsors, Ko-fi, and so on), which the page shows as buttons:
[publisher]
name = "Acme Labs" # defaults to the owner
url = "https://acme.example" # defaults to https://github.com/<owner>
support = [
{ label = "Buy me a coffee", url = "https://buymeacoffee.com/acme" },
{ label = "GitHub Sponsors", url = "https://github.com/sponsors/acme" },
]
Links must be https://. Money goes straight to the publisher; the registry
takes nothing and handles no payments.
A publisher profile edited on the account page is saved as publishers/<owner>.toml
in the registry and wins over [publisher] in the manifest, which is frozen with
each released version. It takes the same fields plus a short bio.
Selling a pack¶
Not switched on. For now every pack in the registry is public and free, and publishers monetize through support links. The code below stays in place for a later stage.
Add a price to the pack manifest:
A priced pack previews only its brief: the site shows the brief, assumptions,
obligations, manifest, evidence and sha256, and pack.md names the components
without including them. Add preview = "full" under [listing] to publish
everything anyway. Its tarball is written to --private-out instead of the
public site and is served by the store, a Cloudflare Worker in
spikinglabs/symbolon-store (private). The pack page
shows a Buy button; after paying through Stripe, the buyer gets a license token:
site build refuses a priced pack unless it is given --private-out and
--store-url. Deployment steps are in that repository's README.