A skill is a program, not a markdown file: <leaf>.cue is a typed model with its own logic and validation, and SKILL.md is rendered from it.
The model carries what prose cannot: typed routing and pipeline edges that must agree across skills, entry roles with their registers, a permission manifest, interface bindings for every program the skill calls, a smoke declaration, title states, the changelog. Every load validates all of it, so a wrong role, a route to nowhere, an undeclared tool or a version without its changelog row does not render. The file sits in the leaf dir, named after it (skills/quality/railroad-review/railroad-review.cue); payloads/<entry-id>.md holds template bodies. SKILL.md renders from both at deploy, never committed; only repo-scoped values wait for invocation (Interfaces and environment). Layout: Skills directory.
A SKILL.md, title.json, enforce.json, smoke.json, answer.md or env.json in a skill dir fails make skills-check.
| Task | Do |
|---|---|
| Change a skill | Edit <leaf>.cue or a payload as text; make skill-render SKILL=<name>. |
| Bump a version | Prepend a changelog row; set frontmatter version to match. |
| Inspect the model | make skill-export SKILL=<name> (JSON). |
| List the atoms | make skill-atoms SKILL=<name> (skillmodel atoms [--json] <dir>): per atom kind, source, readers by entry and role, query names. |
| Adopt a third-party skill | make skill-import DIR=<dir> once; Routing and Model prose to typed blocks. |
| Check everything | make skills-check, part of make audit. |
skill-render loads, validates and checks the model; re-saves <leaf>.cue canonically (CUE formatter, fixed field order); writes payloads, deletes orphans; stores the deployed body's token count on the newest changelog row. A failed check writes nothing. Root passes follow per-dir saves.
| skills-check scope | Fails on |
|---|---|
| Each skill dir | Non-canonical <leaf>.cue; invalid model; orphaned payload; leftover skill.cue (former model file name; the line names the renaming git mv), atoms.json, changelog.json or generated file; SKILL.md without <leaf>.cue. |
| Across the root | The Across skills layer. |
Top-level fields of <leaf>.cue. The schema (internal/skillgraph/schema.cue) refuses a model missing a required one.
| Field | Required | Meaning |
|---|---|---|
| changelog | yes | [{version, date, text | changes, tokens?}], newest first, versions strictly descending (checked); skillmodel renumber repairs, skillcue merge driver merges two branches' bumps. |
| frontmatter | yes | [{key, value}], ordered; value: raw text after key:. format ratchet: 2 caps, role order; 3 typed fields, link tokens, model-checked links and titles. |
| preamble | no | H1 and intro, verbatim markdown. |
| sections | no | [{title, kind?, pred?, replaces? | between?, blocks}]; blocks: prose, heading, entry, args, routing, model. |
| note | no | Model note. |
| tier | no | "annotations" | "strict"; strict: typed atoms, z3 battery. |
| atoms | no | {<id>: {sort, kind?, members?, source?, aliases?}}, skill-local (Formal clauses). |
| exclusive | no | [[atom ids]], mutually exclusive. |
| enforce | no | {anchors, checks, gate, judgment}: runtime enforcement → enforce.json. |
| requires | no | [<component id>] the skill cannot run without. |
| env | no | {<KEY>: {interface, required?}}: called capabilities, one catalog interface each. |
| smoke | yes, by skills-check | {skip} | {invoke, expect, env?, fixture?, mode?, model?, effort?, limits?} (Smoke block). |
| title | no | {states: {<TOKEN>: {meaning, entry}}}: session-title states. |
| docker | no | "always" | "never": where it runs; absent: agnostic. |
| enableDelegation | no | "always": spawned only, never root; "never": root only. |
| enforcePermissions | no | [acceptEdits | planMode] or ["bypass"]: owned modes. |
| answeredBy | no | "hook": the skillhelp hook answers (Deploy). |
| Shared file | Holds |
|---|---|
| queries/*.json | {name?, assume, check? | reach?, bound?, expect}: strict tier's test suite; a query read keeps an atom alive. |
| skills/vocabulary.cue | Shared atoms, exclusive groups. |
| skills/interfaces.cue | Interface catalog, providers. |
From skills/smart/qq/qq.cue:
changelog: [{
date: "2026-10-02"
version: "1.0.4"
text: "Dropped the help-only Eval section — --help shows the smoke and eval declared beside the skill."
tokens: 809
}, {
date: "2026-09-28"
version: "1.0.3"
text: "Routing and Model are typed blocks in skill.cue; the Pipeline line renders from declared edges"
tokens: 809
}]
frontmatter: [{
key: "name"
value: " qq"
}, {
key: "version"
value: " 1.0.4"
}, {
key: "format"
value: " 2"
}, {
key: "allowed-tools"
value: " Glob, Grep, Read"
}]
Section kind: context, phase or route. A route section carries pred (when it applies) and exactly one anchor: replaces: <ids> (entries it substitutes) or between: <after>..<before> (splice point).
| Entry field | Required | Meaning |
|---|---|---|
| id | yes | SKILL-<NAME>-<TOPIC>-NNN[a-z]? |
| role | yes | def, rule, quality, step, spawn, substep, verify, stop or payload. |
| title | yes | 1–3 words. |
| statement | no | Prose, one line. |
| channel, custom | no | Enforcement channel (gate, hook, prose, schema, script); custom tags (hook:read-gate). |
| fields | no | Format 3 only: {script?, schema?, ref?, flags?, forwards?, reads?, returns?, mints?, timeout?} (Fields). |
| items | no | Bullets, fences under the statement. |
| needs, unless | no | Component ids: entry drops when the render's set misses one / holds one. |
| pred, post | no | Formal clauses. |
| parentSpawn, payload, note | no | Substep's spawn; payload entry's fence, body in payloads/<entry-id>.md; model note. |
Flow entry with clauses, skills/verdict/investigate/investigate.cue:
sections: [{
title: "2. Run"
blocks: [{
entry: {
id: "SKILL-INVESTIGATE-RUN-004b"
post: ["claims_ruled"]
pred: "claims_deduped"
role: "substep"
statement: "RULE each claim `holds | refuted | needs-measurement` in Stage B, watching full funnel."
title: "Stage B Rulings"
}
}]
}]
Route section, skills/feature/fdesign/fdesign.cue, blocks elided:
{
title: "Nightly route"
blocks: […]
kind: "route"
pred: "mode = nightly"
replaces: "GATE-001 + CODE-005 + CODE-009 + HANDOFF-001"
}
args block {rows: [{name, form?, default?, effect}]} → the ## Args table:
args: {
rows: [{
name: "topic"
default: "default slice"
effect: "context slice(s) injected; `--no-context` skips all — [Topic use](#skill-qq-context-001)"
form: "`topic:<name>`, repeatable"
}]
}
| Block | Sits in | Renders |
|---|---|---|
| routing | First block of Routing section. | Purpose, Disambiguation table, Preconditions, Pipeline line. Fields {purpose, routes: [{for, use, needs?}], preconditions: [...], pipeline}, all but needs required. |
| model | Only block of Model section. | - Suggested: … - Tested unviable: bullets. Fields {tier, effort, reason, unviable?, routes?, notes?}; tier small, mid-tier, frontier; effort low … max. |
routing: {
pipeline: {
standalone: true
}
preconditions: ["one question in the invocation"]
purpose: "answer a single question fast and cheaply."
routes: [{
for: "a subject to talk through"
use: "/discuss"
}, {
for: "an answer that needs sources and citations"
use: "/research"
}]
}
model: {
effort: "low"
reason: "single lookup, short answer"
tier: "mid-tier"
}
Session-title states of the skill's approval gates. Deploy renders them to title.json beside SKILL.md, not into it.
| Rule | Detail |
|---|---|
| Gate entry | Each state names the step or stop presenting its gate. Its statement (below format 3 also a param bullet) runs $SKILL_<SKILL>_TITLE_SET title set <TOKEN> via an env variable bound to title-set@1; model-checked, literal program refused. |
| Resume | A step resuming after the gate runs … title set WIP. |
| Proof | Deployed copy carries the filled program. The title hook accepts any program word before title set <TOKEN>, takes the printed title: set <TOKEN> line as proof, infers nothing from other tool calls. |
| Rename | At the turn's first confirmed set call; later sets: once at turn end, with the last. |
| No value | No title-set@1 value: no title.json, no states. |
title: {
states: {
"CODE-READY": {
entry: "SKILL-FDESIGN-CODE-005"
meaning: "code gate presented — approve the Changes or request a change"
}
"DESIGN-READY": {
entry: "SKILL-FDESIGN-GATE-001"
meaning: "design gate presented — approve or revise the design"
}
}
}
The skill's smoke test, never rendered into any file or deployed. The smoke runner loads the model and resolves every default in memory.
| Field | Required | Meaning |
|---|---|---|
| skip | for a skip | {skip: "<reason>"} replaces the run. |
| invoke | yes | Text after /<leaf>. |
| expect | yes | {anchors: [...], minAnchors?, minChecks?} naming the skill's own enforce anchors, or {check?: [argv]}. |
| env | no | Repo-scoped values the run sets, every required one. |
| fixture | no | {tree?, patch?} inside the skill's smoke/. |
| mode | no | nightly (default); interactive needs anchors. |
| model, effort | no | Defaults sonnet, low. |
| limits | no | {minutes?, usd?}, defaults 15 and 3. |
skills/feature/fimplement/fimplement.cue, check list shortened; fdesign expects {anchors: [...], minChecks: 0.4} instead:
smoke: {
env: {
CHECK: "go test ./internal/smoke/..."
}
expect: {
check: ["go", "test", "internal/smoke/greeting.go", "internal/smoke/greeting_test.go", …]
}
fixture: {
tree: "tree"
}
invoke: "fimplement-smoke.md"
limits: {
minutes: 45
usd: 8.0
}
}
pipeline: {standalone?, edges?: [{kind, skill, route?, optional?, needs?}], note?} places a skill among the others. standalone: also runs on demand outside the chain. route: a route of the edge's skill (/fdesign master).
| Kind | Meaning | Other end declares |
|---|---|---|
| from / to | runs after / hands off to | to / from |
| spawnedBy / spawns | spawned as a subagent by / spawns | spawns / spawnedBy |
| escalates | escalates to | nothing |
Pipeline line: standalone; <from> → **<name>** → <to>; spawned by …; spawns …; escalates to … — <note>. skillmodel render checks every edge across the skills root: skill exists, other end agrees.
pipeline: {
edges: [{
kind: "from"
skill: "fdesign"
}, {
kind: "from"
skill: "overlap"
optional: true
}, {
kind: "to"
skill: "package-commit"
}, {
kind: "spawnedBy"
skill: "fleet"
}, {
kind: "escalates"
skill: "restack"
needs: ["agentwt"]
}]
}
Five layers: shape in the schema, cross-references in the check, agreement between skills in the root render.
| Layer | Enforces |
|---|---|
Schema, internal/skillgraph/schema.cue | Shape only: id and atom-id grammar, closed role and channel sets, 1–3 word titles, single-line fields. No pred on def, payload; post only on flow roles (step, substep, spawn, verify, stop); a payload only on payload entries, body in its file; pred/replaces/between only on route sections. Enum atoms need members. |
Check, internal/skillgraph/check.go | Cross-references, next table. |
Across skills, skillmodel render (part of make skills-check) | Pipeline edges agree; each Skill(<name>) grant names a skill an edge reaches; each atom has its reader (Formal clauses); each cross-skill link token names an entry of that skill; every skill declares a smoke block. The smineless bundle render (skills.CheckBundle) holds no smine program, unfilled $SKILL_ reference or make <target> of this repo's Makefile. |
| Load | No comments (notes are data: note). Frontmatter name equals dir name. Payload files, smoke fixtures present; skills/interfaces.cue valid; queries/*.json parse; smine literals (Checks). Format 3: fields files and schema properties exist (Fields); no model text links an entry in markdown. |
| Save | Encoded bytes decode back to an equal model before any write. |
| Check area | Rule |
|---|---|
| Frontmatter | name, version present, non-empty; no key authored twice. Aliases strict kebab-case, not the skill's name, unrepeated. version equals changelog[0].version. |
| Entries | Ids unique, naming this skill. Role order per section: def, rule, quality, flow, payload. Route anchors resolve, run forward. No skill atom shadows a shared one. |
| Routing and model | At most one each, in their sections. Pipeline non-empty; no self edge, no edge twice. |
| Runtime placement | bypass alone, only with docker: "always"; no mode twice; Script: pointer only with enableDelegation: "never"; rendered keys docker, enable-delegation, enforce-permissions never authored. |
| Enforce | At most 16 anchors, each referenced; checks and judgments name known anchors; a provenance from anchor captures the named capture; patterns compile. |
| Title | 1–8 states. Tokens match ^[A-Z]+(-[A-Z]+)*$, at most 16 runes, no built-in state. Entries known steps or stops (Title block). Signal regexes compile; tool matches Bash, Edit, Workflow or Write. |
| Requirements | Catalog component ids on requires, entry needs/unless, route and edge needs; needs disjoint from requires everywhere, from unless on an entry. |
| Environment | References, bindings (Checks). |
| Format-2 caps | Statements ≤200 runes, bullets ≤160 (a link token counts as its target's title), no Why: bullets, verbatim prose ≤ line width. |
| Format 3 | No labelled item; fields only on roles taking them, in shape (Fields). Link tokens resolve under the ref discipline: any entry may link a def or payload; a def or payload links nothing; flow links stay in their section; a route may link its anchor entries. Titles unique, case-insensitive. Pred and post identifiers are atoms or enum members. |
| Every skill | Atom sources in the closed grammar (Formal clauses). Each anchor capture read by a provenance check, a judgment's evidence or an atom's runtime source. |
| Smoke | Skip or run; anchors, mode, fixture per Smoke block. |
pred: boolean expression over atoms (risk_map and dispatch_prepared, mode = nightly). post: atoms the entry makes true when it fires. internal/skillmodel.Derive turns them into the semantic model without judgment.
| Entry role | Becomes |
|---|---|
| rule with pred | invariant |
| quality with pred | soft constraint |
| flow role | transition, guarded by pred (conjoined with its route's pred), firing post |
An entry without clauses is uncovered, not an error. make model-analyze SKILL=<name> runs the solver on the derived model. Clauses must always parse; on the strict tier every atom must also resolve against the skill's atoms or skills/vocabulary.cue (ACDSL-SKILL-013).
An atom's source marks an input the world sets: | -separated parts, each one of these.
| Part | Meaning |
|---|---|
| arg: <row>[ => <member>] | Args row, leading dashes stripped; => <member> forces an enum member. |
| session | Fact of the invocation's session. |
| environment | Fact of the machine or repo. |
| runtime: <anchor> | Anchored call succeeded (bool atoms). |
| runtime: <anchor>.<capture> | Bool atom: capture resolved. Int or enum atom: its value. |
| runtime: <anchor>.<capture> = <literal> / != <literal> | Bool atom comparing the capture to a JSON literal. |
A runtime part names an anchor and capture of the skill's own enforce block, so heartbeat's check and the model read one value. Shared atoms (skills/vocabulary.cue) take none; anything else fails the load. Format-3 and shared atoms live only while a stop, rule or quality pred, a route section pred or a query reads them; a step's pred keeps nothing alive. Format-2 locals live with any pred.
skills/quality/railroad-review/railroad-review.cue, strict tier; fdesign's enum atom role reads Args via source: "arg: master => master | arg: derived => derived".
atoms: {
base_build_red: {
sort: "state"
source: "runtime: base_health.base_build != ok"
}
plan_mode: {
sort: "state"
source: "session"
}
recommendation: {
kind: "enum"
members: ["APPROVE", "CONDITIONAL", "REJECT"]
sort: "state"
source: "runtime: review_json.recommendation"
}
void_rounds: {
kind: "int"
sort: "count"
source: "runtime: ledger.void_rounds"
}
}
A format-3 entry carries no labelled bullet: its statement is prose plus at most one inline command; its mechanics are the fields record, rendered after it as * <key>: <value> bullets in this order.
| Key | Roles | Check |
|---|---|---|
| script | spawn, substep | Existing stage file under workflows/; required on a [script] entry. |
| schema | step, spawn, substep, verify | Existing JSON schema under schemas/ declaring properties; required on a [schema] entry. |
| ref | every role but def and payload | Entry ids resolving under the ref discipline; render as derived links. |
| flags | step, spawn, substep, verify | One line; each <name…> placeholder leads with an Args row, def title or defined code span, read, returned or minted name, or env key. |
| forwards | step, spawn | {arg, flag?, stage?}: arg an Args row, stage a property of the entry's schema. |
| reads | step, spawn, substep, verify | Entry-schema properties, dotted for nested. |
| returns | spawn, substep | Entry-schema properties. |
| mints | flow roles | branch starts with frontmatter branch-prefix; file relative; each id template holds a {…} or <…> placeholder. |
| timeout | step, substep whose statement holds RUN | Bash tool milliseconds. |
Links are tokens: [[SECTION-NNN]] within the skill, [[SKILL-X-SECTION-NNN]] across; the render derives [Title · SECTION-NNN](#skill-<leaf>-<section>-<nnn>). Legal in statements, item texts, fields.ref, Args default/effect cells, Routing purpose, preconditions and route use; never in payload bodies (exact bytes). Below format 3 the param label and [Title](#anchor) links stay legal.
From skills/quality/railroad-review/railroad-review.cue: a script spawn; the Build and test step's fields (SKILL-RAILROADREVIEW-ROUND-016).
entry: {
id: "SKILL-RAILROADREVIEW-WALK-004"
channel: "script"
fields: {
ref: ["TPL-008"]
script: "workflows/checklist.js"
}
role: "spawn"
statement: "INVOKE `workflows/checklist.js` — one `railroad-checklist` agent per direction lacking a checklist: contracts on a moved head, over the diff; correctness in round 1 without checklist, over the spec."
title: "Checklist agents"
}
fields: {
flags: "--repo <dispatcher toplevel> --root <artifactsDir parent>/wt --branch claude-review/<runNs>-buildtest-r{r} --salvage <artifactsDir>/salvage"
forwards: [{
arg: "expect-head-red"
flag: "--skip"
}]
mints: {
branch: "claude-review/<runNs>-buildtest-r{r}"
}
timeout: 600000
}
A skill never names a smine program: every tool, command or path it calls is a variable bound to one catalog interface. One skill thus runs on a full install, a partial one, or a colleague's machine with their own tools.
| Scope | Values | Filled |
|---|---|---|
install | Provider programs. | At deploy. |
repo | Build, check, test, acdsl program, plans archive: per repository. | At invocation, from the session's environment. |
skills/interfaces.cue holds interfaces and providers, loaded with every skill like vocabulary.cue. An interface is one capability a skill calls.
| Field | Required | Meaning |
|---|---|---|
| id, version | yes | Capability, contract version; a binding names <id>@<version> (plan-sections@1). |
| kind | yes | command (whole command lines, run bare), program (binary the skill appends args to), path (location) or skill (a deployed leaf name the skill hands off to, read in a Skill(…) grant and the hand-off statement). |
| scope | yes | install or repo; every command record is repo. |
| summary | yes | One line, what it does; quoted in an unset repo-scoped variable's abort sentence. |
| example | on a repo-scoped record | Fitting value (go build ./..., bin/acdsl): skill-page placeholder, abort-sentence example. |
| invocation | yes | {cwd, args, stdin, timeout}; a program's args open with its subcommand word (plan-sections <plan file>); timeout a Go duration or none. |
| result | yes | {exit, stdout}; exit maps codes to meanings, with "0" and "other". |
| validate | yes | {resolves: true} for command and program, {exists: "dir" | "file"} for path, {} for skill (the value is one leaf name, nothing to locate). |
A changed exit code, argument shape or output schema is a new version, never an edit in place.
A provider names a program and the versions it implements: {id, program, binary?, component?, implements, probe: ["interfaces"]}. component: install component shipping it, on PATH on every OS it installs on; binary: path the probe runs when it is off PATH (toolpolicy, a hook binary). Version and scope limits: Checks.
| Provider (six) | Scope |
|---|---|
| smine-rules, smine-agentwt, smine-heartbeat, smine-routine, toolpolicy | install |
| acdsl | repo: no component, no program. Every ACDSL repo vendors its own bin/acdsl, so its five capabilities are repo-scoped. |
| none | repo: command, path and skill interfaces build@1, test@1, check@1, plans-archive@1, commit-skill@1. |
interfaces: [{
id: "test"
version: 1
kind: "command"
scope: "repo"
summary: "Runs the repo's full test suite."
example: "go test ./..."
invocation: {cwd: "repo-root", args: "none", stdin: "none", timeout: "20m"}
result: {exit: {"0": "pass", other: "fail"}, stdout: "diagnostic"}
validate: {resolves: true}
}]
providers: [{
id: "toolpolicy"
program: "toolpolicy"
binary: "~/.claude/hooks/bin/toolpolicy"
component: "gate"
implements: ["permission-check@1", "permission-freeze@1"]
probe: ["interfaces"]
}]
env: {
PLAN_SECTIONS: {interface: "plan-sections@1", required: true}
CHECK: {interface: "check@1"}
}
| Field | Required | Meaning |
|---|---|---|
| key | yes | Interface id uppercased, hyphens as underscores (plan-sections → PLAN_SECTIONS); renders as SKILL_<SKILL>_<KEY>, <SKILL> the leaf uppercased without hyphens (SKILL_FDESIGN_PLAN_SECTIONS). |
| interface | yes | <id>@<version> of a catalog record. |
| required | no | Unset, skips the whole skill at deploy (install-scoped) or aborts the invocation (repo-scoped). Absent: optional. |
Text references $SKILL_<SKILL>_<KEY>; scope decides where.
| Text | May reference |
|---|---|
| Entry statements, items, payload bodies | Both scopes: a placeholder can stand there. |
allowed-tools | Both scopes: the gate fills them. A path rule may root at one (Read($SKILL_PLANARCHIVE_PLANS_ARCHIVE/**)). |
| Args cells, routing and model text, other frontmatter values, enforce patterns, check reasons and recoveries | Install-scoped only. |
| Preamble, prose blocks | No repo-scoped one. |
Files under agents/ and workflows/ (scanned recursively) | Install-scoped required only: filled at deploy, never trimmed. |
A reference fits its interface's kind:
| Kind | What may follow the reference |
|---|---|
| program | The args head word ($SKILL_FDESIGN_PLAN_SECTIONS plan-sections …), then a space, backtick, quote, (, ) or line end, so Bash($SKILL_X_TITLE_SET title *) and freeze( |$) pass. Or, passing the program itself as a value, a closing backtick, quote or ) right after (--acdsl '$SKILL_RAILROADREVIEW_ACDSL_CHECK'). |
| command | Nothing: end, backtick, quote, ), >, 2> or |; a command value takes no arguments. |
| path | Anything (unchecked). |
Deploy fills each variable from the first set source:
SKILL_<SKILL>_<KEY> environment variable.SKILL_<SKILL>_<KEY> row (skills.variables, config server skill page).skills.defaults, keyed by interface id, Environment tab's global defaults card).PROVIDER_<ID> environment variable, its PROVIDER_<ID> row, then its catalog program if its component is in the render's set. A nil set, the canonical deploy, counts every component installed.A provider row moves all of a program's capabilities at once; a SKILL_ row pins one.
For the Claude home a repo-scoped variable is never resolved, so never unset, at deploy (source invocation); it skips nothing there. Its reference renders as a harness placeholder reading SKILL_<SKILL>_<KEY> from the session's environment at invocation. The entry whose statement or payload reads it, else each reading item, becomes one fenced block the harness runs before sending the skill content:
```!
awk 'BEGIN { if (ENVIRON["SKILL_FIMPLEMENT_CHECK"] == "") { print "SKILL_FIMPLEMENT_CHECK: not set - <summary>, e.g. go vet ./... - <hint>"; exit 2 }; printf "%s", "\052\052SKILL-FIMPLEMENT-COMMIT-002\052\052 after \140" ENVIRON["SKILL_FIMPLEMENT_CHECK"] "\140 is green \342\200\224 one Bash call per line" }'
```
| Rule | Detail |
|---|---|
| No shell expansion | The harness checks the line against permission rules first, refusing shell expansion ($NAME, ${…}, $(…), backtick). So awk reads values via ENVIRON; no $ in the line. |
| Escaping | One single-quoted program; every text byte outside letters, digits, space and ,.:/_=+@~- is a three-digit octal escape awk decodes (backticks, quotes, braces, dollars, backslashes, brackets, line breaks, multi-byte runes). Abort message cut to the same characters. |
| Required unset | Prints SKILL_FIMPLEMENT_CHECK: not set - … with the record's summary and example, exits 2: invocation aborted; the harness forgives only exit 1 of search commands. Required guards first. |
| Optional unset | Exits 0 silently; its entry or item vanishes whole. |
| Hint | "set it for this repo on the skill page, or export it" in a deploy; "export it in your shell" in the bundle. |
| Install-scoped in the block | Filled value, escaped like text; no $SKILL_ reference survives, so the bundle scan needs no allowance. |
Bash(awk *) | Appended to allowed-tools when the skill declares a deferred variable; also in the gate template. The gate's awk fence admits the program: no redirect, pipe, system, getline. |
env.json | Beside SKILL.md with the Skill environment component: schema 1, skill, version, one entry per repo-scoped variable a rendered block reads (name, interface id, required, scope, summary, example). The skillenv hook exports each value (repo value, else skill default, else global default); refuses a typed /<leaf> or Skill call with an unset required value before the body expands (Install § Per-repo values). The gate fills grants from the same sidecars. |
| Without the hook | Shell exports reach the placeholder alike. |
| Kind | Value rule |
|---|---|
| every value | One line (a command may hold several); no carriage return, backtick, $SKILL_ or parenthesis: grants splice values into Bash(…), a command of several lines as one rule per line. |
| command | One command per line, run in order, first red one stops. Lines split by internal/cmdline, as in smine-rules: spaces split words, double quotes group, no shell operators (&&, |, ;, $(). Readers say "one Bash call per line". |
| program | One word; a path with a slash allowed. |
| path | Opens with ~/ or /. |
Deploy probes each provider it fills from, once per executable per run: <program> interfaces (or binary) prints {"interfaces": [...]}, the sorted versions it implements; all six binaries answer. A failed check leaves the variable unset, with a cause in the deploy log and on the skill page.
| Check | Cause |
|---|---|
| value rules and kind form | value: holds a backtick, value: a program is one word, got 2, … |
| resolves | <word> does not resolve to an executable. Each command line's first word looked up on PATH; a relative word with a slash under the repo it serves (checkout at deploy; the repo's path for a skill-page repo value). |
| exists | <path> is not an existing dir |
| provider claim | smine-rules does not implement plan-sections@1 (answered: …), or the probe's own failure |
required keeps its effect; the skip line carries the cause.
| Unset variable read by | Effect in the render |
|---|---|
required declaration | Skill skipped: skip <leaf> unconfigured (SKILL_…: <cause>); DeployLeaf refuses it. |
| Item (param, bullet, fence) | Item drops, entry stays. |
flags field | Field bullet drops, entry stays. |
| Statement, payload body | Entry drops. |
allowed-tools rule | Rule drops. |
| Enforce anchor's tool, input or exclude pattern | Anchor drops with every check and judgment naming it. |
| Enforce check's reason or recovery, judgment's question or send | That check or judgment drops. |
A Skill(<leaf>) grant or pipeline edge to a skill the render cannot deploy (unmet requires, unset required variable) drops too. Set values fill as text in SKILL.md and copied sibling files, regex-quoted in enforce patterns, JSON-escaped elsewhere in enforce.json (reasons, recovery texts).
An allowed-tools rule reading a repo-scoped variable stays as text only when the render's set holds gate. The gate fills it per session from the session repo's values (skillenv.Resolve, the hook's resolution) and drops what it cannot fill; without the gate (the bundle) the rule drops. Neither deploy nor gate derives a permission rule from a value.
| Layer | Refuses |
|---|---|
| Model check | Referenced undeclared variable; declared one no text reads; optional one read where nothing can drop it; binding outside the catalog; key other than the derived key; reference not fitting its kind; repo-scoped reference outside entries and allowed-tools. |
| Load | Literals smine-rules, smine-agentwt, smine-heartbeat, smine-routine (word-bounded: skill name smine-routines passes) and toolpolicy in model text and every file under agents/, workflows/; repo-scoped or optional references in those files. |
| Catalog load | Duplicate <id>@<version>; validate block not fitting the kind (a skill record takes {}); exit table without "0" and "other"; program interface without args head; command record outside scope repo; repo-scoped record without example; provider claim no record declares; two providers claiming one version; one provider claiming both scopes. |
The skills sync (internal/skills, configserver -sync-skills) renders for one component set and value map: the install record's components and skill rows (skills.variables, skills.providers, skills.defaults), on every writer path. The config server reads the record per request, so the Overview Sync, an apply and a skill-page save all deploy with the rows on disk.
| Home | Repo-scoped variables |
|---|---|
| ~/.claude/skills | Deferred to invocation as placeholders. |
| ~/.codex/skills | Static (a Codex agent runs no placeholder): deploy environment, record row, then global default (Values); else dropped or skill skipped like any other (skip <leaf> unconfigured (…) in <home>). |
| Rendered in process | When | Holds |
|---|---|---|
| SKILL.md | always | Formal clauses stripped, variables filled or deferred. |
| help-test.md | with session-helpers, whose skillhelp hook reads it | Smoke line; Eval: line when eval/definition.json exists. |
| title.json | with session-helpers and a set title-set@1 variable | Title states. |
| enforce.json | with gate and heartbeat | Enforce block. |
| env.json | with skill-environment and a rendered placeholder; ~/.claude/skills only | Repo-scoped variables the placeholders read. |
| answer.md | a skill with answeredBy: "hook" | Payload bodies. |
| .smine-deployed | always | Marker holding the source hash the smoke checks. |
| Case | Deploy does |
|---|---|
| Never shipped | <leaf>.cue, payloads/, eval/, smoke/, agents/ (deploys to ~/.claude/agents/, filled). |
| Other sibling files | context-map.json, workflows/, schemas/ ship, text filled. A deployed copy holds no model: the config server's graph view shows a no-model card. |
needs missed | Entries, routes, edges drop. |
unless held | Entry drops. |
requires missed | Skill skipped: skip <leaf> unavailable (…). |
make skill-bundle writes the standalone bundle to dist/skills for the empty component set: install-scoped values from the shell's exports, repo-scoped ones as placeholders with the shell hint. A skill ships unless it requires a component, carries a license key or lacks a required install-scoped value.
Index dist/skills/README.md: per shipped skill, what the full install adds (summaries of its unset optional install-scoped variables); per skipped skill, what it needs.
- <leaf> (skipped): needs <ref> from the <component> component
Runtime placement fields render into the deployed frontmatter after the authored fields: answered-by:, docker:, enable-delegation:, enforce-permissions:. With answeredBy: "hook" the skillhelp hook answers every typed invocation with answer.md plus the feature catalog; the model check requires a payload entry and refuses flow entries.
Because the model is typed, its rules can be questioned: a format-2 skill is a controlled-English rulebook (MUST, NEVER, If ⇒ STOP, VERIFY), and modeled, z3 answers whether rules contradict, whether an entry is unreachable, and "if a and b hold, can c?"
An optional per-skill analysis tier (one <leaf>.cue field): authoring contract plus compile-time test suite. No runtime effect: deploy strips clause lines; the executing agent never sees them.
| Tier | Opt-in | What happens |
|---|---|---|
| Annotations | default | Any skill's clause lines are parsed, syntax-checked, rendered (graph view, diagrams), never resolved against a vocabulary: intent documented like interface types, cheap, visible, unenforced. |
| Model | tier: "strict" | Checked semantic model: atoms resolve, kinds pair legally, skillmodel check runs the z3 battery, queries/ is the test suite; make audit-full fails the release gate on an error finding or verdict mismatch. solve, query refuse annotation-only skills. |
Interacting state: counters, modes, obligatory stops gating real actions, fan-out/join arithmetic, as in the railroad-review dispatcher. Linear prose protocols gain nothing.
Atoms build expressions, expressions clauses; clauses derive the model, queries question it.
One named, typed fact about a run.
| Kind | Declared | Example |
|---|---|---|
| true/false | default | asks_user, tools_loaded |
| whole number | kind: int | remaining_budget, lanes_spawned |
| choice | kind: enum, fixed ordered members | mode: interactive, nightly. Order ranks ("at least medium severity"); members exclusive by construction, no exclusivity axioms. |
Shared atoms: skills/vocabulary.cue, alias phrases mapping one wording to one atom. A skill's own: the atoms map of its <leaf>.cue, same shape:
atoms: {<id>: {sort, kind?, members?, source?, aliases?}}
source is a closed grammar; free text fails the load. runtime binds an enforce anchor's capture, so heartbeat's check and the model read one value.
arg: <row>[ => <member>] session environment runtime: <anchor>[.<capture>[ = <literal> | != <literal>]]
An unread atom fails make skills-check; delete it and its clauses.
| Atom | Kept alive by |
|---|---|
| Local atom, format-3 skill | Stop, rule, quality or route section preds; queries. Not step preds. |
| Shared atom | Such a reader in some skill. |
| Local atom, format-2 skill | Any pred. |
An expression (expr) is a condition over atoms: and, or, not, => (implies), parentheses, comparisons; +/- and number literals on ints.
remaining_budget < round_cost lanes_returned = lanes_spawned mode = nightly apply_level >= medium base_provided and base_unresolvable
The last: "a base was given and could not be resolved."
An entry's pred/post fields in <leaf>.cue, written once when authoring. SKILL.md renders them as code lines directly above the headline, no blanks between, each ending in the markdown hard break \ so they stay separate lines, not one paragraph:
`{pred: <expr>}` \
`{post: <atoms>}` \
<entry headline>
| Entry | pred: | post: |
|---|---|---|
| rule | Invariant. Never-prompt rule: {pred: not asks_user}. | |
| step, spawn, substep, verify, stop | Firing guard. Base-unresolvable stop: {pred: base_provided and base_unresolvable}. | Conjunction of atoms made true: a verify's guarantee, a step's effect. |
| quality | Soft: no effect on consistency verdicts; witnesses prefer runs satisfying it. | |
| no clauses | Uncovered. Coverage reported (check prints formalized/coverable counts), never enforced. |
| Rule | Detail |
|---|---|
| Grants stay | Post-granted atoms start ungranted, then stay granted. Reachability is causal: a pred needing tools_loaded fires only after a post grants it. |
| Obligatory stops | A stop whose pred holds blocks every non-stop firing there; a fired stop blocks everything after. |
| Deploy | Strips entry clause lines from the agent copy (KEEP_FORMAL=1 keeps them); route section lines stay. |
| Annotation tier | Without tier: "strict": clauses must parse (gate-checked in every skill); atoms unresolved, no model claims, typed comparisons are documentation, not checked arithmetic. |
In memory, never committed, derived 100% mechanically from the clauses.
| Clause source | Becomes |
|---|---|
A rule with pred: | An invariant. |
| A flow-role entry | A transition. |
| Sections | A chain in document order. |
No translation layer, review pass or committed semantic-model artifact: a pure function of <leaf>.cue plus skills/vocabulary.cue, structurally drift-free. Each invariant and transition keeps its entry id, so solver answers point at <leaf>.cue and SKILL.md entries.
A JSON file in the skill's queries/: one question, frozen to re-ask forever.
| Field | Meaning |
|---|---|
| assume | Given ("suppose the run is nightly"). |
| check | Tested ("…can the gate pause?"). |
| expect | Passing answer: sat (a scenario where all holds together) or unsat (the rules forbid it). |
"If a and b, c cannot be" is exactly: assume a and b, check c, expect unsat.
| Answer | Means |
|---|---|
| sat | Possible. solve prints a witness a human can check (scenario: asks_user=false, mode=nightly, lanes_returned=4, …). Whole-model consistency wants sat: no contradiction. |
| unsat + core: … | Impossible. The core, a proof sketch, lists clashing invariants and transitions by entry id, transitions first; premises (your assumptions, vocabulary exclusivity axioms) stay out. |
| unknown | z3 hit its 10-second per-check timeout. No verdict; solve and query exit 2. |
Can the review ask the user?
make model-query SKILL=railroad-review EXPR='not asks_user'
verdict: unsat core: SKILL-RAILROADREVIEW-EXEC-002
EXEC-002 is the invariant not asks_user: no state with the review asking is reachable.
Cores are minimal: of several rule chains forbidding one thing, the core shows one.
skillmodel check <skill dir> (make model-check SKILL=<name>) runs it on the derived model, one line per finding, then a summary:
<skill dir>: <severity> <check>: <message>
| Check (run order) | Severity | A finding means |
|---|---|---|
| validate | error | Unparsable clause, unresolved atom, illegal kind pairing, consumed bool atom without producer or source, or exclusivity member not a declared bool atom. |
| consistency | error | Invariants and exclusivity axioms contradict; names the clashing invariants. |
| guard | warning | Guard never holds under the invariants: entry dead. |
| redundant-guard | warning | A pred conjunct follows from invariants and sibling conjuncts ("guard always holds" for a one-conjunct pred). |
| halt | warning | A stop's guard holds in every allowed state: stops every run at its start. |
| preservation | warning | Firing from a state satisfying invariants and guard can break an invariant once post atoms turn true. |
| reach | warning | Entry or stop never fires within the complete bound. Or "unreachable under atom = member": reachable otherwise, guard feasible there. |
| completion | warning | No transition of the last phase with transitions fires within the complete bound: every run halts or stalls early. |
| Rule | Detail |
|---|---|
| Severity | Only errors fail check (exit 1); warnings exit 0. A validate error, or an inconsistent model after consistency, stops the battery. Later checks skip entries guard found dead. |
| Tier | Annotation: validate only, no z3. Model: all eight, z3 on PATH or check exits 2. unknown on consistency exits 2, on a warning check yields an "undecided" warning. |
| Budget | One z3 process per check family (consistency; guard, redundant-guard, halt, preservation; reach, completion); each obligation a check-sat-assuming selector, 10-second timeout. |
Posts grant only bool atoms; int and enum atoms never change in a run. So a run changes state at most min(P, G) times, plus one step to fire the target.
complete bound = min(P, G) + 1 G = the number of granted bool atoms P = the number of transitions granting at least one
An unsat reach verdict at that bound is therefore a proof, not a sample. P and G count only the cone of influence: atoms an effective guard, effective invariant or the question (assumes, check) mentions, closed over exclusivity groups. Unread atoms, such as observation atoms, get no step copies and don't inflate it.
smt and solve default to it; an explicit lower --bound or query bound runs, warning its unsat is no proof.
Anchors are solver semantics, not just drawing.
| Anchor | Effect |
|---|---|
| replaces: <items> | While the route pred holds, replaced entries can't fire (guard pred and not <route pred>) and replaced rules' invariants are suspended (not <route pred> => <invariant>); the route runs instead. |
| phase window | Route transitions fire only in the anchor's phases. replaces: lowest to highest replaced-entry phase. between: <after>..<before>: <after>'s phase to <before>'s; start, end and context-section sides unbounded. Entries within a section stay unordered. |
skillmodel query --expr '<expr>' <skill dir> (make model-query SKILL=<name> EXPR='<expr>'): does the model imply the expression? The solver asserts effective invariants, exclusivity axioms and not <expr>, no soft clauses.
| Verdict | Means |
|---|---|
| unsat | Holds; the core names the proving invariants. |
| sat | Counterexample: scenario: atom=value, …. |
Validity, unlike a committed static query file's satisfiability (assume + check, sat = possible). Exit 0 on either verdict, 2 on error or unknown.
The make targets in The commands (make audit: validate only) and the config server graph view, on page load. The read-only graph view shows atoms with grant and read counts, the enforce declaration, the tier in its toolbar (tier: strict — <n> atoms, <m> queries or tier: annotations) and an all-warnings button listing every battery finding and structural warning. Edit the model as <leaf>.cue text.
Start with make model-analyze ("run it, see it"); the rest answer narrower questions.
| Command | Does |
|---|---|
| make model-analyze | Default SKILL=railroad-review. Clause checks, battery (consistency included), then every committed query; annotation tier: check only. |
| make model-queries | Every opted-in skill's battery and committed queries, red on an error finding or verdict mismatch; the model test-suite step of make audit-full. |
| make model-check SKILL=<name> | One skill: clause validation, model-tier battery, coverage report; prints the tier (tier: model / tier: annotations) and model-tier warning count. |
| make audit | Solver-free: its skill-model gate (ACDSL-SKILL-013) runs validate only. |
| make model-query SKILL=<name> EXPR='<expr>' | Does the model imply <expr>? Ad hoc queries. |
| go run ./cmd/skillmodel model <skill dir> | Dumps the derived model as JSON. |
| make skill-export SKILL=<name> | Prints the loaded skill model (entries, sections, merged atoms). |
| go run ./cmd/skillmodel diagram <skill dir> | Or make model-diagram SKILL=<name>: whole-skill Mermaid, below. |
| go run ./cmd/skillmodel query --expr '<expr>' <skill dir> | Ad hoc "does the model imply this?", uncommitted. |
| go run ./cmd/skillmodel solve --query my-question.json skills/quality/railroad-review | Ad hoc assume/check satisfiability, uncommitted. |
| go run ./cmd/skillmodel solve --reach <entry-id> [--bound n] <skill dir> | Dead-entry check. |
solve --reach unsat: no run ever fires the entry, because
replaces route takes its place whenever it could fire.At the default complete bound that is a proof (DEAD — unreachable within the complete bound); a lower --bound warns and prints only unreachable within bound <n>. A query file commits such a question with reach (an entry id) and optional bound in place of check. The battery's reach check already covers every entry and stop.
diagram draws the whole skill as Mermaid; section kinds come from each heading's {section: …} line, kind-less skills render all-phase as before.
| Element | Drawn as |
|---|---|
| The phase chain | In order. |
between routes on phase boundaries | Forks: a split junction, one branch per route sharing the anchors, chains via the previous route's last entry, an otherwise edge unless the branch preds cover every case, a reentry junction. |
Other between routes, all replaces routes | Replaces/between edges to their anchors' sections. |
| Context sections | Detached states. |
| Entries | Ordered kind-tagged lines in their state; flow entries numbered, clauses and enforcement channel shown. |
| Stops | Edges into the halt sink; context-section stops are halt lines. |
| Flag | Effect |
|---|---|
| --phases (PHASES=1) | Trims to the execution chain. |
| --json (JSON=1) | Same projection as stable JSON: the config server graph view's data contract. Server-side the blob adds args, env, warnings and notes outside the contract. |
| Framing | Rule |
|---|---|
| Routes are implicit | Steps gated on an earlier-established pred already form a route; the route kind names and groups it. Its line declares exactly one anchor, never inferred from section order: replaces: <items> (substitution) or between: <after>..<before> (optional insertion). |
| Phase interfaces are derived | Never authored: produces = atoms its posts grant, consumes = earlier-granted atoms its preds mention. Shown per phase card with the graph view's formal layer toggle on. |
Boundary principle: a section ends where its last granted output lands; prose on the next section's internals goes there.
Programmatic stages are workflow files under workflows/: one per spawn, or a composition nesting others (railroad-review's round.js: lanes, dedup, verify, station). The dispatcher invokes the file at its spawn entry; the script channel names it.
| Rule | Detail |
|---|---|
| Prompts | Spawns agents, builds prompts from the skill's payloads (GEN:payload blocks, never hand-copied), folds returns into its result. |
| Schemas and shared blocks | Schemas in schemas/*.json, shared prompt blocks in workflows/common/, injected between GEN markers by make workflow-gen (rules binary over internal/workflowgen). The runtime allows no imports or file reads. |
| Computed args | Every binary-computable dispatcher arg comes from an installed rules binary (smine-rules) subcommand; an enforce anchor captures its printed document, a provenance check pins it. |
| Known exception | railroad-review stage files still carry severity, id, dedup, retry and validation policy, pending their own plan. |
Closed producer/consumer system: an atom guarding flow is granted by a step's post or declared an input by its source (grammar under Atom). skillmodel check enforces producer-or-input on the model tier; the graph view shows inputs as invocation tiles in a column left of the structure.
Refs point into vocabulary (defs, payloads) or own-section wiring, never cross-section control flow: sequencing belongs to the condition system. A route may ref the entries its anchor clause names.
| Element | Behaviour |
|---|---|
| Route cards | Set into the host phase card, cut at the anchor: a substitution before its first replaced entry, an insertion after its split entry; split line in, reentry line back to the chain. Lists the anchor's refs as a replaces or between line. |
| Invocation scenario | Arg and session tiles on: inactive routes and rows an active route replaces gray out. |
| Ref violations | ⚠ analyses on their rows, listed in the all-warnings panel, which opens left outside the graph box; entry cards open right. |
| Links | ↗n outgoing, ↙n incoming on the row; focusing it draws them (neon out, orange in). |
| Topic | State |
|---|---|
| Adoption | Every format-2 skill carries clause annotations (2026-09 fleet reformat). Only railroad-review is model-tier: its atoms (typed budget/lane arithmetic, shared mode enum), battery and three committed queries run in audit-full. |
| Opting in | Set tier: "strict", declare atoms for every clause atom, pin worthwhile queries. Sourced input atoms need no tier and feed the invocation tiles (fdesign). |
| Expressiveness | Bools, whole-number arithmetic, ordered choices: contradiction, implication, counting, causal-ordering questions. No unbounded backend: the complete bound makes unrolling exhaustive. |
| Out of scope | Real time, "eventually". Liveness and optimization: staged in the stage-2 doc plans/2026-09-14-skill-structure-wip-ada2d65/design/smt-stage2.md, with query-authoring conventions and the Stage-3 runtime-referee outlook. |
| Soundness | Causally sound reachability: only posts change state (non-granted bools rigid, granted ones flip only via a granting firing); boundary firings assert posts; negative bool assumptions and negated stop preds block naturally; Route anchors constrain firings. |
| Anchoring | make audit: every clause parses, every model-tier atom resolves. make audit-full: battery plus re-proven committed queries. |
| Meaning | Whether a clause means the English is the author's judgment, made once, reviewed as the <leaf>.cue diff. make skills-check keeps it canonical, the skill dir free of generated files; nothing model-shaped can drift. |