Stable skills, immutable revisions, visibility, latest resolution, and asynchronous generation.
Paths below are on the cassette’s own listener. Through Tapes, replace
/api/skills with /v1/cassettes/skills.
Resource model
Section titled “Resource model”A skill is a stable organization-local identity with a unique normalized slug. It may temporarily contain no revisions. A revision is a complete immutable snapshot with:
- globally unique
id; - per-skill immutable
sequenceNumber; - v1 version string derived from that sequence;
- creator and optional base/source revision UUIDs;
- complete content, hash, provenance, optional change note, and creation time.
Revision content never changes. isPublic and the skill’s optional explicit
latestRevisionId are mutable metadata. Public means visible to members of the
same organization; private means creator-only.
Every foreign relationship uses revision UUID. Version and sequence are display and ordering fields, not identity.
Routes
Section titled “Routes”| Route | Purpose |
|---|---|
POST /api/skills |
Create or resolve the hidden stable identity for a normalized slug |
GET /api/skills |
Paginated list with one effective entry per accessible skill |
GET /api/skills/{skillId} |
Read identity, effective public revision, and viewer-private continuation metadata |
GET /api/skills/{skillId}/skill.md |
Download the effective public revision as SKILL.md |
POST /api/skills/{skillId}/revisions |
Append a complete immutable private revision |
GET /api/skills/{skillId}/revisions |
List public plus viewer-private revision history |
GET /api/skills/{skillId}/revisions/{revisionId} |
Read one exact accessible revision |
PUT /api/skills/{skillId}/revisions/{revisionId}/visibility |
Make a revision public or private without changing content |
PUT /api/skills/{skillId}/latest |
Set or move the explicit latest pointer |
DELETE /api/skills/{skillId}/latest |
Clear the explicit latest pointer |
POST /api/skills/{skillId}/generations |
Durably queue independent asynchronous generation |
GET /api/skills/{skillId}/generations |
List accessible generation history for a skill |
GET /api/skills/{skillId}/generations/{generationId} |
Read persisted progress and artifacts |
GET /api/skills/generations/{generationId} |
Resolve generation directly by public ID |
POST /api/skills/{skillId}/generations/{generationId}/cancellations |
Cooperatively cancel and fence a generation |
Duplicating a revision into another skill is not a separate route: resolve the
target slug with POST /api/skills, read the source revision, then append its
snapshot to the target with sourceRevisionId naming that source. SKILL.md is served only for a skill’s
effective public revision; an exact revision’s content is read as JSON.
There is no mutable draft, autosave/checkpoint, publication-copy, generation resolution, revision deletion, synchronous generation, exact-revision Markdown, dedicated duplicate, or direct content-update route. V1 also exposes no arbitrary revision-tag operations; existing labels are a separate namespace.
Authentication and visibility
Section titled “Authentication and visibility”The tenant-local gateway establishes organization membership and supplies trusted
x-paper-auth-subject. Creator identity is never accepted in request JSON.
Storage applies access before loading content:
revision.isPublic OR revision.creatorSubject == callerSubjectPublic is organization-visible, not internet-wide. Any organization member may append from an accessible revision and may change public/latest metadata in v1. A guessed private revision ID behaves as not found.
Evaluation history follows its exact target revision’s current visibility. Generation history is creator-only until it has a result revision; when that revision is public, bounded candidates/ranking/provenance become organization- visible with it.
Skill identity
Section titled “Skill identity”Create or resolve a stable identity:
{ "slug": "diagnose-flaky-tests"}The slug is normalized and organization-unique. A collision returns the existing skill ID but no inaccessible private content. A zero-revision identity is hidden from ordinary listing.
Effective list and detail reads
Section titled “Effective list and detail reads”Unversioned reads choose:
explicit public latest revision else highest-sequence public revision else no organization-visible defaultGET /api/skills returns one entry per skill, never one per public revision. A
creator also sees private-only skills using their newest private revision. When a
public default exists, it remains the card/detail default even if the viewer has
newer private work; newestPrivateRevision and hasNewerPrivateRevision support
Continue your private revision.
Only equality with stored latestRevisionId sets isLatest: true. The automatic
newest-public fallback does not receive an inferred Latest marker.
Listing keeps the existing bounded keyset/search/filter shape:
| Parameter | Meaning |
|---|---|
limit |
Page size; bounded by the server maximum. |
cursor |
Opaque keyset cursor from nextCursor. A cursor records the sort it was cut under and is rejected with 400 invalid_request when replayed under another sort; cursors issued by replicas that recorded no sort are accepted under either, so paging keeps working across a rolling deployment. |
q |
Search accessible effective name, description, and labels. |
scope |
all, mine, or team under creator/public visibility. |
sort |
downloads or the default effective-update order. |
session_id |
Return accessible skills attributed to a source session. |
Configured external attachment filters remain fail-closed after arming.
Append a revision
Section titled “Append a revision”{ "basedOnRevisionId": "revision-uuid-or-null", "snapshot": { "name": "Diagnose flaky tests", "description": "Use when a test fails intermittently.", "type": "workflow", "tags": ["testing"], "content": "# Diagnose flaky tests\n...", "isAiGenerated": false, "sourceSessionIds": [] }, "changeNote": "Add a reproducibility check", "idempotencyKey": "editor-save-018"}The server assigns revision UUID, sequence, version, creator, timestamps, and
private visibility. The snapshot carries no slug: slug is stable skill identity
metadata resolved once by POST /api/skills. basedOnRevisionId may name any
accessible revision in the same skill. Concurrent saves from one base all succeed with distinct sequences.
The original remains unchanged.
sourceRevisionId is used instead of basedOnRevisionId for a cross-skill
duplicate. The server does not copy anything: the client supplies the complete
snapshot exactly as for any append (typically the snapshot it just read from
the source revision), sourceRevisionId must name a revision of another skill
that is accessible to the caller, and the new private revision records that
source as lineage with origin duplicate. Repeating an idempotency key returns
the retained append.
Visibility
Section titled “Visibility”{ "isPublic": true }PUT /visibility updates only visibility/audit metadata. Repeating the requested
state is successful. Making the explicitly latest revision private returns a
conflict until latest is moved or cleared.
Normal user APIs do not delete revisions. Making a revision private hides it from other organization members and hides its linked evaluation/generation history.
Explicit latest
Section titled “Explicit latest”Set or move:
{ "revisionId": "revision-uuid" }The target must be a public revision of the same skill. The pointer remains
pinned when newer revisions become public. DELETE /latest clears the override
and restores automatic newest-public resolution. Setting or clearing does not
change content or visibility.
Asynchronous generation
Section titled “Asynchronous generation”{ "baseRevisionId": "revision-uuid-or-null", "input": { "name": "Diagnose flaky tests", "description": "Turn session evidence into a reusable workflow.", "type": "workflow", "tags": ["testing"] }, "authorContext": "Prefer reproducible evidence.", "selectedSessionIds": ["session-a", "session-b"]}A successful create returns 202 only after inputs and the immutable
skills-cassette-owned ranking rubric are durable. Multiple generations for one
skill/base may run concurrently. Statuses are:
queued -> generating_candidates -> evaluating_candidates -> synthesizing -> completed | failed | canceledThere is no awaiting_input or resolution. Each selected session produces one
independent candidate inference; context-only generation produces one candidate
without a transcript. Partial source failures retain bounded diagnostics while
healthy candidates continue.
Every viable candidate and optional synthesis is judged under the same profile, version, weighted criteria, author context, and explicit evidence ordering by skills-evaluator. Skills-cassette deterministically ranks persisted results.
Successful completion appends exactly one new private revision and returns its
resultRevisionId. It never makes that revision public or latest. Cancellation
retains artifacts and fences late worker writes.
Generation admission
Section titled “Generation admission”A deployment may close generation admission with
CASSETTE_GENERATION_ENABLED=false. Creating a generation then returns 403
with code generation_disabled, decided before anything is written, so nothing
is queued and then refused. Nothing else changes: listing and reading skills,
revisions and generation history, resolving a skill identity, appending a
revision, visibility and latest all behave exactly as before.
Work already in the durable queue is finished, not dropped — including a row whose lease expired because its worker died. Only a new attempt is new admission: a row whose attempt fails while admission is closed is not requeued and goes terminal instead.
Evaluation contracts
Section titled “Evaluation contracts”Two evaluator contracts cross the boundary with skills-evaluator, in opposite directions.
Stateless candidate judgment is a call from this cassette to
/v1/cassettes/skills-evaluator/candidate-evaluations. The request carries the
complete rendered candidate (and optional baseline), the immutable
profile/version/criteria snapshot stored on the generation, author context, and
explicit evidence session order; the creator subject travels only in the
trusted header. The evaluator echoes the request ref verbatim. Its ref
record also carries revision and revision_sha256, which candidate work never
sets; the adapter accepts them as bounded opaque strings and still requires
source and id to match. Nothing about a candidate evaluation is persisted by
skills-evaluator.
Durable user-requested evaluation is a call to skills-evaluator naming
skill_id and an exact skill_revision_id. Skills-evaluator resolves that
revision through GET /api/skills/{skillId}/revisions/{revisionId} (via the
Tapes prefix) with the forwarded viewer subject, judges that immutable snapshot
whether private or public, and persists the revision UUID on the run. It never
re-resolves latest: an explicit candidate whose content differs from the named
revision is a conflict. Evaluation-run reads re-check the live revision the same
way, so runs are creator-only while the revision is private and
organization-visible while it is public.
Both contracts are verified against the real HTTP applications by
make test-skills-evaluator-contract.
Errors
Section titled “Errors”Lifecycle failures use a bounded stable envelope:
{ "error": { "code": "revision_not_public", "message": "Only a public revision can be marked latest.", "resourceId": "revision-uuid" }}Codes include revision_not_found, revision_private, revision_not_public,
latest_revision_conflict, revision_sequence_conflict,
invalid_generation_state, and generation_disabled. Inaccessible private content uses not-found
semantics. Responses never include raw transcripts, provider/model errors,
creator subjects, claim tokens, or lease state.
Composed Console workflow
Section titled “Composed Console workflow”Console may present Save new revision, Make public, and Make latest together, but calls append → visibility → latest as separate operations. Make latest selects and locks Make public. If a later operation fails, earlier success remains committed and the client retries only the failed step.