Register and stage models
DearDiary.Model and DearDiary.ModelVersion form a project-scoped registry on top of the run-tracking entities. A Model is the named entry that downstream serving code refers to (e.g. "fraud-classifier"); a ModelVersion is a concrete checkpoint with lineage back to the Iteration that produced it, an optional pointer at the artifact bytes in any configured storage backend, and a lifecycle DearDiary.Stage.
Versions transition through NO_STAGE → STAGING → PRODUCTION → ARCHIVED. Promoting a version to PRODUCTION automatically demotes whichever sibling was previously in PRODUCTION, preserving the "at most one production version per model" invariant.
Scaffold a project and an iteration
julia> project_id, _ = create_project("Fraud detection");julia> experiment_id, _ = create_experiment(project_id, DearDiary.IN_PROGRESS, "DT sweep");julia> iteration_id, _ = create_iteration(experiment_id);julia> create_parameter(iteration_id, "max_depth", 7);julia> create_metric(iteration_id, "accuracy", 0.96);
Save the trained model bytes as a Resource. Any serialisation format works; the registry stores only the byte payload and its lineage.
julia> checkpoint_bytes = rand(UInt8, 1024);julia> resource_id, _ = create_resource(experiment_id, "fraud-clf.jlso", checkpoint_bytes);
Register the model
A Model is a named entry: a stable identifier that persists across successive training runs and model versions.
julia> model_id, _ = create_model(project_id, "fraud-classifier");julia> get_model(model_id)DearDiary.Model ├ id = "3640e65e-9434-4250-ad89-b75b9c2ef37f" ├ project_id = "90b0d211-cfd2-4105-8b09-4939cb4b1dfc" ├ name = "fraud-classifier" ├ description = "" ├ created_date = 2026-10-19T16:14:00.492 └ updated_date = nothing
Register a version
A ModelVersion ties a Resource to the Iteration that produced it. The per-model version number is assigned on insert as one greater than the model's current highest version, and a uniqueness constraint keeps it distinct within the model:
julia> version_a_id, _ = create_modelversion(
model_id, iteration_id, resource_id,
"Decision tree, max_depth=7",
);julia> version_a = get_modelversion(version_a_id)DearDiary.ModelVersion ├ id = "ede466d3-f852-4273-8cf9-9986694d1175" ├ model_id = "3640e65e-9434-4250-ad89-b75b9c2ef37f" ├ version = 1 ├ iteration_id = "feeb46ce-d583-4dd3-b8b7-49f81451fe93" ├ resource_id = "2efc2143-4500-4edf-838b-dec0e65c03e1" ├ stage_id = 1 ├ description = "Decision tree, max_depth=7" ├ created_date = 2026-10-19T16:14:00.656 └ updated_date = nothing
A freshly registered version starts in DearDiary.NO_STAGE. Promote it through the lifecycle as evaluation results become available:
julia> update_modelversion(version_a_id, DearDiary.STAGING, nothing, nothing);julia> update_modelversion(version_a_id, DearDiary.PRODUCTION, nothing, nothing);Roll forward to a new checkpoint
Train another iteration, register a second version, and promote it to PRODUCTION. The previous production version is archived by the same update_modelversion call:
julia> iteration_b_id, _ = create_iteration(experiment_id);julia> create_parameter(iteration_b_id, "max_depth", 9);julia> create_metric(iteration_b_id, "accuracy", 0.974);julia> resource_b_id, _ = create_resource(experiment_id, "fraud-clf-v2.jlso", rand(UInt8, 1024));julia> version_b_id, _ = create_modelversion( model_id, iteration_b_id, resource_b_id, "Decision tree, max_depth=9", );julia> update_modelversion(version_b_id, DearDiary.PRODUCTION, nothing, nothing);
The previous production version is now archived:
julia> get_modelversion(version_a_id).stage_id == (DearDiary.ARCHIVED |> Integer)true
julia> get_modelversion(version_b_id).stage_id == (DearDiary.PRODUCTION |> Integer)true
Browsing the registry
get_modelversions returns the per-model history ordered by version ascending, so finding the current production checkpoint is a single filter:
julia> versions = get_modelversions(model_id);julia> production = filter(v -> v.stage_id == (DearDiary.PRODUCTION |> Integer), versions);julia> production[1].version2
The full lineage is reachable from version.iteration_id and version.resource_id:
julia> producing_iteration = (version_b_id |> get_modelversion).iteration_id |> get_iterationDearDiary.Iteration ├ id = "c3a4fb73-6fa1-44b2-b90c-48afcd05700c" ├ experiment_id = "8812345a-b33b-48d1-9449-4f86c5bef269" ├ notes = "" ├ created_date = 2026-10-19T16:14:01.024 ├ end_date = nothing ├ parent_iteration_id = nothing ├ status_id = 1 ├ error_message = "" ├ julia_version = "" ├ git_sha = "" ├ git_dirty = false ├ entrypoint = "" ├ project_toml = "" └ manifest_toml = ""
julia> get_parameters(producing_iteration.id)1-element Vector{DearDiary.Parameter}: DearDiary.Parameter ├ id = "252b68f0-8640-4dfe-ac1e-b675ed5fd93b" ├ iteration_id = "c3a4fb73-6fa1-44b2-b90c-48afcd05700c" ├ key = "max_depth" └ value = "9"
Rename a model
The registry name can change after versions exist. update_model leaves a field alone when it is nothing and returns DearDiary.Duplicate when another model in the project already uses the new name:
julia> update_model(model_id, "fraud-classifier-dt", nothing)DearDiary.Updated
julia> get_model(model_id).name"fraud-classifier-dt"
Delete rules
An iteration or artifact that a version points at cannot be deleted while that version exists, so the registry never references a missing run:
julia> delete_iteration(iteration_b_id)false
Delete the version first, then the run. Deleting a model removes its versions and keeps their artifacts. See Deleting records.
julia> delete_modelversion(version_b_id);julia> delete_iteration(iteration_b_id)true