Versions and commit history
Every push is a new commit — how that gives you rollback, conflict detection, and a DAG
You usually never have to think about this. You edit a skill, run
airskills push, and a new version is saved. This page is for when
you want to understand how "a new version is saved" actually works —
and why it matters.
Every push is a commit
When you push a skill, the server does not overwrite whatever was there. It creates a new commit: a permanent snapshot of your skill at that moment, linked back to whatever commit came before it. Old commits stay around and remain accessible.
This means:
- You can roll back a skill to any previous commit (
airskills restore --commit <sha>— not yet in the CLI, but the API supports it). - You can see who changed what and when (
airskills log <skill>). - The content of every commit is permanent — if you know its SHA, you can always get those exact bytes back.
Why it's a DAG, not a line
If two people edit the same skill on different machines and both push,
one push lands first and the other one finds the world has changed
since it last synced. The server returns 409 Conflict.
The CLI then puts the remote version in a temporary folder alongside yours and points your agent at both, so you can read through and decide what to keep — see incorporation. Once you're happy with your local copy you push again, and the commit DAG now has a small branch that joins back together. Nothing is lost, and every version anyone had is still on record.
In practice, most repos stay linear because one person owns a skill at a time. The DAG matters mainly when sharing and collaboration pick up.
When you'd look at the commit history
- You pushed a bad change and want to see what the skill used to say.
- You're reviewing your own skill and want to see what changed since a week ago.
- You're handing a skill over to someone and want them to see the history of thought that shaped it.
The command:
airskills log <skill-name>
Transfer creates a new DAG
A transfer is not an entry in the DAG —
it's a new skill at the new owner with a new skill_id and its
own fresh DAG. The old skill's DAG stays intact (soft-deleted but
not destroyed); the new one starts fresh with content forked at the
moment of transfer.
This is deliberate: transfers mean "you own this now, the history is yours from this point on."
Rename does not create a new DAG
A rename is just a metadata change.
The skill_id is the same, the commit DAG is the same, the history
is uninterrupted. Only the label changes.
Deep dive: content-addressed commits and conflict detection
For curious developers — skip if you just want to use it.
Each commit has an id that is a SHA over its content — a Merkle
hash of the sorted path:sha256 manifest. The algorithm is pinned to
match exactly between cmd/hash.go (CLI) and lib/storage-utils.ts
(server); any drift would produce hash_mismatch errors on push.
Commits are write-once. The skill_commits table has no UPDATE or
DELETE policies, and a BEFORE UPDATE trigger enforces immutability
server-side. A republish of the same content produces the same SHA;
a change of a single byte produces a different one. This is what
lets consumers pin to a specific commit SHA and know their bytes
can't change under them.
The conflict detection protocol:
- CLI push sends
X-Expected-Commit: <last known head sha>. - Server reads the skill's current
head_commit_id. - Match → accept, write a new commit row with the previous as
parent, advance
head_commit_id. - Mismatch → return
409 Conflictwith the current server head. - CLI pulls the server head into a temporary folder; the user's
agent reads both versions and the user decides what to keep, then
pushes again with
--force(which skips the header check).
Archive fetch by commit SHA is supported via
GET /api/v1/skills/:id/archive?commit=<uuid> — this is how
consumers can pin to a specific reviewed version of a skill rather
than always tracking the head.
Was this page helpful?