# Defining a Contribution Kind


The contribution kinds are not a fixed enum. A kind is a compile-time function from a
`(:provides ...)` clause to a contribution value, registered under a keyword. The
kernel defines its entire vocabulary this way, with the same macro you would use to add
one. There is no privileged set of built-in kinds and a separate plugin path for the
rest; `:tool` and a kind you write yesterday are registered identically.

## The macro

```lisp
(defcontribution-kind kind (extension-id form) &body body)
```

The body is a quasiquote returning the form that constructs your contribution;
`defcontribution-kind` registers it as the form-compiler for `kind`. When `defextension`
parses a `(:provides ...)` block it dispatches on each clause head to the matching
compiler. A clause whose head has no registered compiler signals
`unknown-contribution-kind`, so a kind exists exactly when its compiler is registered.

The compiler runs at macroexpansion of `defextension`, not at install. Its job is
narrow: read the clause syntax, return code that builds one contribution object. The
installing and retracting happen later, through methods on that object.

## The shape

A kind is three pieces: a contribution class, an install/retract method pair
specialized on it, and the `defcontribution-kind` compiler that builds it from a
clause. A synthetic `:banner` kind that registers a startup line:

```lisp
(defclass banner-contribution (kli/ext:contribution)
  ((text :initarg :text :reader banner-text)))

(defmethod kli/ext:install-contribution
    ((protocol kli/ext:extension-protocol) (c banner-contribution) context)
  (declare (ignore context))
  (register-banner protocol (banner-text c))
  (push c (kli/ext:protocol-installed-contributions protocol))
  c)

(defmethod kli/ext:retract-contribution
    ((protocol kli/ext:extension-protocol) (c banner-contribution) context)
  (declare (ignore context))
  (unregister-banner protocol (banner-text c))
  (setf (kli/ext:protocol-installed-contributions protocol)
        (remove c (kli/ext:protocol-installed-contributions protocol)))
  c)

(kli/ext:defcontribution-kind :banner (extension-id form)
  (destructuring-bind (_ text) form
    (declare (ignore _))
    `(make-instance 'banner-contribution
                    :kind :banner :text ,text :source ',extension-id)))
```

Once those three forms load, a `(banner ...)` clause compiles inside any extension:

```lisp
(defextension welcome
  (:provides
   (banner "kli ready.")))
```

The clause head is matched by name, so `banner` works unqualified in the author
package; the helpers it expands into (`register-banner`, the `kli/ext` symbols) are
ordinary functions and stay package-qualified.

## The kernel does exactly this

`:theme` is the same five-part shape, in tree:

```lisp
(defcontribution-kind :theme (extension-id form)
  (destructuring-bind (_ name theme-form) form
    (declare (ignore _))
    `(make-theme-contribution
      :name ',(normalize-extension-id name)
      :theme ,theme-form
      :source ',extension-id)))
```

The `:theme` kind ships with its own `theme-contribution` class, a constructor, and the
install/retract pair beside it. Nothing about it is special: it is a self-contained
domain kind that a subsystem registers when it loads.

The core kinds the protocol appears to "know" are the same. They are
`defcontribution-kind` forms too: `:effect`, `:method`, `:tool`, `:capability`,
`:live-object`, `:contract`, and `:grant`. `:method` compiles to a
`make-method-contribution` that carries a generic-function name, qualifiers,
specializers, and body; its retractor is `remove-method`. `:tool` compiles to a
`make-tool` wrapped in a contribution. The kernel reaches for the macro you do.

The set stays open across the tree. Each subsystem registers its own kinds at load:
`:command`, `:keybinding`, `:event-type` and `:event-handler`, and the terminal-UI kinds
`:message-renderer`, `:status-slot`, and `:widget`. A kind lives wherever its domain
lives, not in a central registry of permitted types.

## The reversibility contract

A kind is real only when its `install-contribution` and `retract-contribution` are
symmetric on `(extension-protocol, your-contribution)`. The `:theme` pair is the
minimal correct form: install registers the theme and pushes the contribution onto
`protocol-installed-contributions`; retract unregisters by name and removes the
contribution by identity. That symmetry is not decoration. Deactivation walks the
contributions an extension recorded at install and calls `retract-contribution` on each
(see [Lisp extension anatomy](/kli/extend/lisp-extensions/anatomy)), so a kind whose
retract does not undo its install leaks on every `/disable` and `/reload`.

Three rules keep a custom kind honest:

- **Register and unregister the same name.** Whatever install files under a key, retract
  removes under that key.
- **Record the contribution, drop it by identity.** Push on install, `remove` the same
  object on retract, so deactivation has an exact list rather than a guess.
- **Keep state with the contribution or in protocol storage, never in a global.** A
  slot on the contribution (`banner-text` above) or
  `(kli/ext:ensure-protocol-storage protocol KEY constructor)` dies with the protocol; a
  `defvar` outlives retraction and leaks across reloads.

The `:effect` kind threads its state through `contribution-state` because it has no
class of its own; a kind with a class keeps state in slots, as `:banner` does.

## Related

- [Contribution kinds](/kli/extend/lisp-extensions/contribution-kinds) — the kinds you
  reach for before defining one of your own.
- [Lisp extension anatomy](/kli/extend/lisp-extensions/anatomy) — install as a recorded
  transaction, and why every kind retracts.
- [Extensions all the way down](/kli/concepts/extensions-all-the-way-down) — why the kind
  vocabulary being contributed the same way is the whole point.
- [The live image](/kli/concepts/the-live-image) — why a running kli can register a new
  kind and start compiling clauses against it without a restart.
