@projectpac/protocol-skills
v0.1.3
Published
Part of PAC: @projectpac/protocol-skills.
Downloads
73
Readme
@projectpac/protocol-skills
Each node holds instruction documents, advertises some of them, and a peer can ask for one to be applied to something.
A skill arrives as a markdown document with a little front matter, because a skill is something a human writes and edits. The two halves then live apart, and that split is the design: the body becomes a source, so this protocol names it and never holds it, and the front matter stays a declaration the protocol can read without reading the skill. Describing a skill must not mean reading it, the same way describing an extension must not mean running it.
Every call runs in a box both sides verify and neither owns. The caller
stakes its request and whichever of its own private sources the human consented
to; the owner stakes the skill, the model, and the key that pays for it. Each
side gets back only what it is entitled to: the caller an answer, the owner the
fact that a run happened. Neither node's material passes through the other, and
this protocol holds none of it -- every step that touches material is an
apply, and the box program is proposed byte for byte so both sides approve the
same bytes. The program is the box's own canonical manifest, unchanged: it names
which provider may see plaintext and how much output comes back, and renaming a
field of it would mean proposing a program the box does not run.
There is no local mode. A skill run on the owner's own model would mean the caller's input leaving the caller's machine, and the body sitting in protocol memory to be put in a prompt -- so removing it is what lets the body be a source at all.
What a skill declares
---
description: Builds a music-taste profile from someone's listening history.
provider: openrouter
model: anthropic/claude-sonnet-4.5
key: openrouter-key
requiredSources: spotify_songs
---description and provider and requiredSources are advertised; model and
key are not. requiredSources names the caller's sources, not the
owner's: it is what a caller is being asked to disclose, published so it can be
read before anything is disclosed. A call may narrow that list and may never
widen it, and naming a source outside it is refused rather than quietly dropped.
Starting a call takes three answers. A list is exactly those sources. An empty list is none of them, and the call runs on its request text alone. Omitted is what the skill declares and this node holds -- the router's case, since it picks the skill and names no sources.
Nothing here asks a human. Which sources a call may apply is policy's question,
asked once on jc.submit by every protocol that stakes anything, so a node that
wants to be asked says so in one place and a node that does not is not asked
twice -- the same way joint search works. What is refused where it starts is a
skill that reads only sources this node does not hold, because a call over none
of the data comes back saying the data was missing and costs a box run to learn
it.
A skill whose body or key source is missing is declared but not offered: a peer that called it would get a failure this node could have predicted.
| | |
| --------- | ------------------------------------------------------------------------------------------------------- |
| routes | POST /calls, GET /calls, GET /board, GET /skills, POST /skills |
| payloads | call.request, call.failed, call.accepted, call.session |
| schedules | advertise 60s (presence), advance 2s (the box state machine), sweep 30s (terminate what stalled) |
| needs | any source (the body, the key, and what a caller consents to), the jc.* operations, jc.submit gated |
| intents | skill -- picked only for something the board is offering right now |
library.ts is the document grammar and the declaration, calls.ts the durable
records, index.ts the state machine. Driving it by hand:
devtools/README.md.
