← HelicalCONTENT HISTORY

Update to Helical

Snapshot Oct 28, 2026 · 13:02 UTC · version 1.0.5

Collection source: downloaded plugin package.

WHAT CHANGED

Instructions updated for fine-tune-model

Skill instructions — Now says: **Every run that spends credit needs the user's explicit approval.** That approval comes at `resolveConfirmation`, after the platform's quote. So do not ask...

Observed in instructions or declared skills. Runtime behavior has not been tested.

Save Helical and check future changes in Following.

Up to 10 plugins free · no card required.

All fields and original values.

Skill instructions

Before

Oct 24, 2026 · 13:41 UTC

## 1. Select the dataset and confirm the label column **Confirm the label column with the user before queuing.** Training on a plausible-looking but wrong column wastes the whole run, and nothing downstream will catch it. Where you can s...

After

Oct 28, 2026 · 13:02 UTC

**Every run that spends credit needs the user's explicit approval.** That approval comes at `resolveConfirmation`, after the platform's quote. So do not ask the user to approve the configuration before queuing. Make the choices below, qu...

Compare saved observations

Download comparison JSON

SKILL.md text changes

− removed · + added

--- before
+++ after
@@ -18,7 +18,13 @@
 most expensive run on this surface, so the preparation below matters more than it does for
 embeddings.
 
-## 1. Select the dataset and confirm the label column
+**Every run that spends credit needs the user's explicit approval.** That approval comes at
+`resolveConfirmation`, after the platform's quote. So do not ask the user to approve the
+configuration before queuing. Make the choices below, queue the run, then ask once, showing
+the choices and the price together. Queuing is free. To change a choice, reject the queued
+run and queue again.
+
+## 1. Select the dataset and choose the label column
 
 `listDatasets(...)` → `getDataset({ id })`. The record names likely label columns:
 `celltypeColumn`, `celltypeColumnLv2`, `diseaseColumn`, `donorColumn`. Note `cellCount`
@@ -29,10 +35,12 @@
 copy of the file (the record's `linkData`, when it has one; an uploaded dataset usually
 does not).
 
-**Confirm the label column with the user before queuing.** Training on a plausible-looking
-but wrong column wastes the whole run, and nothing downstream will catch it. Where you can
-see the classes, check they look like what the user expects. A column with one dominant
-class or hundreds of singletons will not train usefully.
+**Choose the label column, and name it in the approval request.** Training on a
+plausible-looking but wrong column wastes the whole run, and nothing downstream will catch
+it. Where you can see the classes, check they look like what the user expects. A column
+with one dominant class or hundreds of singletons will not train usefully. Ask before
+queuing only when you cannot choose: several columns fit the request, none does, or the
+classes look unusable.
 
 **Validation needs its own dataset.** This surface does not split the training data. To
 validate, the user registers a separate held-out dataset and you pass it as `valDatasetId`.
@@ -91,8 +99,8 @@
 
 Reasonable starting values when the user has no preference: `learning_rate` 1e-4,
 `batch_size` 16, `epochs` 1, `logging_steps` 10, `lr_scheduler` `"constant"`, `min_lr`
-1e-6, `seed` 42, `num_trainable_layers` 2. State the values you chose. They are your
-decisions, not platform defaults, and the user may want different ones.
+1e-6, `seed` 42, `num_trainable_layers` 2. Queue with them; do not stop to ask first. They
+are your decisions, not platform defaults, so list them in the approval request.
 
 Start with a single epoch. It is cheaper to run again than to find out after a long run
 that the configuration was wrong.
@@ -107,9 +115,11 @@
 of the same dataset, because each epoch makes several passes over the training cells, and
 validation adds a pass over the validation cells each epoch.
 
-**Get the user's explicit yes**, restating the dataset, the label column, the base model,
-the epoch count and the quoted price. An approval for an embedding run is not an approval
-for this one. Then call `resolveConfirmation({ id: confirmation_id, decision: "approve" })`.
+**Ask for the user's explicit yes in one message.** Name the dataset, the label column, the
+base model, the values you chose and the quoted price. Say the run is queued and waits for
+their yes, and that any value can be changed before it starts, with a new quote. An
+approval for an embedding run is not an approval for this one, and an earlier "sounds good"
+about the plan is not approval for the run. Then call `resolveConfirmation({ id: confirmation_id, decision: "approve" })`.
 If they decline, resolve it with `"reject"`.
 
 To change anything, reject the queued confirmation, then queue again with
