← 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 in-silico-perturbation

Skill instructions — Now says: Estimate the price Estimate before uploading anything: the cost depends on the combined file's cell count, so estimate and the total. Do not ask for a yes...

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

Price it, then confirm Price before uploading anything: the cost depends on the combined file's cell count, so estimate and the total, and get their yes before spending. your estimate. against the reference centroid · check identity is p...

After

Oct 28, 2026 · 13:02 UTC

Estimate the price Estimate before uploading anything: the cost depends on the combined file's cell count, so estimate and the total. Do not ask for a yes here. Every run that spends credit needs the user's explicit approval of the platf...

Compare saved observations

Download comparison JSON

SKILL.md text changes

− removed · + added

--- before
+++ after
@@ -77,12 +77,16 @@
 
 State the edit you chose; it is your decision, not a platform default.
 
-## 3. Price it, then confirm
+## 3. Estimate the price
 
-Price before uploading anything: the cost depends on the combined file's cell count, so
+Estimate before uploading anything: the cost depends on the combined file's cell count, so
 work it out with the rates from `listModels` as `check-credits` describes. Each model is
 **one job** over the whole combined file. Show the user the cell count, the per-model
-estimate and the total, and get their yes before spending.
+estimate and the total.
+
+Do not ask for a yes here. Every run that spends credit needs the user's explicit approval
+of the platform's quote in step 5, and uploading spends no credit. Stop to ask now only when
+the total exceeds the user's balance (read it fresh with `whoami()`, per `check-credits`).
 
 If the total is too much, cut cells or genes; do not split into per-gene runs, which pays
 the per-job amount once per gene.
@@ -102,7 +106,7 @@
 
 Follow `compute-embeddings` for each model on the registered dataset: queue the run, show
 the platform's own quote, and approve only after the user says yes. That quote replaces
-your estimate.
+your estimate. Each model needs its own yes, against its own quote.
 
 ## 6. Download and score
 
@@ -143,5 +147,5 @@
 
 Edit counts locally, never embeddings · the model sees every condition · one combined file,
 one run per model · always include an expression-matched null · score within cell type
-against the reference centroid · check identity is preserved · estimate → show the price →
-explicit yes → run · hypotheses, not hits · never print credentials or raw upstream errors.
+against the reference centroid · check identity is preserved · estimate → upload → queue →
+quote → explicit yes → run · hypotheses, not hits · never print credentials or raw upstream errors.
Full technical diff · 1 changed fields

changed /skill_md_contents

