nextv2.0.0
Configuration

Add-ons and collections

The files of a Kraft add-on and its collection, addons.yaml and addons.lock, the naming rules, what an add-on may set, and when an add-on loads, updates and is restored.

A Kraft add-on is a directory of chains, library components, skills and agent profiles. A collection is a git repository or a directory that lists add-ons. Kraft ships no collection and has no directory of them, so you add each collection by its address, in one of the forms under Collection sources. config/addons.yaml names the collections an instance knows and the add-ons it installed, and config/addons.lock records what each one resolved to.

Share chains with add-ons walks through publishing and installing one. The kraft admin addon verbs are in Admin verbs.

A collection's layout

acme-kraft/
  .kraft/collection.json
  addons/
    release/
      .kraft/addon.json
      library.yaml
      profiles.yaml
      chains/ship.yaml
      skills/release-notes/SKILL.md

.kraft/collection.json sits at the root of the collection. Each add-on is a directory inside it with its own .kraft/addon.json.

Both manifests are JSON. Kraft ignores a top-level key it does not read, so a manifest written for a newer Kraft still loads, and kraft admin addon validate lists each such key as a warning. The JSON Schemas are published for editors:

collection.json

.kraft/collection.json
{
  "$schema": "https://kraft-sdlc.dev/schemas/collection.schema.json",
  "name": "acme",
  "description": "ACME's Kraft chains and review methods",
  "owner": { "name": "ACME Platform", "email": "platform@acme.dev" },
  "addons": [
    { "name": "release", "source": "./addons/release", "description": "Release chains" }
  ]
}
FieldTypeRequiredMeaning
namenameyesThe collection's id: its key in addons.yaml, and the part after @ in an add-on id.
descriptionstringnoWhat the collection holds.
ownerobjectyesname and email, both required, and an optional url, which is an absolute https:// URL.
addonslistyesAt least one entry. No name is listed twice, and no entry's source is inside another's.
addons[].namenameyesThe same name the add-on's own addon.json declares.
addons[].sourcestringyesThe add-on's directory: ./ and a relative path, with no . or .. segment, no trailing /, and not inside .kraft/.
addons[].descriptionstringnoWhat the add-on holds.

An add-on lives in its collection's own repository. A source that is not a relative path is refused.

addon.json

addons/release/.kraft/addon.json
{
  "$schema": "https://kraft-sdlc.dev/schemas/addon.schema.json",
  "name": "release",
  "version": "1.0.0",
  "description": "Release chains",
  "requires": { "kraft": "2", "harnesses": ["claude"], "profiles": ["strong"] }
}
FieldTypeRequiredMeaning
namenameyesThe add-on's name and its default namespace. Never kraft.
versionstringyesMAJOR.MINOR.PATCH, with an optional pre-release suffix such as 2.0.0-rc.1. No build metadata.
descriptionstringnoWhat the add-on holds.
authorobjectnoname and email, both required when author is given, and an optional url.
homepagestringnoAn absolute https:// URL.
licensestringnoThe license's name.
requiresobjectyesWhat the add-on needs from the instance. An unknown key inside it is refused: requires.<key> is not understood by this Kraft; upgrade Kraft.
requires.kraftstringyesMAJOR or MAJOR.MINOR. See Which Kraft an add-on runs on.
requires.harnesseslist of namesnoHarness profile ids the instance's harnesses.yaml defines. The add-on's tasks name only these.
requires.profileslist of namesnoAgent profiles of the instance that the add-on's tasks select by bare name.

Which Kraft an add-on runs on

requires.kraft names the oldest Kraft the add-on runs on, and that version's major is the only one it runs on.

requires.kraftRuns on
"2"Any 2.x.
"2.4"2.4.0 or later, below 3.0.0.
  • Only the release part of Kraft's version counts, so a release candidate of 2.4.0 satisfies "2.4".
  • An add-on whose requires.kraft does not hold is refused at validate, install and update, and left out at load.
  • A source build of Kraft, such as a checkout, skips the check. validate says so.

An add-on's files

Kraft reads these files of an add-on directory and nothing else. Each is optional, and an add-on with none of them is refused.

PathHolds
library.yamlThe tasks, steps, nodes and steering sections, as in the local file.
chains/<name>.yamlOne chain per file, as in config/chains/. The chain is named by its id, or by <name> when it has none.
skills/<name>/SKILL.mdOne method per directory. Kraft reads the SKILL.md and no file beside it.
profiles.yamlA profiles: section, in the shape of agent profiles. A harnesses key is refused.

Each <name> is a name. A file that looks like part of the layout and is not, such as chains/Ship.yaml or library.yml, is not read, and validate warns about it. Any other file in the directory never reaches Kraft.