@@ -147,8 +157,9 @@
 
 ## Conventions
 
-`datasetId`, never a path · scope is derived from the signed-in user · verify the label
+`datasetId`, never a path · scope is derived from the signed-in user · check the label
 column before queuing · validate with a separate dataset, never a split · send only the
-schema's fields · queue → relay the quote → explicit yes → `resolveConfirmation` · poll the
+schema's fields · no approval before queuing · queue → choices and quote in one message →
+explicit yes → `resolveConfirmation` · poll the
 run · a new fine-tune can run here at once, promoted or not · never print credentials or
 raw upstream errors.
Full technical diff · 1 changed fields

changed /skill_md_contents

BEFORE
"---\nname: fine-tune-model\ndescription: >-\n  Fine-tune a Helical foundation model on a labelled single-cell dataset. Use when the\n  user wants to personalize, train, or adapt a model to their own labels. For\n  representations from an existing model without training, use compute-embeddings\n  instead.\n---\n\n# Fine-tune a model\n\nTrains a foundation model on a labelled dataset and registers the result in the user's\nmodel list.\n\n`triggerFinetuning` does not start anything. It queues the run and returns a\n`confirmation_id` and the platform's price. The run starts only when\n`resolveConfirmation` approves it, and **that call is the user's approval**. This is the\nmost expensive run on this surface, so the preparation below matters more than it does for\nembeddings.\n\n## 1. Select the dataset and confirm the label column\n\n`listDatasets(...)` → `getDataset({ id })`. The record names likely label columns:\n`celltypeColumn`, `celltypeColumnLv2`, `diseaseColumn`, `donorColumn`. Note `cellCount`\ntoo; the price scales with it.\n\nThese tools cannot list a dataset's other `.obs` columns or their values. If the user wants\na column the record does not name, ask them for its exact name, or read it from a local\ncopy of the file (the record's `linkData`, when it has one; an uploaded dataset usually\ndoes not).\n\n**Confirm the label column with the user before queuing.** Training on a plausible-looking\nbut wrong column wastes the whole run, and nothing downstream will catch it. Where you can\nsee the classes, check they look like what the user expects. A column with one dominant\nclass or hundreds of singletons will not train usefully.\n\n**Validation needs its own dataset.** This surface does not split the training data. To\nvalidate, the user registers a separate held-out dataset and you pass it as `valDatasetId`.\nWithout one, the run trains with no validation.\n\n## 2. Choose a base model\n\n`listModels()`. Fine-tune from a `base_models` entry: pass its `name` as `model` and omit\n`model_version`. To train an existing fine-tune further, pass a `models` entry's `name`\nplus its `version`, as a number, in `model_version`, promoted or not. Pick a model whose\n`credits_per_cell` is not `null`; an unpriced model cannot run here.\n\nBefore committing: if the user wants representations rather than a classifier, a\nzero-shot embedding may already answer it. `compute-embeddings` is far cheaper and faster.\nSay so rather than fine-tuning by default.\n\n## 3. Queue it\n\nThese fields are required, and a missing one is a 400:\n\n```\ntriggerFinetuning({\n  datasetId, model,\n  labels:      [\"cell_type\"],      # one entry per prediction task\n  task_type:   [\"prediction\"],     # \"prediction\" | \"contrastive\"\n  model_type:  [\"classification\"], # one per prediction task: \"classification\" | \"regression\"\n  learning_rate, batch_size, epochs, logging_steps,\n  lr_scheduler,                    # \"constant\" | \"linear\" | \"cosine_with_min_lr\"\n  min_lr, seed, num_trainable_layers,\n  registered_model_name,           # letters, digits, _ - { } only\n  # optional: model_version, valDatasetId, weight_decay, warmup_steps,\n  #           early_stopping_patience, lora_config, task_weights, ...\n})\n```\n\n`labels` and `model_type` describe the **prediction tasks only**, one entry each per task,\nin the same order. `task_type` lists one entry per task, contrastive included. The tool's\nown description calls all three parallel; that holds only when every task is a prediction.\n\nAt most one task can be contrastive. It takes no `labels` entry; its columns go in its own\nfields, and nothing checks them before the run starts:\n\n- `loss_type: \"supcon\"` needs `supcon_label_columns`, `temperature` and\n  `n_labels_per_batch` (at most `batch_size`).\n- `loss_type: \"pairwise\"` needs `pairwise_similarity_columns` and `margin`.\n\nTwo optional fields carry conditions the server does not check either.\n`early_stopping_patience` needs a `valDatasetId`, and `warmup_steps` needs a non-constant\n`lr_scheduler`. A bad combination is charged and then fails, so get these right before\nqueuing.\n\n**Send only fields the schema lists.** It refuses an unknown field with a 400. Three\ncommon ones are not offered here: `val_split` (pass `valDatasetId` instead),\n`val_steps` (validation runs once an epoch) and `hyperparameter_config` (one run trains\none configuration).\n\nReasonable starting values when the user has no preference: `learning_rate` 1e-4,\n`batch_size` 16, `epochs` 1, `logging_steps` 10, `lr_scheduler` `\"constant\"`, `min_lr`\n1e-6, `seed` 42, `num_trainable_layers` 2. State the values you chose. They are your\ndecisions, not platform defaults, and the user may want different ones.\n\nStart with a single epoch. It is cheaper to run again than to find out after a long run\nthat the configuration was wrong.\n\n`registered_model_name` must not be only a model prefix (`scGPT_` alone is rejected). Give\nit a name that says what makes this model different.\n\n## 4. Show the price, then approve\n\nThe reply carries the platform's quote: the cells it counts and the most it\nwill cost. Relay that figure; do not recompute it. It is usually many times an embedding\nof the same dataset, because each epoch makes several passes over the training cells, and\nvalidation adds a pass over the validation cells each epoch.\n\n**Get the user's explicit yes**, restating the dataset, the label column, the base model,\nthe epoch count and the quoted price. An approval for an embedding run is not an approval\nfor this one. Then call `resolveConfirmation({ id: confirmation_id, decision: \"approve\" })`.\nIf they decline, resolve it with `\"reject\"`.\n\nTo change anything, reject the queued confirmation, then queue again with\n`triggerFinetuning`; that mints a new quote. A confirmation left pending stays approvable\nuntil it expires.\n\nA 409 from `triggerFinetuning` means the same run is already queued.\n`listPendingConfirmations()` finds it, but its entry carries no price and does not show\nthe epochs or learning rate. If you do not still have that confirmation's quote from this\nconversation, reject it and queue again, so the user approves a price they have seen.\n\nFor a 402 or an unusable quote, see `check-credits`.\n\n## 5. Follow it through\n\nOn approval, `outcome.dag_run_id` names the run. Poll `getRunDetails({ runId })` until\n`state` is `succeeded`, `failed` or `cancelled`. Fine-tuning runs are long, so poll at a\nsensible interval and keep the user informed. On failure, `getDagErrors({ runId })` says\nwhat went wrong. A failed or cancelled run is refunded in full.\n\nOn success the model is registered under `registered_model_name` and shows in\n`listModels` `models`, unpromoted. Promotion is only a label here: the model can be used\nstraight away for embeddings or further training. Pass its `name` and `version`. It is\ncharged at its `base_model`'s rates.\n\n## Judging whether it worked\n\nBe honest about the limit: these tools report run state and artifacts, not a training\ncurve or a metric. `getRunDetails` lists the artifacts. `downloadArtifact` fetches a\nsingle file, such as a metrics file if the run wrote one, but not a checkpoint directory.\n\nThe practical comparison here is an embedding from the base model against one from the\nfine-tuned model, judged on the UMAP. Offer that rather than\nclaiming a quality verdict the tools cannot support.\n\n## Conventions\n\n`datasetId`, never a path · scope is derived from the signed-in user · verify the label\ncolumn before queuing · validate with a separate dataset, never a split · send only the\nschema's fields · queue → relay the quote → explicit yes → `resolveConfirmation` · poll the\nrun · a new fine-tune can run here at once, promoted or not · never print credentials or\nraw upstream errors.\n"
AFTER
"---\nname: fine-tune-model\ndescription: >-\n  Fine-tune a Helical foundation model on a labelled single-cell dataset. Use when the\n  user wants to personalize, train, or adapt a model to their own labels. For\n  representations from an existing model without training, use compute-embeddings\n  instead.\n---\n\n# Fine-tune a model\n\nTrains a foundation model on a labelled dataset and registers the result in the user's\nmodel list.\n\n`triggerFinetuning` does not start anything. It queues the run and returns a\n`confirmation_id` and the platform's price. The run starts only when\n`resolveConfirmation` approves it, and **that call is the user's approval**. This is the\nmost expensive run on this surface, so the preparation below matters more than it does for\nembeddings.\n\n**Every run that spends credit needs the user's explicit approval.** That approval comes at\n`resolveConfirmation`, after the platform's quote. So do not ask the user to approve the\nconfiguration before queuing. Make the choices below, queue the run, then ask once, showing\nthe choices and the price together. Queuing is free. To change a choice, reject the queued\nrun and queue again.\n\n## 1. Select the dataset and choose the label column\n\n`listDatasets(...)` → `getDataset({ id })`. The record names likely label columns:\n`celltypeColumn`, `celltypeColumnLv2`, `diseaseColumn`, `donorColumn`. Note `cellCount`\ntoo; the price scales with it.\n\nThese tools cannot list a dataset's other `.obs` columns or their values. If the user wants\na column the record does not name, ask them for its exact name, or read it from a local\ncopy of the file (the record's `linkData`, when it has one; an uploaded dataset usually\ndoes not).\n\n**Choose the label column, and name it in the approval request.** Training on a\nplausible-looking but wrong column wastes the whole run, and nothing downstream will catch\nit. Where you can see the classes, check they look like what the user expects. A column\nwith one dominant class or hundreds of singletons will not train usefully. Ask before\nqueuing only when you cannot choose: several columns fit the request, none does, or the\nclasses look unusable.\n\n**Validation needs its own dataset.** This surface does not split the training data. To\nvalidate, the user registers a separate held-out dataset and you pass it as `valDatasetId`.\nWithout one, the run trains with no validation.\n\n## 2. Choose a base model\n\n`listModels()`. Fine-tune from a `base_models` entry: pass its `name` as `model` and omit\n`model_version`. To train an existing fine-tune further, pass a `models` entry's `name`\nplus its `version`, as a number, in `model_version`, promoted or not. Pick a model whose\n`credits_per_cell` is not `null`; an unpriced model cannot run here.\n\nBefore committing: if the user wants representations rather than a classifier, a\nzero-shot embedding may already answer it. `compute-embeddings` is far cheaper and faster.\nSay so rather than fine-tuning by default.\n\n## 3. Queue it\n\nThese fields are required, and a missing one is a 400:\n\n```\ntriggerFinetuning({\n  datasetId, model,\n  labels:      [\"cell_type\"],      # one entry per prediction task\n  task_type:   [\"prediction\"],     # \"prediction\" | \"contrastive\"\n  model_type:  [\"classification\"], # one per prediction task: \"classification\" | \"regression\"\n  learning_rate, batch_size, epochs, logging_steps,\n  lr_scheduler,                    # \"constant\" | \"linear\" | \"cosine_with_min_lr\"\n  min_lr, seed, num_trainable_layers,\n  registered_model_name,           # letters, digits, _ - { } only\n  # optional: model_version, valDatasetId, weight_decay, warmup_steps,\n  #           early_stopping_patience, lora_config, task_weights, ...\n})\n```\n\n`labels` and `model_type` describe the **prediction tasks only**, one entry each per task,\nin the same order. `task_type` lists one entry per task, contrastive included. The tool's\nown description calls all three parallel; that holds only when every task is a prediction.\n\nAt most one task can be contrastive. It takes no `labels` entry; its columns go in its own\nfields, and nothing checks them before the run starts:\n\n- `loss_type: \"supcon\"` needs `supcon_label_columns`, `temperature` and\n  `n_labels_per_batch` (at most `batch_size`).\n- `loss_type: \"pairwise\"` needs `pairwise_similarity_columns` and `margin`.\n\nTwo optional fields carry conditions the server does not check either.\n`early_stopping_patience` needs a `valDatasetId`, and `warmup_steps` needs a non-constant\n`lr_scheduler`. A bad combination is charged and then fails, so get these right before\nqueuing.\n\n**Send only fields the schema lists.** It refuses an unknown field with a 400. Three\ncommon ones are not offered here: `val_split` (pass `valDatasetId` instead),\n`val_steps` (validation runs once an epoch) and `hyperparameter_config` (one run trains\none configuration).\n\nReasonable starting values when the user has no preference: `learning_rate` 1e-4,\n`batch_size` 16, `epochs` 1, `logging_steps` 10, `lr_scheduler` `\"constant\"`, `min_lr`\n1e-6, `seed` 42, `num_trainable_layers` 2. Queue with them; do not stop to ask first. They\nare your decisions, not platform defaults, so list them in the approval request.\n\nStart with a single epoch. It is cheaper to run again than to find out after a long run\nthat the configuration was wrong.\n\n`registered_model_name` must not be only a model prefix (`scGPT_` alone is rejected). Give\nit a name that says what makes this model different.\n\n## 4. Show the price, then approve\n\nThe reply carries the platform's quote: the cells it counts and the most it\nwill cost. Relay that figure; do not recompute it. It is usually many times an embedding\nof the same dataset, because each epoch makes several passes over the training cells, and\nvalidation adds a pass over the validation cells each epoch.\n\n**Ask for the user's explicit yes in one message.** Name the dataset, the label column, the\nbase model, the values you chose and the quoted price. Say the run is queued and waits for\ntheir yes, and that any value can be changed before it starts, with a new quote. An\napproval for an embedding run is not an approval for this one, and an earlier \"sounds good\"\nabout the plan is not approval for the run. Then call `resolveConfirmation({ id: confirmation_id, decision: \"approve\" })`.\nIf they decline, resolve it with `\"reject\"`.\n\nTo change anything, reject the queued confirmation, then queue again with\n`triggerFinetuning`; that mints a new quote. A confirmation left pending stays approvable\nuntil it expires.\n\nA 409 from `triggerFinetuning` means the same run is already queued.\n`listPendingConfirmations()` finds it, but its entry carries no price and does not show\nthe epochs or learning rate. If you do not still have that confirmation's quote from this\nconversation, reject it and queue again, so the user approves a price they have seen.\n\nFor a 402 or an unusable quote, see `check-credits`.\n\n## 5. Follow it through\n\nOn approval, `outcome.dag_run_id` names the run. Poll `getRunDetails({ runId })` until\n`state` is `succeeded`, `failed` or `cancelled`. Fine-tuning runs are long, so poll at a\nsensible interval and keep the user informed. On failure, `getDagErrors({ runId })` says\nwhat went wrong. A failed or cancelled run is refunded in full.\n\nOn success the model is registered under `registered_model_name` and shows in\n`listModels` `models`, unpromoted. Promotion is only a label here: the model can be used\nstraight away for embeddings or further training. Pass its `name` and `version`. It is\ncharged at its `base_model`'s rates.\n\n## Judging whether it worked\n\nBe honest about the limit: these tools report run state and artifacts, not a training\ncurve or a metric. `getRunDetails` lists the artifacts. `downloadArtifact` fetches a\nsingle file, such as a metrics file if the run wrote one, but not a checkpoint directory.\n\nThe practical comparison here is an embedding from the base model against one from the\nfine-tuned model, judged on the UMAP. Offer that rather than\nclaiming a quality verdict the tools cannot support.\n\n## Conventions\n\n`datasetId`, never a path · scope is derived from the signed-in user · check the label\ncolumn before queuing · validate with a separate dataset, never a split · send only the\nschema's fields · no approval before queuing · queue → choices and quote in one message →\nexplicit yes → `resolveConfirmation` · poll the\nrun · a new fine-tune can run here at once, promoted or not · never print credentials or\nraw upstream errors.\n"
Full snapshot data
{
  "description": "Fine-tune a Helical foundation model on a labelled single-cell dataset. Use when the user wants to personalize, train, or adapt a model to their own labels. For representations from an existing model without training, use compute-embeddings instead.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 403
    }
  ],
  "name": "fine-tune-model",
  "skill_md_contents": "---\nname: fine-tune-model\ndescription: >-\n  Fine-tune a Helical foundation model on a labelled single-cell dataset. Use when the\n  user wants to personalize, train, or adapt a model to their own labels. For\n  representations from an existing model without training, use compute-embeddings\n  instead.\n---\n\n# Fine-tune a model\n\nTrains a foundation model on a labelled dataset and registers the result in the user's\nmodel list.\n\n`triggerFinetuning` does not start anything. It queues the run and returns a\n`confirmation_id` and the platform's price. The run starts only when\n`resolveConfirmation` approves it, and **that call is the user's approval**. This is the\nmost expensive run on this surface, so the preparation below matters more than it does for\nembeddings.\n\n**Every run that spends credit needs the user's explicit approval.** That approval comes at\n`resolveConfirmation`, after the platform's quote. So do not ask the user to approve the\nconfiguration before queuing. Make the choices below, queue the run, then ask once, showing\nthe choices and the price together. Queuing is free. To change a choice, reject the queued\nrun and queue again.\n\n## 1. Select the dataset and choose the label column\n\n`listDatasets(...)` → `getDataset({ id })`. The record names likely label columns:\n`celltypeColumn`, `celltypeColumnLv2`, `diseaseColumn`, `donorColumn`. Note `cellCount`\ntoo; the price scales with it.\n\nThese tools cannot list a dataset's other `.obs` columns or their values. If the user wants\na column the record does not name, ask them for its exact name, or read it from a local\ncopy of the file (the record's `linkData`, when it has one; an uploaded dataset usually\ndoes not).\n\n**Choose the label column, and name it in the approval request.** Training on a\nplausible-looking but wrong column wastes the whole run, and nothing downstream will catch\nit. Where you can see the classes, check they look like what the user expects. A column\nwith one dominant class or hundreds of singletons will not train usefully. Ask before\nqueuing only when you cannot choose: several columns fit the request, none does, or the\nclasses look unusable.\n\n**Validation needs its own dataset.** This surface does not split the training data. To\nvalidate, the user registers a separate held-out dataset and you pass it as `valDatasetId`.\nWithout one, the run trains with no validation.\n\n## 2. Choose a base model\n\n`listModels()`. Fine-tune from a `base_models` entry: pass its `name` as `model` and omit\n`model_version`. To train an existing fine-tune further, pass a `models` entry's `name`\nplus its `version`, as a number, in `model_version`, promoted or not. Pick a model whose\n`credits_per_cell` is not `null`; an unpriced model cannot run here.\n\nBefore committing: if the user wants representations rather than a classifier, a\nzero-shot embedding may already answer it. `compute-embeddings` is far cheaper and faster.\nSay so rather than fine-tuning by default.\n\n## 3. Queue it\n\nThese fields are required, and a missing one is a 400:\n\n```\ntriggerFinetuning({\n  datasetId, model,\n  labels:      [\"cell_type\"],      # one entry per prediction task\n  task_type:   [\"prediction\"],     # \"prediction\" | \"contrastive\"\n  model_type:  [\"classification\"], # one per prediction task: \"classification\" | \"regression\"\n  learning_rate, batch_size, epochs, logging_steps,\n  lr_scheduler,                    # \"constant\" | \"linear\" | \"cosine_with_min_lr\"\n  min_lr, seed, num_trainable_layers,\n  registered_model_name,           # letters, digits, _ - { } only\n  # optional: model_version, valDatasetId, weight_decay, warmup_steps,\n  #           early_stopping_patience, lora_config, task_weights, ...\n})\n```\n\n`labels` and `model_type` describe the **prediction tasks only**, one entry each per task,\nin the same order. `task_type` lists one entry per task, contrastive included. The tool's\nown description calls all three parallel; that holds only when every task is a prediction.\n\nAt most one task can be contrastive. It takes no `labels` entry; its columns go in its own\nfields, and nothing checks them before the run starts:\n\n- `loss_type: \"supcon\"` needs `supcon_label_columns`, `temperature` and\n  `n_labels_per_batch` (at most `batch_size`).\n- `loss_type: \"pairwise\"` needs `pairwise_similarity_columns` and `margin`.\n\nTwo optional fields carry conditions the server does not check either.\n`early_stopping_patience` needs a `valDatasetId`, and `warmup_steps` needs a non-constant\n`lr_scheduler`. A bad combination is charged and then fails, so get these right before\nqueuing.\n\n**Send only fields the schema lists.** It refuses an unknown field with a 400. Three\ncommon ones are not offered here: `val_split` (pass `valDatasetId` instead),\n`val_steps` (validation runs once an epoch) and `hyperparameter_config` (one run trains\none configuration).\n\nReasonable starting values when the user has no preference: `learning_rate` 1e-4,\n`batch_size` 16, `epochs` 1, `logging_steps` 10, `lr_scheduler` `\"constant\"`, `min_lr`\n1e-6, `seed` 42, `num_trainable_layers` 2. Queue with them; do not stop to ask first. They\nare your decisions, not platform defaults, so list them in the approval request.\n\nStart with a single epoch. It is cheaper to run again than to find out after a long run\nthat the configuration was wrong.\n\n`registered_model_name` must not be only a model prefix (`scGPT_` alone is rejected). Give\nit a name that says what makes this model different.\n\n## 4. Show the price, then approve\n\nThe reply carries the platform's quote: the cells it counts and the most it\nwill cost. Relay that figure; do not recompute it. It is usually many times an embedding\nof the same dataset, because each epoch makes several passes over the training cells, and\nvalidation adds a pass over the validation cells each epoch.\n\n**Ask for the user's explicit yes in one message.** Name the dataset, the label column, the\nbase model, the values you chose and the quoted price. Say the run is queued and waits for\ntheir yes, and that any value can be changed before it starts, with a new quote. An\napproval for an embedding run is not an approval for this one, and an earlier \"sounds good\"\nabout the plan is not approval for the run. Then call `resolveConfirmation({ id: confirmation_id, decision: \"approve\" })`.\nIf they decline, resolve it with `\"reject\"`.\n\nTo change anything, reject the queued confirmation, then queue again with\n`triggerFinetuning`; that mints a new quote. A confirmation left pending stays approvable\nuntil it expires.\n\nA 409 from `triggerFinetuning` means the same run is already queued.\n`listPendingConfirmations()` finds it, but its entry carries no price and does not show\nthe epochs or learning rate. If you do not still have that confirmation's quote from this\nconversation, reject it and queue again, so the user approves a price they have seen.\n\nFor a 402 or an unusable quote, see `check-credits`.\n\n## 5. Follow it through\n\nOn approval, `outcome.dag_run_id` names the run. Poll `getRunDetails({ runId })` until\n`state` is `succeeded`, `failed` or `cancelled`. Fine-tuning runs are long, so poll at a\nsensible interval and keep the user informed. On failure, `getDagErrors({ runId })` says\nwhat went wrong. A failed or cancelled run is refunded in full.\n\nOn success the model is registered under `registered_model_name` and shows in\n`listModels` `models`, unpromoted. Promotion is only a label here: the model can be used\nstraight away for embeddings or further training. Pass its `name` and `version`. It is\ncharged at its `base_model`'s rates.\n\n## Judging whether it worked\n\nBe honest about the limit: these tools report run state and artifacts, not a training\ncurve or a metric. `getRunDetails` lists the artifacts. `downloadArtifact` fetches a\nsingle file, such as a metrics file if the run wrote one, but not a checkpoint directory.\n\nThe practical comparison here is an embedding from the base model against one from the\nfine-tuned model, judged on the UMAP. Offer that rather than\nclaiming a quality verdict the tools cannot support.\n\n## Conventions\n\n`datasetId`, never a path · scope is derived from the signed-in user · check the label\ncolumn before queuing · validate with a separate dataset, never a split · send only the\nschema's fields · no approval before queuing · queue → choices and quote in one message →\nexplicit yes → `resolveConfirmation` · poll the\nrun · a new fine-tune can run here at once, promoted or not · never print credentials or\nraw upstream errors.\n"
}

SHA-256 of public snapshot: d0600647b78da7778dd3077660bad6815102abfb9347fe3dd9bee9f3ddd0710d