BEFORE
"---\nname: in-silico-perturbation\ndescription: >-\n  Run an in-silico gene knockout, knockdown or overexpression screen with Helical\n  foundation models by editing the expression matrix locally, then embedding the\n  perturbed cells on the platform. Use when the user wants to perturb genes, screen\n  targets, or find genes whose perturbation shifts diseased cells toward a healthy state\n  — even when no perturbation tool is available, because none is needed.\n---\n\n# In-silico perturbation\n\nFinds which gene perturbations move cells from one state toward another, by showing a\nfoundation model perturbed cells and measuring where their embeddings land.\n\n**The one thing to understand before starting:** a perturbation is an edit to the\nexpression matrix, and you can make it yourself. You do not need a perturbation, target-ID\nor gene-validation tool, and their absence is not a reason to stop. The work splits in\ntwo:\n\n- **Locally:** download the dataset, edit the counts, build one file, and later score the\n  embeddings.\n- **On Helical:** upload that file, embed it with each model, download the embeddings.\n\n**The model must see the perturbed counts.** Embedding the unperturbed dataset and then\n\"knocking down\" a gene in embedding space, or scoring genes against centroids of the\noriginal embeddings, measures nothing. Every perturbed condition goes through the model.\n\n## 1. Get the expression matrix locally\n\n`listDatasets(...)` → `getDataset({ id })`. Note `cellCount`, `geneCount`, and the\n`.obs` columns it names: `celltypeColumn`, `diseaseColumn`, `donorColumn`. You need the\ncell-type and state columns to choose cells and to score.\n\nThe platform does not hand back a dataset's own file. Get the `.h5ad` from the record's\n`linkData` (the original download URL), or from the user if they have it. If neither\nexists, say so plainly; do not substitute a different dataset.\n\nCheck before editing:\n\n- **Raw counts.** The models normalise their input themselves. If `X` holds normalised or\n  log values, use `layers[\"counts\"]` or `raw` instead; if there are no counts, tell the\n  user the perturbation will be less faithful.\n- **Genes are present and expressed.** For each candidate, report the fraction of target\n  cells with non-zero counts. Drop genes that are absent or barely detected — knocking out\n  a gene the cells do not express changes nothing and wastes a slot.\n\n## 2. Build one combined file\n\nPut every condition in **one `.h5ad`**, so each model needs one embedding run, not one per\ngene, and every condition goes through the same model pass. Tag each cell with a\n`perturbation` column in `.obs`:\n\n| Block | Cells | `perturbation` |\n|---|---|---|\n| Reference | target-state cells (e.g. healthy), unedited | `reference` |\n| Control | source-state cells (e.g. injured), unedited | `control` |\n| One per gene | a copy of the control cells with that gene edited | the gene name |\n| Null | a copy of the control cells with a random, expression-matched gene edited | `null_<gene>` |\n\n- **Include the null.** Any edit moves an embedding a little. Without 3–5 random genes,\n  matched to the candidates on detection rate and edited the same way, there is nothing\n  to say a candidate's shift is more than noise.\n- **Subsample.** The file holds `reference + control × (1 + genes + nulls)` cells. A few\n  hundred control cells per cell type is enough; balance donors where the metadata allows.\n- **Keep the layout of the source.** Change only count values and add the `perturbation`\n  column. Keep `.var` and the existing `.obs` columns as they were, so the same gene\n  identifiers and column names apply. Give each copied cell a unique `obs_names`, such as\n  `<cell>__<perturbation>`.\n\nHow to edit a gene, on raw counts:\n\n- **Knockout:** set the gene to 0.\n- **Knockdown:** multiply its counts by a factor, e.g. 0.25. Geneformer reads gene *rank*,\n  so a partial knockdown may not change its input at all — use a knockout for it, and say so.\n- **Overexpression:** raise it, e.g. to its 99th percentile across the dataset.\n\nState the edit you chose; it is your decision, not a platform default.\n\n## 3. Price it, then confirm\n\nPrice before uploading anything: the cost depends on the combined file's cell count, so\nwork it out with the rates from `listModels` as `check-credits` describes. Each model is\n**one job** over the whole combined file. Show the user the cell count, the per-model\nestimate and the total, and get their yes before spending.\n\nIf the total is too much, cut cells or genes; do not split into per-gene runs, which pays\nthe per-job amount once per gene.\n\n## 4. Upload and register\n\n`initiateDatasetUpload({ sizeBytes })` → upload every part → `completeDatasetUpload` →\n`registerDataset`. Follow the tool descriptions exactly: slice parts by **exact bytes**\nfrom each part's `offsetBytes` and `lengthBytes`, and do not complete while a part is\noutstanding.\n\nRegister with the source record's metadata, a new `name` that says what was perturbed\n(names are unique per project; a clash is a 409), and `parentDatasetId` set to the source\ndataset, so the derivation is on record.\n\n## 5. Embed with each model\n\nFollow `compute-embeddings` for each model on the registered dataset: queue the run, show\nthe platform's own quote, and approve only after the user says yes. That quote replaces\nyour estimate.\n\n## 6. Download and score\n\n`getRunDetails({ runId })` until `succeeded`, then `downloadArtifact({ artifactId })` for\nthe embedding matrix and fetch it with `curl`. If the artifact carries cell ids, join on\nthem. If it is a bare matrix, check its row count equals your file's cell count before\nassuming the rows follow your file's order, and say you assumed it.\n\nScore **within each cell type**, using cosine distance in each model's own space:\n\n- `H` = centroid of the `reference` cells of that type.\n- For each control cell `i` and condition `g`:\n  `shift = (d(controlᵢ, H) − d(gᵢ, H)) / d(controlᵢ, H)`. Positive means toward the\n  reference.\n- A gene's score is its mean shift, compared with the null genes' shifts: report its\n  margin over the null, not the raw number.\n- **Identity check:** a perturbed cell should still be nearest its own cell type's\n  centroid. A gene that \"improves\" cells by making them stop looking like their cell type\n  is a red flag, not a hit.\n\nAcross models, a gene matters when it beats the null in at least two of them.\n\n## 7. Report\n\nA ranked table per cell type: gene, mean shift per model, margin over the null, models in\nagreement, identity check passed or not. Lead with the genes that agree across models.\n\nThese are hypotheses for experimental follow-up, not validated targets. Say what was\nedited and how, how many cells, and any assumption about row order.\n\n## Hand-off\n\n- Embedding mechanics and approval → `compute-embeddings`.\n- Cost and balance → `check-credits`.\n- Want a model adapted to the tissue first? → `fine-tune-model`, then embed with it here.\n\n## Conventions\n\nEdit counts locally, never embeddings · the model sees every condition · one combined file,\none run per model · always include an expression-matched null · score within cell type\nagainst the reference centroid · check identity is preserved · estimate → show the price →\nexplicit yes → run · hypotheses, not hits · never print credentials or raw upstream errors.\n"
AFTER
"---\nname: in-silico-perturbation\ndescription: >-\n  Run an in-silico gene knockout, knockdown or overexpression screen with Helical\n  foundation models by editing the expression matrix locally, then embedding the\n  perturbed cells on the platform. Use when the user wants to perturb genes, screen\n  targets, or find genes whose perturbation shifts diseased cells toward a healthy state\n  — even when no perturbation tool is available, because none is needed.\n---\n\n# In-silico perturbation\n\nFinds which gene perturbations move cells from one state toward another, by showing a\nfoundation model perturbed cells and measuring where their embeddings land.\n\n**The one thing to understand before starting:** a perturbation is an edit to the\nexpression matrix, and you can make it yourself. You do not need a perturbation, target-ID\nor gene-validation tool, and their absence is not a reason to stop. The work splits in\ntwo:\n\n- **Locally:** download the dataset, edit the counts, build one file, and later score the\n  embeddings.\n- **On Helical:** upload that file, embed it with each model, download the embeddings.\n\n**The model must see the perturbed counts.** Embedding the unperturbed dataset and then\n\"knocking down\" a gene in embedding space, or scoring genes against centroids of the\noriginal embeddings, measures nothing. Every perturbed condition goes through the model.\n\n## 1. Get the expression matrix locally\n\n`listDatasets(...)` → `getDataset({ id })`. Note `cellCount`, `geneCount`, and the\n`.obs` columns it names: `celltypeColumn`, `diseaseColumn`, `donorColumn`. You need the\ncell-type and state columns to choose cells and to score.\n\nThe platform does not hand back a dataset's own file. Get the `.h5ad` from the record's\n`linkData` (the original download URL), or from the user if they have it. If neither\nexists, say so plainly; do not substitute a different dataset.\n\nCheck before editing:\n\n- **Raw counts.** The models normalise their input themselves. If `X` holds normalised or\n  log values, use `layers[\"counts\"]` or `raw` instead; if there are no counts, tell the\n  user the perturbation will be less faithful.\n- **Genes are present and expressed.** For each candidate, report the fraction of target\n  cells with non-zero counts. Drop genes that are absent or barely detected — knocking out\n  a gene the cells do not express changes nothing and wastes a slot.\n\n## 2. Build one combined file\n\nPut every condition in **one `.h5ad`**, so each model needs one embedding run, not one per\ngene, and every condition goes through the same model pass. Tag each cell with a\n`perturbation` column in `.obs`:\n\n| Block | Cells | `perturbation` |\n|---|---|---|\n| Reference | target-state cells (e.g. healthy), unedited | `reference` |\n| Control | source-state cells (e.g. injured), unedited | `control` |\n| One per gene | a copy of the control cells with that gene edited | the gene name |\n| Null | a copy of the control cells with a random, expression-matched gene edited | `null_<gene>` |\n\n- **Include the null.** Any edit moves an embedding a little. Without 3–5 random genes,\n  matched to the candidates on detection rate and edited the same way, there is nothing\n  to say a candidate's shift is more than noise.\n- **Subsample.** The file holds `reference + control × (1 + genes + nulls)` cells. A few\n  hundred control cells per cell type is enough; balance donors where the metadata allows.\n- **Keep the layout of the source.** Change only count values and add the `perturbation`\n  column. Keep `.var` and the existing `.obs` columns as they were, so the same gene\n  identifiers and column names apply. Give each copied cell a unique `obs_names`, such as\n  `<cell>__<perturbation>`.\n\nHow to edit a gene, on raw counts:\n\n- **Knockout:** set the gene to 0.\n- **Knockdown:** multiply its counts by a factor, e.g. 0.25. Geneformer reads gene *rank*,\n  so a partial knockdown may not change its input at all — use a knockout for it, and say so.\n- **Overexpression:** raise it, e.g. to its 99th percentile across the dataset.\n\nState the edit you chose; it is your decision, not a platform default.\n\n## 3. Estimate the price\n\nEstimate before uploading anything: the cost depends on the combined file's cell count, so\nwork it out with the rates from `listModels` as `check-credits` describes. Each model is\n**one job** over the whole combined file. Show the user the cell count, the per-model\nestimate and the total.\n\nDo not ask for a yes here. Every run that spends credit needs the user's explicit approval\nof the platform's quote in step 5, and uploading spends no credit. Stop to ask now only when\nthe total exceeds the user's balance (read it fresh with `whoami()`, per `check-credits`).\n\nIf the total is too much, cut cells or genes; do not split into per-gene runs, which pays\nthe per-job amount once per gene.\n\n## 4. Upload and register\n\n`initiateDatasetUpload({ sizeBytes })` → upload every part → `completeDatasetUpload` →\n`registerDataset`. Follow the tool descriptions exactly: slice parts by **exact bytes**\nfrom each part's `offsetBytes` and `lengthBytes`, and do not complete while a part is\noutstanding.\n\nRegister with the source record's metadata, a new `name` that says what was perturbed\n(names are unique per project; a clash is a 409), and `parentDatasetId` set to the source\ndataset, so the derivation is on record.\n\n## 5. Embed with each model\n\nFollow `compute-embeddings` for each model on the registered dataset: queue the run, show\nthe platform's own quote, and approve only after the user says yes. That quote replaces\nyour estimate. Each model needs its own yes, against its own quote.\n\n## 6. Download and score\n\n`getRunDetails({ runId })` until `succeeded`, then `downloadArtifact({ artifactId })` for\nthe embedding matrix and fetch it with `curl`. If the artifact carries cell ids, join on\nthem. If it is a bare matrix, check its row count equals your file's cell count before\nassuming the rows follow your file's order, and say you assumed it.\n\nScore **within each cell type**, using cosine distance in each model's own space:\n\n- `H` = centroid of the `reference` cells of that type.\n- For each control cell `i` and condition `g`:\n  `shift = (d(controlᵢ, H) − d(gᵢ, H)) / d(controlᵢ, H)`. Positive means toward the\n  reference.\n- A gene's score is its mean shift, compared with the null genes' shifts: report its\n  margin over the null, not the raw number.\n- **Identity check:** a perturbed cell should still be nearest its own cell type's\n  centroid. A gene that \"improves\" cells by making them stop looking like their cell type\n  is a red flag, not a hit.\n\nAcross models, a gene matters when it beats the null in at least two of them.\n\n## 7. Report\n\nA ranked table per cell type: gene, mean shift per model, margin over the null, models in\nagreement, identity check passed or not. Lead with the genes that agree across models.\n\nThese are hypotheses for experimental follow-up, not validated targets. Say what was\nedited and how, how many cells, and any assumption about row order.\n\n## Hand-off\n\n- Embedding mechanics and approval → `compute-embeddings`.\n- Cost and balance → `check-credits`.\n- Want a model adapted to the tissue first? → `fine-tune-model`, then embed with it here.\n\n## Conventions\n\nEdit counts locally, never embeddings · the model sees every condition · one combined file,\none run per model · always include an expression-matched null · score within cell type\nagainst the reference centroid · check identity is preserved · estimate → upload → queue →\nquote → explicit yes → run · hypotheses, not hits · never print credentials or raw upstream errors.\n"
Full snapshot data
{
  "description": "Run an in-silico gene knockout, knockdown or overexpression screen with Helical foundation models by editing the expression matrix locally, then embedding the perturbed cells on the platform. Use when the user wants to perturb genes, screen targets, or find genes whose perturbation shifts diseased cells toward a healthy state — even when no perturbation tool is available, because none is needed.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 464
    }
  ],
  "name": "in-silico-perturbation",
  "skill_md_contents": "---\nname: in-silico-perturbation\ndescription: >-\n  Run an in-silico gene knockout, knockdown or overexpression screen with Helical\n  foundation models by editing the expression matrix locally, then embedding the\n  perturbed cells on the platform. Use when the user wants to perturb genes, screen\n  targets, or find genes whose perturbation shifts diseased cells toward a healthy state\n  — even when no perturbation tool is available, because none is needed.\n---\n\n# In-silico perturbation\n\nFinds which gene perturbations move cells from one state toward another, by showing a\nfoundation model perturbed cells and measuring where their embeddings land.\n\n**The one thing to understand before starting:** a perturbation is an edit to the\nexpression matrix, and you can make it yourself. You do not need a perturbation, target-ID\nor gene-validation tool, and their absence is not a reason to stop. The work splits in\ntwo:\n\n- **Locally:** download the dataset, edit the counts, build one file, and later score the\n  embeddings.\n- **On Helical:** upload that file, embed it with each model, download the embeddings.\n\n**The model must see the perturbed counts.** Embedding the unperturbed dataset and then\n\"knocking down\" a gene in embedding space, or scoring genes against centroids of the\noriginal embeddings, measures nothing. Every perturbed condition goes through the model.\n\n## 1. Get the expression matrix locally\n\n`listDatasets(...)` → `getDataset({ id })`. Note `cellCount`, `geneCount`, and the\n`.obs` columns it names: `celltypeColumn`, `diseaseColumn`, `donorColumn`. You need the\ncell-type and state columns to choose cells and to score.\n\nThe platform does not hand back a dataset's own file. Get the `.h5ad` from the record's\n`linkData` (the original download URL), or from the user if they have it. If neither\nexists, say so plainly; do not substitute a different dataset.\n\nCheck before editing:\n\n- **Raw counts.** The models normalise their input themselves. If `X` holds normalised or\n  log values, use `layers[\"counts\"]` or `raw` instead; if there are no counts, tell the\n  user the perturbation will be less faithful.\n- **Genes are present and expressed.** For each candidate, report the fraction of target\n  cells with non-zero counts. Drop genes that are absent or barely detected — knocking out\n  a gene the cells do not express changes nothing and wastes a slot.\n\n## 2. Build one combined file\n\nPut every condition in **one `.h5ad`**, so each model needs one embedding run, not one per\ngene, and every condition goes through the same model pass. Tag each cell with a\n`perturbation` column in `.obs`:\n\n| Block | Cells | `perturbation` |\n|---|---|---|\n| Reference | target-state cells (e.g. healthy), unedited | `reference` |\n| Control | source-state cells (e.g. injured), unedited | `control` |\n| One per gene | a copy of the control cells with that gene edited | the gene name |\n| Null | a copy of the control cells with a random, expression-matched gene edited | `null_<gene>` |\n\n- **Include the null.** Any edit moves an embedding a little. Without 3–5 random genes,\n  matched to the candidates on detection rate and edited the same way, there is nothing\n  to say a candidate's shift is more than noise.\n- **Subsample.** The file holds `reference + control × (1 + genes + nulls)` cells. A few\n  hundred control cells per cell type is enough; balance donors where the metadata allows.\n- **Keep the layout of the source.** Change only count values and add the `perturbation`\n  column. Keep `.var` and the existing `.obs` columns as they were, so the same gene\n  identifiers and column names apply. Give each copied cell a unique `obs_names`, such as\n  `<cell>__<perturbation>`.\n\nHow to edit a gene, on raw counts:\n\n- **Knockout:** set the gene to 0.\n- **Knockdown:** multiply its counts by a factor, e.g. 0.25. Geneformer reads gene *rank*,\n  so a partial knockdown may not change its input at all — use a knockout for it, and say so.\n- **Overexpression:** raise it, e.g. to its 99th percentile across the dataset.\n\nState the edit you chose; it is your decision, not a platform default.\n\n## 3. Estimate the price\n\nEstimate before uploading anything: the cost depends on the combined file's cell count, so\nwork it out with the rates from `listModels` as `check-credits` describes. Each model is\n**one job** over the whole combined file. Show the user the cell count, the per-model\nestimate and the total.\n\nDo not ask for a yes here. Every run that spends credit needs the user's explicit approval\nof the platform's quote in step 5, and uploading spends no credit. Stop to ask now only when\nthe total exceeds the user's balance (read it fresh with `whoami()`, per `check-credits`).\n\nIf the total is too much, cut cells or genes; do not split into per-gene runs, which pays\nthe per-job amount once per gene.\n\n## 4. Upload and register\n\n`initiateDatasetUpload({ sizeBytes })` → upload every part → `completeDatasetUpload` →\n`registerDataset`. Follow the tool descriptions exactly: slice parts by **exact bytes**\nfrom each part's `offsetBytes` and `lengthBytes`, and do not complete while a part is\noutstanding.\n\nRegister with the source record's metadata, a new `name` that says what was perturbed\n(names are unique per project; a clash is a 409), and `parentDatasetId` set to the source\ndataset, so the derivation is on record.\n\n## 5. Embed with each model\n\nFollow `compute-embeddings` for each model on the registered dataset: queue the run, show\nthe platform's own quote, and approve only after the user says yes. That quote replaces\nyour estimate. Each model needs its own yes, against its own quote.\n\n## 6. Download and score\n\n`getRunDetails({ runId })` until `succeeded`, then `downloadArtifact({ artifactId })` for\nthe embedding matrix and fetch it with `curl`. If the artifact carries cell ids, join on\nthem. If it is a bare matrix, check its row count equals your file's cell count before\nassuming the rows follow your file's order, and say you assumed it.\n\nScore **within each cell type**, using cosine distance in each model's own space:\n\n- `H` = centroid of the `reference` cells of that type.\n- For each control cell `i` and condition `g`:\n  `shift = (d(controlᵢ, H) − d(gᵢ, H)) / d(controlᵢ, H)`. Positive means toward the\n  reference.\n- A gene's score is its mean shift, compared with the null genes' shifts: report its\n  margin over the null, not the raw number.\n- **Identity check:** a perturbed cell should still be nearest its own cell type's\n  centroid. A gene that \"improves\" cells by making them stop looking like their cell type\n  is a red flag, not a hit.\n\nAcross models, a gene matters when it beats the null in at least two of them.\n\n## 7. Report\n\nA ranked table per cell type: gene, mean shift per model, margin over the null, models in\nagreement, identity check passed or not. Lead with the genes that agree across models.\n\nThese are hypotheses for experimental follow-up, not validated targets. Say what was\nedited and how, how many cells, and any assumption about row order.\n\n## Hand-off\n\n- Embedding mechanics and approval → `compute-embeddings`.\n- Cost and balance → `check-credits`.\n- Want a model adapted to the tissue first? → `fine-tune-model`, then embed with it here.\n\n## Conventions\n\nEdit counts locally, never embeddings · the model sees every condition · one combined file,\none run per model · always include an expression-matched null · score within cell type\nagainst the reference centroid · check identity is preserved · estimate → upload → queue →\nquote → explicit yes → run · hypotheses, not hits · never print credentials or raw upstream errors.\n"
}

SHA-256 of public snapshot: 8057b4b046e0922137fa55b0048cc2cc30e517ec86e8e578cee0dc96068b7b43