User guide: contents

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#

StageWhat the Foundry doesDetail
ProfileReads the repository for evidence of its technology.Skill catalogue
CataloguePlanner steps with skills: catalog get a short list of approved or built-in skills with a matching pin that pass coder resolution.Skill catalogue
RequestThe final plan ends with one SKILL_REQUEST: line.Skill request
ResolutionThe ids become exact id@version plus their dependencies, and are checked under skills.unresolved after the plan gate.Skill selection, Missing and conflicting skills
LockThe first agent step writes skill-lock.json. It is checked again before every agent session.Skill lock of a run
LoadingA Claude session gets one <foundry-skills> block; a resumed session gets a one-line reminder.Skills in Claude sessions
Review checksSteps 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. include makes 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. include is 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#

  1. Make a folder <data folder>/skills/<id>/, or one under a folder of skills.roots. Optional sub-folders are references, scripts, assets and evals.

  2. Write SKILL.md: frontmatter with name (the same as the id) and description (at most 1024 characters), then the instructions. At most 64 KiB. Only the description and the instructions reach a session.

  3. 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: low

    More fields: detectors, connectors, tool_profile, provider, composable (see Complementary providers). A provider block says that the skill needs a vendor: provider: {name: acme, products: [{name: kafka, version: "3.7"}], tools: [kafka-admin]}. name is 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 and scf skills show it (see Skill catalogue). The eleven built-in packages in skills/ (typescript, java, spring-boot, nodejs, rest-openapi, kafka, ibm-mq, cassandra, oracle, integration-testing, small-changes) are examples; spring-boot shows a dependency (on java).

  4. Optional: REVIEW.md, a few short checks for a reviewer, at most 8 KiB. The package needs the reviewer role or no roles.

  5. Versions are MAJOR.MINOR.PATCH. Any change to a pinned package needs a new version, or pin --replace.

  6. Pin it: run scf skills, check the package, copy the digest, then run scf skills pin <id@version> <digest>.

  7. Evidence for a new version: see Evidence to promote a skill version.

  8. No credentials and no live endpoints: see Credentials, endpoints and connectors.

Context budgets#

