REUSE
Skills: work you write once¶
A Skill is a piece of work somebody already wrote, published under a name, so a plan can call it instead of spelling the steps out again.
If you have written two plans that both read a Slack channel, you have written
those steps twice. Change your mind about them and you have to remember both.
A skill is the fix: publish slack.read once, and every plan names it.
The one idea to hold onto
A published version is never rewritten. Publishing the same name again
creates the next version. A plan that names slack.read@1 runs exactly what
it ran the day you wrote it, whatever anybody publishes later.
What a skill is made of¶
Two things: the steps, and the ports.
The steps are ordinary plan steps. The ports are the small contract around them: what the skill takes in, and what it hands back:
{
"name": "slack.read",
"display_name": "Read recent messages in a Slack channel",
"description": "Reads a channel's recent history. Read-only: it can never post.",
"visibility": "private",
"body": {
"ports": {
"inputs": {
"type": "object",
"required": ["gate", "channel"],
"properties": {
"gate": { "type": "string", "description": "your api gate, from `ekka gate list`" },
"channel": { "type": "string", "description": "the channel id, like C0123ABCD" }
}
},
"input_map": {
"gate": "call.target",
"channel": "call.inputs.channel"
},
"output_map": {
"messages": "call.output.rows"
}
},
"steps": [ { "id": "call", "action_ref": "ekka.gate.api.v1", "…": "…" } ]
}
}
input_map says where each port lands inside the steps. output_map says which
step output the caller receives, and under what name. Between them, a caller
never has to know what is inside.
@self., not @op.
Inside a skill body, one step reads an earlier one with
@self.<step>.output.<field>. A skill cannot name the operation that will
call it, because it does not know who that will be. If you are editing a
body and see @self., leave it alone.
Calling one from a plan¶
A plan step names the skill and fills its ports:
{
"id": "history",
"skill": "slack.read@1",
"display_name": "Read recent messages",
"inputs": {
"gate": "enclave8e1d5a6aApi",
"channel": "@input.channel"
}
}
That is the whole step. No action ref, no contracts, no request shape. The skill carries all of it.
Name the version (slack.read@1) when you want the plan pinned, which is almost
always. Without @1 you get the newest at the moment the plan is created, and
plans are frozen when they are created, so it will not drift afterwards either.
Four ways to make one¶
1 · Promote a plan you already wrote¶
The common one. You have a plan that works; make it reusable:
It copies the steps exactly as you wrote them, and turns every @input.<name>
into a port. If your plan has more than one operation, name the one you mean
with --op <id>.
It writes a file. Nothing is published until you say so.
Check the gate before you publish
A plan names a concrete gate, like enclave8e1d5a6aApi, because it runs on
one machine. A skill is published for whoever has one. promote tells you
when it copied a pinned gate, and the edit is small: set target to
"placeholder" and add "gate": "<step>.target" to input_map.
2 · Generate one from a connected API¶
If the work is one call to an API you have cataloged:
The generated step names the operation rather than spelling out its URL, so the address stays where it is maintained (on the catalog row) and cannot go stale in your skill.
3 · Copy somebody else's¶
Change the name in the file before publishing, or you will be adding a version
to the skill you copied from.
4 · Write the file by hand¶
Everything above produces the same kind of file. You can also just write one.
Whichever route you took, check it and publish it:
ekka skill test slack.read.skill.json # every check publish makes, creates nothing
ekka skill publish slack.read.skill.json # EKKA assigns the version
Who can see it, and who can read it¶
Two separate settings, and they answer two different questions.
visibility: who can FIND it.
private |
your organization only. The default. |
public |
anyone on EKKA can find it. |
openness: who can READ the steps inside.
open |
the body is served, and ekka skill fork will copy it. |
source_available |
the body is served so you can read it, and fork refuses. |
closed |
the steps are never served. The ports, the permissions it needs, and its digest stay public. |
A closed skill still runs perfectly well: the Enclave receives the body it
needs to execute. What closed withholds is the body from a reader.
What the label always tells you
Openness is always public, whatever it is set to. You can always see that a
skill is closed before you decide to depend on it, along with its ports and
the permissions it asks for. You are never asked to call something whose
requirements are hidden.
Reading what is there¶
ekka skill list # every skill this organization can use
ekka skill list --all # including archived ones
ekka skill show slack.read # its ports, its size, its versions
ekka skill show slack.read@1 # an older version
show tells you what it takes, what it hands back, how many steps it has, and
the digest of the body, enough to decide whether to depend on it without
reading the steps.
Withdrawing one¶
A published version is never rewritten, so fixing a skill means publishing the next version. That leaves the old one still sitting in the list, still offered. Archiving is how you stop offering it.
✓ Archived file.summarize@1
It is out of `ekka skill list` and the next plan naming it will be refused.
Nothing that already runs stops. That is worth being precise about, because "archive" in other tools often means the thing breaks. When a plan is created, the skill's steps are copied into it. The plan does not look the skill up again at run time, so a schedule firing tonight runs exactly what it ran last night.
What archiving changes is the future: the skill leaves ekka skill list, and
the next plan that names it is refused when you try to create it.
Nothing is deleted, and you can undo it:
A version is always required. file.summarize@1 and file.summarize@2 are
different bodies and a plan pins one of them, so the name on its own does not
say which to withdraw.
You can only archive skills your own organization published. A skill someone else made public stays theirs.
Permissions do not change¶
A skill is a body of work, not an authority. When a plan calls one, the steps run as the agent that owns the plan, and every gate call inside still needs a Grant.
Publishing a skill that calls Slack does not give anybody Slack. They still need
the grant, on their own connection, in their own organization. skill from-api
prints which grant its output will need, for exactly this reason.
What's next¶
- Multi-step plans and schedules: where a skill call fits in a larger plan
- Core concepts: agents, plans, gates, grants and receipts
- CLI reference: every
ekka skillcommand