Size and content limits

An add-on over one of these limits is refused, and the message names the file.

LimitValue
Each YAML file1 MiB, and 20,000 YAML nodes with each use of an alias counted
Each JSON manifest256 KiB
Each SKILL.md256 KiB
Files in one add-on500
One add-on in all20 MiB

Every file is a regular file holding UTF-8 text. These are refused:

  • a symlink or a git submodule where a layout file or folder would be;
  • a Git LFS pointer in place of the file;
  • a file or a manifest string that carries a control character other than newline and tab, or a Unicode format character such as a bidi control or a zero-width character;
  • in a directory collection, a layout file with a second hard link.

What an add-on may set

A policy: block in an add-on, at any level, takes only these keys:

KeyLimits
time_cap_minutes, total_time_cap_minutes, timeout_minutesHow long.
budget_usd, token_budgetHow much.
max_attemptsHow many times.
deny_toolsWhich tools the permission gate denies.

policy.yaml defines each key. A limit above the instance's maxima: is refused.

An add-on is refused, with the file and key named, when it holds any of these:

  • a task of kind: subprocess;
  • any other policy: key, such as allowed_tools, grants, sandbox or allowed_harnesses;
  • a harness: that requires.harnesses does not list, in a task or in an agent profile's fallback;
  • a harnesses section in profiles.yaml;
  • a qualified extends, steering or profile that names anything but the add-on itself, an agent profile's fallback included;
  • a file nested deeper than its parser reads;
  • a skill that names another installed add-on.

builtin and forge tasks are allowed, mr.merge included.

Names

A name is a lowercase letter, then lowercase letters, digits, _ or -. A collection name, an add-on name and an alias are at most 64 characters.

  • An add-on id is <addon>@<collection>, such as release@acme.
  • A namespace is the add-on's name, or the alias it was installed under with --as.
  • A qualified name is <namespace>:<name>. Everything an add-on declares is addressed that way from outside it.
WhatQualified reference
A chain--chain release:ship, default_chain: release:ship
A task, step or nodeextends: release:implementer
A steering profilesteering: [release:house-style]
A skillskill: release:release-notes
An agent profileprofile: release:careful

Inside an add-on's own files, a reference resolves like this:

KeyA bare name meansQualified
extends, steering, skillThe add-on's own declaration.The add-on's own name only. A skill may also name kraft: or another tool's plugin.
profileThe instance's agent profile.The add-on's own name, for a profile in its profiles.yaml.
harnessThe instance's harness profile.Never qualified.

A reference an add-on makes to its own name follows the alias, so an add-on installed under another namespace works unchanged.

These rules keep namespaces apart:

  • kraft is reserved. No add-on is named kraft and no alias is kraft.
  • A namespace is unique on the instance, a disabled add-on's included. A second add-on of the same name needs an alias.
  • A namespace never equals the id of an entry in repos.yaml.
  • A local chain id, library.yaml key or harnesses.yaml agent profile id never contains :.
  • A bare name in local configuration never resolves to an add-on's declaration.

A skill: whose qualifier is not a loaded add-on's namespace or kraft is handed to the agent as another tool's plugin skill. A qualifier that is an installed add-on that did not load is an error.

addons.yaml

config/addons.yaml
collections:
  acme:
    git: https://github.com/acme/kraft-addons.git
    ref: main
  local:
    path: /home/you/src/my-addons
addons:
  release@acme: true
  review-methods@acme: { auto_update: true }
  mobile@acme: { as: acme-mobile }
  legacy@acme: false
  tools@local: true

Kraft never seeds this file: kraft admin addon collection add creates it. You may edit it by hand. A key Kraft does not read is refused at every level, with the key named.

collections

Each key is the name the collection's collection.json declares.

FieldTypeDefaultMeaning
gitstringone of git or pathA URL git clones. See Collection sources.
pathstringone of git or pathAn absolute directory in normal form, read in place. ~ is not expanded.
refstringthe remote's default branchA branch, tag or commit of a git collection. A value YAML reads as a number needs quotes. Not allowed with path.
auto_updatebooleanfalseEvery add-on installed from the collection updates itself. true is refused with path.

addons

Each key is an add-on id whose collection is under collections. The value is true, false or a map. true and false are short for {enabled: true} and {enabled: false}.

FieldTypeDefaultMeaning
asnamethe add-on's nameThe namespace the add-on is installed under.
enabledbooleantruefalse keeps the add-on installed and not loaded.
auto_updatebooleanunsetThis add-on updates itself. Refused when its collection sets auto_update: true, and for an add-on of a directory collection.

Collection sources

kraft admin addon collection add SOURCE writes one of these:

