Automatic skills
Skills are on by default. For the shipped flows nobody has to set anything up: the Foundry picks the skills, checks them, locks them and loads them. It never installs, downloads or grants anything. This part gives the whole picture and links to the sections below for the detail.
What happens by itself#
| Stage | What the Foundry does | Detail |
|---|---|---|
| Profile | Reads the repository for evidence of its technology. | Skill catalogue |
| Catalogue | Planner steps with skills: catalog get a short list of approved or built-in skills with a matching pin that pass coder resolution. | Skill catalogue |
| Request | The final plan ends with one SKILL_REQUEST: line. | Skill request |
| Resolution | The ids become exact id@version plus their dependencies, and are checked under skills.unresolved after the plan gate. | Skill selection, Missing and conflicting skills |
| Lock | The first agent step writes skill-lock.json. It is checked again before every agent session. | Skill lock of a run |
| Loading | A Claude session gets one <foundry-skills> block; a resumed session gets a one-line reminder. | Skills in Claude sessions |
| Review checks | Steps with skill_role: reviewer get the REVIEW.md checks of the loaded skills, on Claude and Codex. | Review checks for reviewers |
What policy decides#
skills.catalogue: what a planner may see.skills.selection: what a coder may get.includemakes a skill mandatory.skills.unresolved: what happens to a skill that cannot be used: stop, or warn for low-risk work.skills.review: the size of the review block.- Pins (
scf skills pin): which content is approved.includeis never a pin.
These are all the settings with their defaults:
skills:
builtin: true
roots: []
repository: false
catalogue: { max_candidates: 20, max_tokens: 2000, include: [], exclude: [] }
selection: { max_skills: 6, max_skill_tokens: 5000, max_tokens: 15000, include: [], exclude: [], exclusive: [] }
review: { max_tokens: 3000, max_skill_tokens: 1000 }
unresolved:
unknown: stop
missing: stop
untrusted: stop
conflict: stop
oversized: stop
high_risk: [migration, migrations, security, messaging, kafka, rabbitmq, amqp, queue, outbox]
skills.tools is read but no run uses it yet.
Write a skill#
Make a folder
<data folder>/skills/<id>/, or one under a folder ofskills.roots. Optional sub-folders arereferences,scripts,assetsandevals.Write
SKILL.md: frontmatter withname(the same as the id) anddescription(at most 1024 characters), then the instructions. At most 64 KiB. Only the description and the instructions reach a session.Write
skill.yaml:id: my-skill version: 1.0.0 category: language capabilities: [strict-typing] dependencies: - id: small-changes min_version: 1.0.0 conflicts: [] roles: [coder, reviewer] risk: lowMore fields:
detectors,connectors,tool_profile,provider,composable(see Complementary providers). Aproviderblock says that the skill needs a vendor:provider: {name: acme, products: [{name: kafka, version: "3.7"}], tools: [kafka-admin]}.nameis a slug; at most 16 products and 16 tools. A skill without it is portable. The block is text only: it grants no connector, shell or network access. The catalogue andscf skillsshow it (see Skill catalogue). The eleven built-in packages inskills/(typescript,java,spring-boot,nodejs,rest-openapi,kafka,ibm-mq,cassandra,oracle,integration-testing,small-changes) are examples;spring-bootshows a dependency (onjava).Optional:
REVIEW.md, a few short checks for a reviewer, at most 8 KiB. The package needs thereviewerrole or no roles.Versions are
MAJOR.MINOR.PATCH. Any change to a pinned package needs a new version, orpin --replace.Pin it: run
scf skills, check the package, copy the digest, then runscf skills pin <id@version> <digest>.Evidence for a new version: see Evidence to promote a skill version.
No credentials and no live endpoints: see Credentials, endpoints and connectors.
Context budgets#
| Limit | Default | When it is passed |
|---|---|---|
catalogue.max_tokens | 2000 | The lowest-ranked skills are dropped, then descriptions are shortened. |
selection.max_skill_tokens | 5000 | The skill is refused as too-large. |
selection.max_skills / selection.max_tokens | 6 / 15000 | The skill is refused as over-count / over-budget. |
review.max_tokens / review.max_skill_tokens | 3000 / 1000 | The skill is left out of the review block and logged. |
The estimate counts the description and SKILL.md, not reference files. A resumed session costs one reminder line; a new session gets the block once.
Same skill twice#
Two installed skills with the same description and instructions are the same text to the model. The Foundry sends that text once: the first copy in load order keeps the section, and its also="…" attribute names the other copies. The other copies still count as loaded in the run, and their lock entries stay, but they cost no tokens. A copy is never reported as omitted, and it never blocks a mandatory skill. The same applies to review blocks, where the key is the description and REVIEW.md. In the catalogue, one entry is offered per text: the best tier wins, then the lowest id, then the lowest version. The run log says n mirror(s) collapsed. Skills whose text differs by even one character are not merged.
Trust#
- Sources come in this order: the data folder, the approved releases in
skill-releases/(see "Retained releases" below),skills.roots, the built-in skills, and the repository (only withskills.repository: true). Personal.claudeand.codexfolders are never scanned. - Releases. A folder in the data folder or in
skills.rootsis a catalogue release: it holdscatalogue-release.json(schema: 1,catalogue,version,createdAt,sourcewithrepositoryand a fullcommit, andskillswithid,versionanddigest). It loads only if its catalogue, version and digest are listed inskills.releases.approved, and its packages match the manifest exactly. Otherwise the whole folder is refused (root-refused) and none of its packages load.scf skillsshows aRELEASEline for each. Skills are never fetched from the network during a run or a resume. - Retained releases. Every release folder
skill-releases/<catalogue>/<version>/that is listed inskills.releases.approvedis loaded, as the sourcerelease <catalogue>@<version>. Within one catalogue the newest version is read first, so its packages are the active ones. An older approved version stays loaded for exact lookups: a run locked toid@versionand digest still resumes after a newer version is published and approved, andscf skills release verifyand the resolver find it. A retained package that a newer one shadows is never offered to a catalogue or chosen for a new plan. A folder in the store that is not approved is not loaded, andscf skillsprints aNOTEfor it (not a problem). Publish and discovery never delete a release, so every approved version is kept. Askills.rootsentry that points insideskill-releases/is ignored with aNOTE; list the release inskills.releases.approvedinstead.- Approval is the pin. An approved, verified release lists the digest of each package, so its packages are selectable with those digests (source
catalogue, trustapproved, pinpinned). They need no entry inskills.lock.jsonand noscf skills pin. A hand pin for the sameid@versionwith another digest is still amismatch, and the stricter rule wins. Data folder andskills.rootspackages still need their own pin. A run locked to an older approved release keeps its exactid@versionand digest, so it still resumes after a newer release is approved. A run lock written before this change, with sourceadminfor the same package, still verifies. - List.
scf skills release listreads only. It printsRELEASE <catalogue>@<version> <digest> <state> <n> skills, locked by <k> runs(ornot locked by any run) for each stored release, where the state isapproved,unapproved,mismatchorinvalid. An approved release line ends with(source catalogue). It prints noPINlines. It ends withOK <n> releasesorNo releases are published, and exits 1 when the store cannot be read in full.
- Approval is the pin. An approved, verified release lists the digest of each package, so its packages are selectable with those digests (source
- Release manifest 2.
catalogue-release.jsonmay also sayschema: 2. Each skill then lists every file of its package (files:path,sha256,bytes), itsprovenance(source,repositoryKey,commit,path,trustand an optionalreview), itslicences, itsattributionfiles (kept in<folder>/.attribution/<id>/), itsevaluation(suite,record, andverdictpassed,failedornot-run) and an optionalrevoked(on,reason). The release digest covers all of these fields. A schema-1 manifest works as before.- Integrity. Each discovery checks a schema-2 folder file by file: every listed file must be there with its size and SHA-256, every package digest must recompute, and no other file may be in a package or in
.attribution/(onlycatalogue-release.jsonand.DS_Storeare allowed besides). A link is refused. Any failure refuses the whole folder (root-refused), whateverallow_unreleasedsays. - Revoked skills. A skill with
revokedin an approved schema-2 release is never loaded, from any root. A locked run that uses it stops withskill integrity: … is revoked by release <catalogue>@<version>and the plan-again hint; it no longer says "is missing". A revocation changes the release digest, so the owner approves the new digest inskills.releases.approved, and the old one stops matching. A revocation in a release always wins over a local lifecycle state (see "Skill lifecycle" below). - Evaluation verdict. Loading does not depend on the verdict.
scf skills release verifyshows it for each skill. - Offline check.
scf skills release verify <dir>checks one release folder with the same rules and opens no network connection. It printsVERIFIED <catalogue>@<version> <digest> <n> skills(exit 0), orPROBLEM …(exit 1). It then printsAPPROVEDorNOT-APPROVEDagainstskills.releases.approved. The reason never shows a path or file content.
- Integrity. Each discovery checks a schema-2 folder file by file: every listed file must be there with its size and SHA-256, every package digest must recompute, and no other file may be in a package or in
- Signed approvals. Set
skills.releases.keysto require a trusted signature: up to 10 entries{id, ed25519}, whereed25519is the base64 of the 32-byte raw Ed25519 public key. Once the list is not empty, everyskills.releases.approvedentry must also havekey(anidfromkeys) andsignature(base64 of 64 bytes). The signature covers the textscf-skill-catalogue-approval-v1, then the catalogue, version and digest, one per line. A release with the approved digest that is unsigned, or whose signature does not verify, is refused (root-refused: "is not signed by a trusted key" or "signature does not verify"). None of its packages load, even withallow_unreleased. With no keys, nothing changes. The config is refused for an unknown key, a key id listed twice, a key that is not 32 bytes, orkeywithoutsignature(or the reverse).- Before you add keys, sign every approved entry, including the entries that locked runs use. Once keys are set, an unsigned approved entry is refused, so a run locked to it cannot resume. Sign the old entries first, or finish those runs before you add keys.
- Sign. The publisher runs
scf skills release sign <catalogue>@<version> --key <private-key-file>on a stored release. The key file is a PEM Ed25519 private key. The command refuses a link, and a file that group or others can read (usechmod 600). It printsAPPROVE <catalogue> <version> <digest> key <id> signature <b64>; paste thekeyandsignatureinto the entry. It never prints key material. It signs only releases in the store, because the signature covers the catalogue, version and digest. - Status. Once keys are set,
scf skills release verifyandscf skills release listprintSIGNED <key>,UNSIGNEDorBAD-SIGNATUREfor each release.
- Adopt a release. An administrator can name the local artifact of an approved release in
skills.releases.approved[].artifact: an absolute path to a schema-2 release folder. A URL, a relative path, a network path, or a path inside.claudeor.codexis refused when the config is read.scf skills release adopt [<catalogue>@<version>]copies each approved release that is missing fromskill-releases/(or only the one you name). It checks the folder with the same rules asverify, requires a valid signature fromskills.releases.keys, and requires the catalogue, version and digest to match the entry. Then it writes the release once. It prints one line per entry:ADOPTED <catalogue>@<version> <digest>,PRESENT,REFUSED <catalogue>@<version>: <reason>orMISSING-ARTIFACT. A refusal writes nothing, and the exit code is 1 on any refusal (or on a named entry withMISSING-ARTIFACT). The command opens no network connection.- Missing release. An approved release that is not in
skill-releases/is ascf skillsproblem (root-missing) that says to runscf skills release adopt. A run never fetches it. Askills.rootsfolder that holds the exact approved release (same digest, stateapproved) does not trigger the problem.
- Missing release. An approved release that is not in
- Local development. To try an unpublished catalogue, set
skills.releases.allow_unreleased: true. Folders without an approved release then load, andscf skillsprints aNOTEfor each. Keep itfalse(the default) in production. Existing folders are refused after an update until they are an approved release or this is set. - Pins tie a version to its digest. A mismatch blocks the skill until you pin it again or give it a new version.
- Repository skills are
unapproved: they are listed only withskills.repository: true, never pinned and never selected. - Isolation. A Claude session that gets a skill block, or that is a reviewer, runs without your personal Claude setup, even with
isolate_agentsoff. Steps withskills: offorskills: catalogget no skill block, so they do not turn it on by themselves. Codex reviewers ignore your Codex user config. - No grants. A skill cannot give a tool, a permission or network access. The Foundry's rules and the task win.
Skill lifecycle#
An administrator can change the state of one approved or built-in skill version. The record is skill-lifecycle.jsonl in the data folder: one JSON line per change, only ever added to. A missing file means every version is active, so an install without changes behaves as before.
| State | New plans | Locked runs (a run that already has a skill lock) |
|---|---|---|
active | selected | selected |
deprecated | selected | goes on; the run log has a skill lifecycle: note |
quarantined | not selected (ineligible) | stops with skill integrity: unless allow-run was given for that run |
retired | not selected (ineligible) | goes on; the run log names the replacement |
revoked | not selected (ineligible) | always stops with skill integrity: … is revoked |
- Commands.
scf skills lifecyclelists the skills that are not active, with their state and replacement.scf skills lifecycle <id@version>prints the history of one skill: one#seqline per change, and areleaseline when a release revokes it.scf skills lifecycle <id@version> <state> --reason <text> [--replacement <id@version>]sets a state. It prints the transition (kafka@1.0.0: active → quarantined (#3)), oneRUN <id> locks kafka@1.0.0: <effect>line per run whose skill lock holds the version, thenOK.scf skills allow-run <run-id> <id@version> --reason <text>lets one run go on with a quarantined version; it printsALLOWED <run-id> <id@version> (#5). Resume the run to go on.Use--runs-dir <dir>to point at another runs folder. Without it, the runs folder of the data folder is searched. - Who. Only administrators may change the record or allow a run. The admin API has the same actions:
GET /api/skills/lifecycle,POST /api/skills/lifecycleandPOST /api/skills/lifecycle/allow-run(see the permissions table in section 7). - What can change. Only approved and built-in versions have a lifecycle. A repository skill, or a version that is not installed, is refused. A state cannot be set again to the state it already has, and a
revokedversion never changes again. - Replacement. Optional for
deprecated,quarantined,retiredandrevoked. It must be an installed, approved version that is not quarantined, retired or revoked. Retiring the active version of an id, with no replacement installed and pinned, blocks that id for new plans. There is no fallback to another version of the same id; the message names the replacement. - Reason. One to 200 characters, no control characters, no spaces at the ends, and not a credential-looking token. The reason is shown in the listing, the history and the run log. Stop reasons do not repeat it.
- Allowances. An allowance belongs to one run and one quarantine. A new quarantine starts with none. An allowance does not help when the version is revoked by a release, and
allow-runis refused for such a key. - Releases win. A
revokedskill in an approved release (see "Revoked skills" above) overrides any local state. The listing saysby release <catalogue>@<version>. A release can only revoke: it cannot restore a version, and it cannot carry other states. - Fail closed. A record that cannot be read stops every new plan that selects a skill and every locked run with skills.
scf skillsprintsPROBLEM the skill lifecycle record …with the line number, and never the content. This applies when the file has a line that is not valid JSON, a field that is not known, a gap inseq, afromthat does not match the state, a change the rules refuse (for example a hand-writtenrevoked → active), a same-state change, a symbolic link, or a file over 4 MiB. Repair the file from a copy, then plan again. - Size limit. The record may hold 4 MiB. A change that would go over the limit is refused before anything is written, and the file stays as it was. Do not delete or shorten the file: without it every version counts as
activeagain. Archiving is a manual step for an administrator, and nothing rotates the file. - Delay. The run page reads the registry with a cache of up to one minute. The admin API clears the cache after each change; a change made with the command line can take up to a minute to show there. The engine always reads the record fresh.
The two pipelines and skills#
| Step | Flow | What it does for skills |
|---|---|---|
plan, revise_plan | issue-gitflow, issue-plan | Gets the catalogue (skills: catalog) and writes the SKILL_REQUEST line. |
plan_review | issue-gitflow, issue-plan | Review checks for the draft request (skill_role: reviewer). |
risk_gate | issue-gitflow | Plan gate: the request is validated and resolved under skills.unresolved. The run stops here on a problem. |
post_plan | issue-plan | Plan gate: the same check. Posts "Required skills" and writes the plan record. |
plan_check | issue-code-daily | Compares the plan comment with the plan record and carries the request. |
implement | issue-gitflow, issue-code-daily | The first agent step: writes skill-lock.json and loads the block. |
fix_guard, fix_tests, address_review_N, docs | issue-gitflow, issue-code-daily | The lock is checked again. A resumed session gets a reminder, a new session the full block. |
review_1, review_2 | issue-gitflow, issue-code-daily | Review checks from the lock. |
For the same plan and commit, both pipelines lock the same skills.
Find out why a run stopped#
Look here, in this order:
- The Skills card of the run page (Skills on the run page).
- The stop reason of the run.
- The log lines:
skill catalogue: …,skill context: loaded|reloaded|reused …,! skill context: rejected (…),⚠ skill …and· skills: off for this flow. scf skills: pin state, both digests and problems.- The health line: skill problems and
skill lock.
| The stop reason starts with | What to do |
|---|---|
planning failed: the skill request of the plan is not valid | start the run again so the issue is planned again |
skill integrity: (a skill changed, is missing, is no longer pinned, now comes from the repository or cannot be verified; or the lock of the run is missing or changed) | restore the exact locked package and its pin (run scf skills to see them), then resume the run; or plan again to lock the current skills |
skill integrity: … is quarantined | an administrator may run scf skills allow-run <run-id> <id@version> --reason <text> (the command is in the reason), then resume the run; or plan again. A … is revoked reason (by a release or locally) cannot be allowed: plan the issue again |
skill selection is blocked: … does not fit the skill context budget | raise skills.selection.max_tokens, then resume the run |
skill selection is blocked: | pin the skill or change skills.selection.include, then resume the run |
skills not resolved: N skill(s) cannot be used — id [code]: … | see the table of codes below |
skills not resolved: skills.selection include/exclude is not valid | correct the lists in the config, then resume the run |
skills not resolved: … with "plan the issue again" (the plan comment is gone or was edited, a newer plan has no record, the record was cleaned up, the comments could not be read) | plan the issue again, then resume the run |
skills not resolved: the skills could not be checked, the skill request of the plan could not be read again or the plan record could not be checked | resume the run; if it stays, plan the issue again |
skills not resolved: the plan records of this issue are not valid | the stored plan records of the issue cannot be read: plan the issue again, or ask an admin to check the plan records in the data folder, then resume the run |
skills not resolved: the plan record could not be written (…) | fix what the reason names (often the data folder cannot be written), then resume the run to try the step again |
The reason code of each skill, the kind of policy that decides it, and what to do:
| Code | Kind | What to do |
|---|---|---|
unknown | unknown | Put it in the skills folder of the data folder, pin it with scf skills pin, then resume the run |
excluded | untrusted | Take it off skills.selection.exclude or skills.catalogue.exclude, then resume the run |
unapproved | untrusted | Copy it to an administrator skills folder, pin it, then resume the run |
unpinned | untrusted | Check the package, run scf skills pin <id@version> <digest>, then resume the run |
mismatch | untrusted | Run scf skills to see both digests, then pin it with --replace or give it a new version |
unverified | untrusted | Fix or delete skills.lock.json, pin again, then resume the run |
role | untrusted | Release a version that allows the role, or plan without it |
too-large | oversized | Raise skills.selection.max_skill_tokens or shorten the skill |
over-count | oversized | Raise skills.selection.max_skills or request fewer skills |
over-budget | oversized | Raise skills.selection.max_tokens or request fewer skills |
dependency-unavailable | missing, or the kind of the dependency's own problem (untrusted for an unpinned dependency, oversized for a large one) | Fix the dependency by its own code (the message names it); or shorten the chain |
dependency-version | missing | Install a newer version of the dependency and pin it, then resume the run |
dependency-cycle | missing | Remove the cycle from the skill packages, then resume the run |
conflict | conflict | Put one of them on skills.selection.exclude |
ineligible | untrusted | The version is quarantined, retired or revoked (or the lifecycle record cannot be read). Use the replacement or another skill, then plan again |
blocked | conflict | A mandatory skill was refused or the include/exclude lists are not valid. Fix that first |
A kind set to warn lets low-risk work go on. Medium and high risk, and mandatory skills, always stop; see Missing and conflicting skills.
Skill sources#
The Foundry reads skill packages only from approved folders and builds one list from them. A package that does not pass the skill schema is not listed, and the problem shows in the health line. The planner's catalogue and the resolver read this list; see Automatic skills.
skills:
builtin: true # skills shipped with the Foundry (default: true)
roots: [] # more administrator folders, absolute paths (default: none)
repository: false # also read <repo>/.claude-factory/skills (default: false)
- Sources, highest first:
<data folder>/skills, then each entry ofrootsin order, then the built-in skills, then the repository's skills (only whenrepository: true). - Same id and version twice: the one from the higher source is kept. The other is dropped and reported as a duplicate.
- Same id, other version: the highest source wins and its version is the active one. The other versions stay in the list, marked as shadowed.
- Problems are never silent: a missing, unreadable or refused folder, an invalid package and a symlinked package are each named in health and in
scf skills. - Personal folders are never scanned: the config refuses a root with a
.claudeor.codexpath segment. The registry also refuses~/.claude,~/.codex,CLAUDE_CONFIG_DIR,CODEX_HOMEand symlinks into them. - Limits: 500 folders per root; 1000 packages or 64 MiB of files in all. The result is cached for 60 seconds.
- Settings card: an admin can change these keys on the Skills card of Settings (see Settings and safety); a change that lowers safety asks first.
Credentials, endpoints and connectors#
skill.yaml can name the connectors a skill needs:
risk: medium
connectors: [kafka, oracle]
- Connectors are slugs only, at most 16, no duplicates. The package needs
risk: mediumorhigh. No run connects to anything. A skill that names one stops the run at the plan gate, or gets a warning underdegrade; see Skills, tools and connectors. - Refused: credential files by name (
.env,*.pem,id_rsa,*.key,*.p12and similar;.example,.sampleand.templatecopies are fine), credentials and live endpoints in any file, and file or folder names that look like a credential. - Write endpoints like this:
kafka://<broker>:9092,redis://${REDIS_HOST}:6379,amqp://mq.example.com,localhost, the wordshost,hostname,server,brokerordb, and names ending in.example,.testor.invalid. A placeholder user or password does not help when the host is real. - Messages:
is a credential file by its name; a skill must not hold credentials,holds what looks like a credential or a live endpoint (<rule>); use a placeholder,a file name looks like a credential (<rule>)andthe folder name looks like a credential (<rule>). They name the rule, never the value. - One finding at a time. The scan runs before anything else is read. While it finds something, no other problem of the package is shown; they appear once the finding is fixed.
- No allow-list. The scan cannot be switched off for a line (there is no
factory:allow-secrethere). - Pinned packages too. A pinned package that trips the scan no longer loads. Fix the text, give the package a new version and pin again.
Pinned versions and integrity#
A pin ties one skill version to the exact content you looked at. If the content changes later, the skill stops being usable until you decide again.
What the digest covers: every file in the package folder (path and raw bytes), in a fixed order. Changing one byte, adding, removing or renaming a file changes it.
.DS_Store, file modes and empty folders do not count. Line-ending conversion changes the bytes, so a Git checkout with CRLF can give another digest than a tarball.Trust by source:
Source Trust Can be pinned Data folder, skills.rootsapproved yes, by you with scf skills pinApproved release in skill-releases/approved (source catalogue)not needed: the release lists the digest Built-in builtin yes, pinned for you when the server starts Repository unapproved never The lock file is
skills.lock.jsonin the data folder. It holds the digest for eachid@version. Listing skills and the health line only read it.To pin: run
scf skills, check the package, copy its digest, then runscf skills pin <id@version> <digest>. Pinning a version that is already pinned with the same digest changes nothing.scf skills pin --builtinpins the built-in skills; the server does this at start as well.scf skills unpin <id@version>removes a pin.Pin states in
scf skills:pinned(matches),unpinned(not approved yet),mismatch(changed since the pin) andunverified(the lock cannot be read).A mismatch means the content differs from what you approved. Health and
scf skillsshow both digests, and the skill cannot be selected. There are two ways out: give the changed package a new version (the new version starts unpinned), or check the package and runscf skills pin <id@version> <new digest> --replace. A built-in skill is never re-pinned automatically; a release that changes one must change itsversion.An unreadable lock (bad JSON, wrong version, a symlink or a folder in its place) makes every approved and built-in skill
unverified, and nothing is pinned or unpinned. Health showsskill lock. Fix or deleteskills.lock.jsonin the data folder, then pin again. The server still starts.Runs must name the exact version (
id@version). A newer or older version is never used instead. Repository skills can never be selected for unattended runs.Limit: the lock detects a change after the pin, not a bad first copy. Anyone who can write the data folder can also change the lock.