---
title: Register your own model family
description: Teach the catalog an architecture it does not know, from the snapshot layout through identity matching to runnable commands.
---

<!-- CERTIFICATION: registration from a consumer TU outside the shipped library follows the public
     family-authoring headers; it was not compiled against the shipped package. -->

The catalog covers the common architectures; your in-house model is not in it. Modelverse's answer is self-registration: a family is a translation unit that pushes a `ModelRegistration` into the shared registry at static-initialization time, and every built-in family registers exactly this way. There is no central list to edit. You compile one registration TU into your program against the public headers, and `list`, `info` and the loaders treat your family like any other. This page is the contract at full depth; for the guided walk from a hand-written checkpoint to a running family of your own, start with the [Adding your own model tutorial series](../getting-started/own-model/01-a-model-from-scratch.md); this page's worked example is a text-generation family, the detail-heavy case the tutorial deliberately avoids.

## What a snapshot must contain

A model is a directory of files, and identity resolution reads only its configuration:

- `config.json` with `model_type` and `architectures`; these two values are the identity keys a registration matches on.
- The processor files the model needs (`tokenizer.json` and friends for text; preprocessor configs for image and audio).
- The weights (`*.safetensors`, ONNX, or GGUF); untouched at identity time.

A repo that ships no `config.json` can still be matched: a registration may carry a `probe_snapshot` function that recognizes the family from the file set alone and answers the `model_type` to route as.

## The registration, identity first

One complete TU. With only this much, `list` shows the family (identity-only), `info` resolves your checkpoints to it, and every runnable command refuses precisely, naming what the family provides:

```cpp title="my_model_family.cpp"
#include "clika_modelverse/models/registration.h"

namespace {

using namespace clika_modelverse;

// The identity template: what a resolved checkpoint of this family IS.
// The registry fills architecture, model_type and variant from config.json.
class MyModel final : public Model {
 public:
    std::string_view family() const noexcept override { return "my-model"; }
};

// The identity keys, matched against config.json.
constexpr std::string_view kModelTypes[]    = {"my_model"};
constexpr std::string_view kArchitectures[] = {"MyModelForCausalLM"};
// The Hugging Face pipeline tags the family's checkpoints carry, by the
// constants of models/base/pipeline_tags.h (the registrar refuses a literal
// outside that vocabulary).
constexpr std::string_view kPipelineTags[]  = {pipeline_tags::kTextGeneration};

// Static-init self-registration: constructing the registrar is the whole hookup.
const ModelFamilyRegistrar kRegistrar{
    [] {
        ModelRegistration registration{};
        registration.metadata.family = "my-model";
        registration.metadata.vendor = "my-org";
        registration.metadata.pipeline_tags = kPipelineTags;
        registration.metadata.description = "the in-house my_model language models";
        registration.metadata.input_combos = kTextOnlyCombos;
        registration.metadata.output_combos = kTextOnlyCombos;
        registration.hf_model_types = kModelTypes;
        registration.hf_architectures = kArchitectures;
        registration.factory = make_model<MyModel>();
        return registration;
    }(),
};

}  // namespace
```

Compile that TU into any program that links `Modelverse::modelverse` (the command-dispatch functions in the library serve programs that embed them; the shipped `clikart-cli` binary knows only the built-in families). An incomplete declaration (no family name, no vendor, no pipeline tag or one outside the Hugging Face vocabulary, no modalities, no identity factory) is refused at process start, not papered over downstream.

```text
my-model  identity-only
  *  Input Modalities: text
  * Output Modalities: text
  *          Commands: none (identity-only)
  *            Vendor: my-org
```

## Making it runnable

Two more fields turn identity into execution:

- **`build_generative`** materializes the runnable model from a resolved snapshot: read the config, adapt the checkpoint's tensor names where they differ from what your forward expects (`ClikaRT::io::TensorsAdapter`, rename-at-lookup, no payload copies), assemble the forward from the `clika_modelverse/modules/` building blocks (attention, decoder stack, projections, rope cache) or your own `nn::Module`, and return a `GenerativeModel` wrapping it with the snapshot's tokenizer and generation defaults. The GGUF twin, `build_generative_gguf`, receives the already-loaded file so identity read and weight bind share one mapping.
- **`cli`** declares the family's commands. The battery builders compose the standard text surface in one line, and with it your family's `prompt`, `serve` and `bench` behave exactly as the tutorial documents them:

```cpp
        registration.cli = CliSurface{batteries::cli::llm_cli, batteries::cli::llm_cli_verbs()};
        registration.factory = make_model<MyModel>();
        registration.build_generative = build_my_model;
```

A family wanting a different surface composes its own `FamilyApp` from the same public builders (`batteries/cli/prompt.h`, `serve.h`, `bench.h`), one verb per builder; the advertised verb list and the App are pinned to agree by the registration contract.

Custom behavior around an EXISTING family needs none of this: [Add your own node to a model pipeline](add-a-pipeline-node.md) composes your logic with any catalog model. The complete field-by-field contract, including the speech, embedding and reranking factories, is `clika_modelverse/models/registration.h` in the installed headers.

## The nearest family to start from

A checkpoint no family claims is refused by name: `clikart-cli info <source>` prints the `model_type` and `architectures[0]` it read and that no template serves them, and `clikart-cli list` prints every family with the keys it claims, so the family whose definition the checkpoint's `config.json` shares is the one to read first. A composite whose `config.json` carries a nested `text_config` naming a registered family composes that family's text encoder through the text-backbone seam (`build_text_backbone` on the registration), with no forward of its own for the tower. A decoder driven by the embeddings of an encoder you wrote has no such seam in this release: its forward is written from the modules, as the section above does for a text-generation family.