SOURCEWritten as
A directory that existspath:, as its absolute path.
owner/repogit: https://github.com/owner/repo.git
Anything elsegit:, as given.

git: takes these forms and no other:

  • https://host/path, with no credentials in it;
  • ssh://host/path or ssh://user@host/path;
  • file:///path;
  • user@host:path.

http://, git:// and a bare owner/repo are refused in the file. A collection already added from another source is refused until it is removed.

addons.lock

Only kraft admin addon writes the lock. Each installed add-on has one entry:

config/addons.lock
# Written by `kraft admin addon`. Do not edit.
lock_version: 1
addons:
  release@acme:
    namespace: release
    ref: main
    git: https://github.com/acme/kraft-addons.git
    commit: f4a11422cc8377a6ae61353897431dafa72b8ef5
    tree: de1b745c4020ff2d468c14451da2f28a5fb56207
    source: ./addons/release
    digest: sha256:f915f6266d319e9c0bc2bb2483683d74f307839138258901919e7d071fd37da4
    version: 1.0.0
    updated_at: '2026-10-10T13:23:58.690123Z'
FieldMeaning
namespaceThe namespace the add-on was reviewed and installed under.
refThe collection's ref when it was locked. null for the default branch and for a directory collection.
gitThe collection's git URL when it was locked. null for a directory collection.
commitThe collection's commit. null for a directory collection.
treeThe git tree of the add-on's directory in that commit. null for a directory collection.
sourceThe add-on's source in collection.json.
digestThe digest of the add-on's files.
versionThe add-on's version.
updated_atWhen the entry was written.

What loads

An add-on loads when it has an enabled entry in addons.yaml and an entry in the lock. Kraft reads it from the store the lock names and never fetches a collection to load the library.

StateWhat Kraft does
Listed, with no lock entryNot loaded: is not installed; run `kraft admin addon install` .
DisabledNot loaded. It keeps its namespace.
Locked, and not listedNot loaded. The next accepted install or update, or an uninstall, drops the lock entry.
The alias, or the collection's ref or git URL, differs from the lockLoaded under the locked namespace and commit, until kraft admin addon update applies the change after a review.

An add-on that fails one of these checks is left out by itself, and the rest of the library loads:

  • its store is missing and not restored yet, or could not be restored;
  • its files do not match the locked digest;
  • its requires.kraft does not hold for the running Kraft;
  • harnesses.yaml no longer defines a harness or agent profile it requires;
  • its namespace is now a repository id;
  • one of its own files no longer loads;
  • one of its limits is above a maxima: lowered since it was installed.

GET /api/health names each one under invalid_templates as add-on <id> with the reason, and answers degraded. A chain of yours that extends a left-out add-on stops resolving. The add-on is still checked for updates.

An addons.yaml or addons.lock that does not read loads no add-on at server start. On a reload the server keeps the add-ons it has. Either way invalid_templates names addons.yaml with the reason.

Saving a policy.yaml, harnesses.yaml or repos.yaml that would leave a loaded add-on out is reported before it is applied: would leave add-on release@acme out: ....

Install and update

An install or update fetches the collection, checks the add-on, checks the whole library with the add-on in it, and prints a review. It writes addons.yaml, the lock and the store only after the review is accepted. The fetch itself fills the collection's mirror under run/addons/mirrors/ before the review.

When an add-on has an update

CollectionAn update is waiting when
gitThe add-on's version differs from the lock's, higher or lower, or the collection's ref or git URL, or the add-on's alias, was changed by hand. A new commit at the same version is not an update.
directoryThe add-on's digest differs from the lock's, whatever its version, or its alias was changed by hand.
  • kraft admin addon install ADDON@COLLECTION --re-install takes the commit the ref names now, at an unchanged version too.
  • kraft admin addon update notes files that changed at an unchanged version: version 1.0.0 unchanged, but its files changed at <commit>; --re-install to take it.
  • An add-on its collection no longer lists stays on its locked version.

What refuses an update

A candidate is refused, and nothing is written, when:

  • it breaks a rule under An add-on's files;
  • its namespace is another add-on's, or a repository id;
  • the instance lacks a harness or agent profile it requires;
  • a chain it ships does not resolve;
  • a chain of yours that resolves now would stop resolving;
  • a default_chain or steering in repos.yaml, a chain in intake.yaml or a policy.yaml trigger, or an agent profile fallback in harnesses.yaml would stop resolving.

One add-on change runs at a time. A second one waits 5 seconds, then fails with another add-on change is running; try again. An update writes nothing when addons.yaml or addons.lock changed while its review was open: add-ons changed while you reviewed; run update again. Each message and its fix: Add-on problems.