LimitDefaultWhen it is passed
catalogue.max_tokens2000The lowest-ranked skills are dropped, then descriptions are shortened.
selection.max_skill_tokens5000The skill is refused as too-large.
selection.max_skills / selection.max_tokens6 / 15000The skill is refused as over-count / over-budget.
review.max_tokens / review.max_skill_tokens3000 / 1000The 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 with skills.repository: true). Personal .claude and .codex folders are never scanned.
  • Releases. A folder in the data folder or in skills.roots is a catalogue release: it holds catalogue-release.json (schema: 1, catalogue, version, createdAt, source with repository and a full commit, and skills with id, version and digest). It loads only if its catalogue, version and digest are listed in skills.releases.approved, and its packages match the manifest exactly. Otherwise the whole folder is refused (root-refused) and none of its packages load. scf skills shows a RELEASE line 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 in skills.releases.approved is loaded, as the source release <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 to id@version and digest still resumes after a newer version is published and approved, and scf skills release verify and 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, and scf skills prints a NOTE for it (not a problem). Publish and discovery never delete a release, so every approved version is kept. A skills.roots entry that points inside skill-releases/ is ignored with a NOTE; list the release in skills.releases.approved instead.
    • Approval is the pin. An approved, verified release lists the digest of each package, so its packages are selectable with those digests (source catalogue, trust approved, pin pinned). They need no entry in skills.lock.json and no scf skills pin. A hand pin for the same id@version with another digest is still a mismatch, and the stricter rule wins. Data folder and skills.roots packages still need their own pin. A run locked to an older approved release keeps its exact id@version and digest, so it still resumes after a newer release is approved. A run lock written before this change, with source admin for the same package, still verifies.
    • List. scf skills release list reads only. It prints RELEASE <catalogue>@<version> <digest> <state> <n> skills, locked by <k> runs (or not locked by any run) for each stored release, where the state is approved, unapproved, mismatch or invalid. An approved release line ends with (source catalogue). It prints no PIN lines. It ends with OK <n> releases or No releases are published, and exits 1 when the store cannot be read in full.
  • Release manifest 2. catalogue-release.json may also say schema: 2. Each skill then lists every file of its package (files: path, sha256, bytes), its provenance (source, repositoryKey, commit, path, trust and an optional review), its licences, its attribution files (kept in <folder>/.attribution/<id>/), its evaluation (suite, record, and verdict passed, failed or not-run) and an optional revoked (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/ (only catalogue-release.json and .DS_Store are allowed besides). A link is refused. Any failure refuses the whole folder (root-refused), whatever allow_unreleased says.
    • Revoked skills. A skill with revoked in an approved schema-2 release is never loaded, from any root. A locked run that uses it stops with skill 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 in skills.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 verify shows 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 prints VERIFIED <catalogue>@<version> <digest> <n> skills (exit 0), or PROBLEM … (exit 1). It then prints APPROVED or NOT-APPROVED against skills.releases.approved. The reason never shows a path or file content.
  • Signed approvals. Set skills.releases.keys to require a trusted signature: up to 10 entries {id, ed25519}, where ed25519 is the base64 of the 32-byte raw Ed25519 public key. Once the list is not empty, every skills.releases.approved entry must also have key (an id from keys) and signature (base64 of 64 bytes). The signature covers the text scf-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 with allow_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, or key without signature (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 (use chmod 600). It prints APPROVE <catalogue> <version> <digest> key <id> signature <b64>; paste the key and signature into 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 verify and scf skills release list print SIGNED <key>, UNSIGNED or BAD-SIGNATURE for 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 .claude or .codex is refused when the config is read. scf skills release adopt [<catalogue>@<version>] copies each approved release that is missing from skill-releases/ (or only the one you name). It checks the folder with the same rules as verify, requires a valid signature from skills.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> or MISSING-ARTIFACT. A refusal writes nothing, and the exit code is 1 on any refusal (or on a named entry with MISSING-ARTIFACT). The command opens no network connection.
    • Missing release. An approved release that is not in skill-releases/ is a scf skills problem (root-missing) that says to run scf skills release adopt. A run never fetches it. A skills.roots folder that holds the exact approved release (same digest, state approved) does not trigger the problem.
  • Local development. To try an unpublished catalogue, set skills.releases.allow_unreleased: true. Folders without an approved release then load, and scf skills prints a NOTE for each. Keep it false (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 with skills.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_agents off. Steps with skills: off or skills: catalog get 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.

StateNew plansLocked runs (a run that already has a skill lock)
activeselectedselected
deprecatedselectedgoes on; the run log has a skill lifecycle: note
quarantinednot selected (ineligible)stops with skill integrity: unless allow-run was given for that run
retirednot selected (ineligible)goes on; the run log names the replacement
revokednot selected (ineligible)always stops with skill integrity: … is revoked
  • Commands. scf skills lifecycle lists the skills that are not active, with their state and replacement. scf skills lifecycle <id@version> prints the history of one skill: one #seq line per change, and a release line 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)), one RUN <id> locks kafka@1.0.0: <effect> line per run whose skill lock holds the version, then OK. scf skills allow-run <run-id> <id@version> --reason <text> lets one run go on with a quarantined version; it prints ALLOWED <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/lifecycle and POST /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 revoked version never changes again.
  • Replacement. Optional for deprecated, quarantined, retired and revoked. 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-run is refused for such a key.
  • Releases win. A revoked skill in an approved release (see "Revoked skills" above) overrides any local state. The listing says by 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 skills prints PROBLEM 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 in seq, a from that does not match the state, a change the rules refuse (for example a hand-written revoked → 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 active again. 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#

StepFlowWhat it does for skills
plan, revise_planissue-gitflow, issue-planGets the catalogue (skills: catalog) and writes the SKILL_REQUEST line.
plan_reviewissue-gitflow, issue-planReview checks for the draft request (skill_role: reviewer).
risk_gateissue-gitflowPlan gate: the request is validated and resolved under skills.unresolved. The run stops here on a problem.
post_planissue-planPlan gate: the same check. Posts "Required skills" and writes the plan record.
plan_checkissue-code-dailyCompares the plan comment with the plan record and carries the request.
implementissue-gitflow, issue-code-dailyThe first agent step: writes skill-lock.json and loads the block.
fix_guard, fix_tests, address_review_N, docsissue-gitflow, issue-code-dailyThe lock is checked again. A resumed session gets a reminder, a new session the full block.
review_1, review_2issue-gitflow, issue-code-dailyReview 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:

  1. The Skills card of the run page (Skills on the run page).
  2. The stop reason of the run.
  3. The log lines: skill catalogue: …, skill context: loaded|reloaded|reused …, ! skill context: rejected (…), ⚠ skill … and · skills: off for this flow.
  4. scf skills: pin state, both digests and problems.
  5. The health line: skill problems and skill lock.
The stop reason starts withWhat to do
planning failed: the skill request of the plan is not validstart 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 quarantinedan 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 budgetraise 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 validcorrect 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 checkedresume the run; if it stays, plan the issue again
skills not resolved: the plan records of this issue are not validthe 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:

CodeKindWhat to do
unknownunknownPut it in the skills folder of the data folder, pin it with scf skills pin, then resume the run
excludeduntrustedTake it off skills.selection.exclude or skills.catalogue.exclude, then resume the run
unapproveduntrustedCopy it to an administrator skills folder, pin it, then resume the run
unpinneduntrustedCheck the package, run scf skills pin <id@version> <digest>, then resume the run
mismatchuntrustedRun scf skills to see both digests, then pin it with --replace or give it a new version
unverifieduntrustedFix or delete skills.lock.json, pin again, then resume the run
roleuntrustedRelease a version that allows the role, or plan without it
too-largeoversizedRaise skills.selection.max_skill_tokens or shorten the skill
over-countoversizedRaise skills.selection.max_skills or request fewer skills
over-budgetoversizedRaise skills.selection.max_tokens or request fewer skills
dependency-unavailablemissing, 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-versionmissingInstall a newer version of the dependency and pin it, then resume the run
dependency-cyclemissingRemove the cycle from the skill packages, then resume the run
conflictconflictPut one of them on skills.selection.exclude
ineligibleuntrustedThe version is quarantined, retired or revoked (or the lifecycle record cannot be read). Use the replacement or another skill, then plan again
blockedconflictA 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 of roots in order, then the built-in skills, then the repository's skills (only when repository: 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 .claude or .codex path segment. The registry also refuses ~/.claude, ~/.codex, CLAUDE_CONFIG_DIR, CODEX_HOME and 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: medium or high. No run connects to anything. A skill that names one stops the run at the plan gate, or gets a warning under degrade; see Skills, tools and connectors.
  • Refused: credential files by name (.env, *.pem, id_rsa, *.key, *.p12 and similar; .example, .sample and .template copies 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 words host, hostname, server, broker or db, and names ending in .example, .test or .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>) and the 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-secret here).
  • 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:

    SourceTrustCan be pinned
    Data folder, skills.rootsapprovedyes, by you with scf skills pin
    Approved release in skill-releases/approved (source catalogue)not needed: the release lists the digest
    Built-inbuiltinyes, pinned for you when the server starts
    Repositoryunapprovednever
  • The lock file is skills.lock.json in the data folder. It holds the digest for each id@version. Listing skills and the health line only read it.

  • To pin: run scf skills, check the package, copy its digest, then run scf skills pin <id@version> <digest>. Pinning a version that is already pinned with the same digest changes nothing. scf skills pin --builtin pins 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) and unverified (the lock cannot be read).

  • A mismatch means the content differs from what you approved. Health and scf skills show 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 run scf skills pin <id@version> <new digest> --replace. A built-in skill is never re-pinned automatically; a release that changes one must change its version.

  • 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 shows skill lock. Fix or delete skills.lock.json in 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.

Improve this page on GitHub