The review

The review compares the candidate with the locked version, or with nothing on an install. It prints two lists, with every control and format character written out as \u{...}.

Reach is what can change how far a run goes:

  • a gate removed, or a gate's auto_review added or changed;
  • a change to chain_finalized, skippable, reject_to or artifact_required;
  • an mr.merge or mr.mark_ready step, with whether a gate comes before it;
  • a forge target the chain did not use before, or a forge step removed;
  • a task's harness, model, effort, profile or fallback changed, a fallback: [] removed among them;
  • an agent profile in profiles.yaml changed;
  • a node or step no longer read_only, or a task's scope changed;
  • a limit set, raised or removed, a fix loop's max_attempts among them, or a deny_tools entry dropped;
  • the collection's git URL changed;
  • the order of a chain's nodes, or of a node's steps, changed;
  • the installed version could not be read to compare with;
  • requires changed;
  • a lower version, or a move from a release onto a pre-release;
  • each reference in your own files that the add-on's namespace now captures.

Content is the rest: components added, removed or changed, each new or changed prompt and steering text, each agent profile added or removed, each skill as a diff, a lowered limit, and each new reference to another tool's plugin skill.

Both lists also cover each chain of yours that resolves through the add-on.

Auto-update

An add-on auto-updates when its collection sets auto_update: true, or its own entry does. The server runs it in the background after each start, and again once a day while it runs.

An auto-update applies an update only when its review has no Reach line. Anything else is held for a person, and the locked version keeps serving. It never does these:

  • install an add-on that has no lock entry;
  • take a newer commit at an unchanged version;
  • apply an alias, a ref or a collection git URL changed by hand.

Kraft does not fetch a collection none of whose installed add-ons auto-update, except to restore a missing store.

Each add-on's last outcome is kept in run/addons/status.json and shown by addon_updates in GET /api/health, as outcome and kind only: message stays in the file, because that endpoint needs no login. The add-on updates row of kraft admin doctor names the held and failed ones:

outcomeMeaning
appliedThe update was taken.
up to dateNothing to take.
heldA person must run kraft admin addon update. message, in the file and in doctor, says why.
failedkind is network, auth, refused or busy, and message, in the file and in doctor, says why.

A held or failed auto-update does not make health degraded. An entry is dropped once you apply the add-on's update yourself or uninstall the add-on.

What a work item pins

A work item's chain is copied at intake, with every prompt and steering text in it. Two things are read later, at each launch: skills and agent profiles. For each add-on the chain resolved through, the item records the add-on's id, commit, tree, source, digest and version, and reads that add-on's skills and agent profiles from that version until the item ends.

  • An update or an uninstall does not change a running item.
  • A chain revision resolves against the pinned versions.
  • The chain_loaded event carries the pins.

Restoring a store

A store named by the lock or by a work item's pin can be missing: after config/ is copied to another machine, after run/ is cleared, or after a newer lock is put in config/. Kraft restores it:

  • in the background after the server starts, and with each daily auto-update check;
  • in the background after a reload;
  • before a launch that reads a version its work item pinned.

A restore takes the locked commit from the local mirror, or fetches that commit from the collection's URL. A directory collection is copied again. The files are kept only if their digest is the locked one. A restore is not a review.

Until its store is back, an add-on is left out. Filing an item on one of its chains answers 503 with add-on <id> is being restored; retry. A work item whose pinned store cannot be restored stops.

The digest

The digest is sha256: and the SHA-256 of a manifest with one line per file the add-on holds, sorted by path. Each line is the path, the git mode (100644 or 100755) and the SHA-256 of the file's bytes, separated by NUL bytes and ended by a newline.

The same files give the same digest on every machine. kraft admin addon validate prints it, and the store keeps the manifest beside the files as .kraft-digest.

On disk

Add-on state lives under $KRAFT_HOME/run/addons/:

PathHolds
mirrors/<collection>-<hash>.gitOne bare git mirror per git collection. Kraft never checks out a working tree from it and never runs a hook.
store/<digest>/One add-on's files, read-only, shared by every install with the same content.
staging/A store being written.
status.jsonEach add-on's last auto-update.

The server deletes a store once the lock, the loaded library and every work item that has not ended no longer name it. kraft admin addon never deletes one.

  • Share chains with add-ons: publish a collection, install from one, and keep it current.
  • Admin verbs: every kraft admin addon verb and flag.
  • Security: what an add-on can and cannot change on an instance.
  • Doctor failures: the add-ons and add-on updates rows.
  • Add-on problems: the messages kraft admin addon prints when it refuses a collection or an add-on, and what validate rejects, with the fix for each.
Copyright © 2026