Skill Tracer
Skills › Scientific Agent Skills › genomic-intelligence

genomic-intelligence

skill · 416 lines · MIT · skills/genomic-intelligence/SKILL.md

The facts

Its description

Predicts regulatory features, gene structure, and expression directly from DNA sequence using Genomic Intelligence's hosted transformer DNA language models — no local GPU or model weights. Six tasks over a REST API and a hosted MCP server (keyless public demo): promoter regions, splice donor/acceptor sites, enhancer activity, chromatin state, sequence-to-expression (log TPM), and de-novo gene annotation, plus a composite find-genes-then-predict-expression workflow. Use when the user has a gene symbol, a genomic region, or a DNA/FASTA sequence and wants any of these predictions, mentions Genomic Intelligence, genomicintelligence.ai, api.genomicintelligence.ai, or mcp.genomicintelligence.ai.

The description the agent reads to decide when to use it.

From its settingsline 3

Files it mentions

references/mcp.md, references/tasks.md, references/sequence-acquisition.md (first 5 of 10 lines)

Files the text names, such as ones to read or write. Found by matching words, so treat them as pointers.

Found by matching wordsline 60 line 145 line 202 line 330 line 386

Licence

MIT, from the skill file

The terms its author shares it under, which let it be shown here.

From its settingsline 4 licence text

Other settings

compatibility: Python 3.10+ with the `requests` library for the REST examples. Network access required. The REST `/v1` API needs a `GI_API_KEY` (a `gi_` bearer); the hosted MCP server at mcp.genomicintelligence.ai/mcp works keyless against a rate- and concurrency-limited public demo tier, key optional. metadata: name: GI_API_KEY

More settings at the top of the file, shown as written.

From its settingsline 5 line 6
Other files in this skill (5) Close

The text

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1---
2name: genomic-intelligence
3description: "Predicts regulatory features, gene structure, and expression directly from DNA sequence using Genomic Intelligence's hosted transformer DNA language models — no local GPU or model weights. Six tasks over a REST API and a hosted MCP server (keyless public demo): promoter regions, splice donor/acceptor sites, enhancer activity, chromatin state, sequence-to-expression (log TPM), and de-novo gene annotation, plus a composite find-genes-then-predict-expression workflow. Use when the user has a gene symbol, a genomic region, or a DNA/FASTA sequence and wants any of these predictions, mentions Genomic Intelligence, genomicintelligence.ai, api.genomicintelligence.ai, or mcp.genomicintelligence.ai."
4license: MIT
5compatibility: Python 3.10+ with the `requests` library for the REST examples. Network access required. The REST `/v1` API needs a `GI_API_KEY` (a `gi_` bearer); the hosted MCP server at mcp.genomicintelligence.ai/mcp works keyless against a rate- and concurrency-limited public demo tier, key optional.
6metadata:
7 version: "1.4"
8 last-reviewed: "2026-10-01"
9 skill-author: Genomic Intelligence
10 trigger-keywords: DNA sequence prediction, regulatory genomics, promoter prediction, splice site prediction, enhancer activity, chromatin state, gene expression prediction, sequence to expression, log TPM, gene annotation, transcript prediction, DNA language model, genomic intelligence, hosted inference, Ensembl sequence, FASTA prediction, cis-regulatory, TSS window, DeepSEA, DeepSTARR, BigBird splice, MCP genomics
11 openclaw:
12 primaryEnv: GI_API_KEY
13 envVars:
14 - name: GI_API_KEY
15 required: false
16 description: Optional gi_ bearer key for the REST /v1 API and higher MCP rate and concurrency limits. The hosted MCP demo runs keyless; request a key at contact@genomicintelligence.ai.
17---
19# Genomic Intelligence — DNA Sequence Models
21Genomic Intelligence (GI) serves transformer DNA language models over six
22sequence-analysis tasks on managed GPUs. Give it a **gene symbol**, a **genomic
23region**, or a **DNA/FASTA sequence**; it returns structured predictions —
24promoter regions, splice sites, enhancer activity, chromatin state, expression
25(log TPM), and de-novo gene annotation. Nothing runs locally: no model weights,
26no GPU, no heavy Python stack. It is a thin client over a hosted, versioned
27inference API.
29**Official docs:** [docs.genomicintelligence.ai](https://docs.genomicintelligence.ai) ·
30REST contract at [api.genomicintelligence.ai/v1/openapi.json](https://api.genomicintelligence.ai/v1/openapi.json) ·
31hosted MCP server at `https://mcp.genomicintelligence.ai/mcp`

Genomic Intelligence — DNA Sequence Models

Genomic Intelligence (GI) serves transformer DNA language models over six sequence-analysis tasks on managed GPUs. Give it a gene symbol, a genomic region, or a DNA/FASTA sequence; it returns structured predictions — promoter regions, splice sites, enhancer activity, chromatin state, expression (log TPM), and de-novo gene annotation. Nothing runs locally: no model weights, no GPU, no heavy Python stack. It is a thin client over a hosted, versioned inference API.

Official docs: docs.genomicintelligence.ai · REST contract at api.genomicintelligence.ai/v1/openapi.json · hosted MCP server at https://mcp.genomicintelligence.ai/mcp

Raw lines 1 to 32
Uses an outside tool MCP :31
33## When to use this skill
35Use GI when the user has DNA and wants a model prediction:
37- **Find promoters** in a genomic region (`promoter`)
38- **Predict splice** donor/acceptor sites (`splice`)
39- **Score enhancer activity** — developmental & housekeeping (`enhancer`)
40- **Annotate chromatin state** across hundreds of tracks (`chromatin`)
41- **Predict expression** as log(TPM+1) from a sequence + cell-type context (`expression`)
42- **Annotate genes/transcripts** de novo, no reference needed (`annotation`)
43- **Find the genes in a region and predict each one's expression** (composite)
45Not for local alignment, variant calling, or file I/O — use a local tool
46(BioPython, bcftools) for those. GI is for **model inference from sequence**.
48> Research and development use. Not for clinical or diagnostic decisions.

When to use this skill

Use GI when the user has DNA and wants a model prediction:

  • Find promoters in a genomic region (promoter)
  • Predict splice donor/acceptor sites (splice)
  • Score enhancer activity — developmental & housekeeping (enhancer)
  • Annotate chromatin state across hundreds of tracks (chromatin)
  • Predict expression as log(TPM+1) from a sequence + cell-type context (expression)
  • Annotate genes/transcripts de novo, no reference needed (annotation)
  • Find the genes in a region and predict each one's expression (composite)

Not for local alignment, variant calling, or file I/O — use a local tool (BioPython, bcftools) for those. GI is for model inference from sequence.

Research and development use. Not for clinical or diagnostic decisions.
Raw lines 33 to 49
50## Two ways to call GI

Two ways to call GI

Raw lines 50 to 51
52### Hosted MCP server (keyless; preferred on MCP hosts)
54GI hosts an MCP server at `https://mcp.genomicintelligence.ai/mcp` (Streamable
55HTTP). When your agent host supports MCP, prefer it: it works **keyless** against
56a rate- and concurrency-limited public demo tier, and an optional `gi_` bearer
57key raises those limits. It exposes acquisition tools that return a **sequence handle**
58(`sequence_ref`) and `predict_*` tools that take that handle, so large sequences
59stay out of the context. See [MCP workflow](#mcp-workflow-handle-based) below and

Hosted MCP server (keyless; preferred on MCP hosts)

GI hosts an MCP server at https://mcp.genomicintelligence.ai/mcp (Streamable HTTP). When your agent host supports MCP, prefer it: it works keyless against a rate- and concurrency-limited public demo tier, and an optional gi_ bearer key raises those limits. It exposes acquisition tools that return a sequence handle (sequence_ref) and predict_* tools that take that handle, so large sequences stay out of the context. See MCP workflow below and references/mcp.md.

Raw lines 52 to 61
Uses an outside tool MCP :52
Reads a file references/mcp.md :60
62### REST API (universal)
64Plain HTTP with `requests` against `https://api.genomicintelligence.ai/v1`. The
65REST path **requires** a `GI_API_KEY` (a `gi_` bearer). Use it on any host, in
66scripts, or when you need the raw envelope. See [Core REST workflow](#core-rest-workflow).

REST API (universal)

Plain HTTP with requests against https://api.genomicintelligence.ai/v1. The REST path requires a GI_API_KEY (a gi_ bearer). Use it on any host, in scripts, or when you need the raw envelope. See Core REST workflow.

Raw lines 62 to 67
68## Access and authentication
701. The **hosted MCP demo is keyless** — try it with nothing set.
712. REST prediction and job operations need a key, sent as `Authorization: Bearer <key>`.
72 Public `GET /v1/tasks/{task}/models` discovery needs no key and is rate-limited
73 by source IP; inspect model windows and bounds before requesting access.
74 See the [current authentication contract](https://docs.genomicintelligence.ai/).
75 Request a prediction key at [contact@genomicintelligence.ai](mailto:contact@genomicintelligence.ai).
763. **Never hardcode the key.** Read it from the `GI_API_KEY` environment variable
77 (or a `.env` via `python-dotenv`). Never commit keys.
79```bash
80export GI_API_KEY="gi_yourkeyhere" # optional for MCP; required for REST
81export GI_BASE_URL="https://api.genomicintelligence.ai" # override for staging
82```
84Keys are scoped to a partner tier with concurrency and per-minute caps. A `429`
85means you hit a cap — back off and retry, or ask GI to raise your tier.

Access and authentication

  1. The hosted MCP demo is keyless — try it with nothing set.
  2. REST prediction and job operations need a key, sent as Authorization: Bearer <key>. Public GET /v1/tasks/{task}/models discovery needs no key and is rate-limited by source IP; inspect model windows and bounds before requesting access. See the current authentication contract. Request a prediction key at contact@genomicintelligence.ai.
  3. Never hardcode the key. Read it from the GI_API_KEY environment variable (or a .env via python-dotenv). Never commit keys.
export GI_API_KEY="gi_yourkeyhere"     # optional for MCP; required for REST
export GI_BASE_URL="https://api.genomicintelligence.ai"   # override for staging

Keys are scoped to a partner tier with concurrency and per-minute caps. A 429 means you hit a cap — back off and retry, or ask GI to raise your tier.

Raw lines 68 to 86
Uses an outside tool MCP :70
87## The six tasks
89Each task is **its own published operation** with its own request schema, its own
90minimum length, and its own closed `options` object — `POST
91/v1/tasks/promoter/predict`, `/v1/tasks/splice/predict`,
92`/v1/tasks/enhancer/predict`, `/v1/tasks/chromatin/predict`,
93`/v1/tasks/annotation/predict`, `/v1/tasks/expression/predict`. Each path is a
94literal string, so nothing needs to be constructed, and there is no shared
95`PredictRequest` schema. Body is `{sequence, sequence_name?, model?,
96options?}`, returning a `{data, meta}` envelope. What differs per task:
98| Task | Recommended mode | Accepted length | `context_window_bp` | Notes |
99|---|---|---|---|---|
100| `promoter` | sync | 300–500,000 bp | 2,000 bp | sliding-window promoter regions |
101| `splice` | sync | 100–500,000 bp | 15,000 bp | donor/acceptor sites (long-context BigBird); strand-specific — feed transcript orientation |
102| `enhancer` | sync | 50–500,000 bp | 249 bp | dev + housekeeping scores (DeepSTARR, *Drosophila*) |
103| `chromatin` | sync | 200–500,000 bp | 1,000 bp | hundreds of tracks (DeepSEA) |
104| `expression` | sync | **9,198–500,000 bp** | n/a (`trained_window_bp` 9,198) | log(TPM+1); needs `tss_index` unless exactly 9,198 bp, plus a cell-type `description` |
105| `annotation` | async | 1,000–500,000 bp | n/a | de-novo transcripts; submit + poll; sync JSON above 200,000 bp is `413 sync_too_large` |
107`Recommended mode` is guidance, not a constraint — every task accepts both. Omit `Prefer` for a synchronous `200`; send `Prefer: respond-async` for a `202` plus `GET /v1/tasks/jobs/{job_id}`. The one enforced limit is per operation: where `/v1/openapi.json` publishes `x-sync-limit-bp` on a `POST`, a synchronous JSON request above that length is `413 sync_too_large` — 200,000 bp on `annotation` and 50,000 bp on the composite workflow in contract revision 16. Read the field rather than memorising the numbers. Annotation BED/GFF3 stays synchronous at any admitted length and can time out; the other five predict tasks have no hard sync cap.
109**The minimum is admission control, not regime.** A request above the floor but
110shorter than the selected model's `bio_spec.context_window_bp` is *accepted and
111scored* — against a window padded out to the context window. Enhancer is the
112sharp case: the floor is 50 bp but the context window is 249 bp, so 50–248 bp is
113scored mostly on padding. Compare your length against
114`context_window_bp` from `GET /v1/tasks/{task}/models` to know whether the model
115saw real sequence. Longer-than-context input is fine — the scanner steps a
116prediction window at a time and pads only the final partial window.
118Under the floor and over the 500,000 bp cap are **both `422 validation_failed`**
119at `loc ["body","sequence"]`; over-length is *not* a `413`. All lengths are
120measured after whitespace is stripped, so a line-wrapped FASTA body can be pasted
121verbatim (a `>` header line still fails the alphabet check).
123`options` is typed and **closed** (`additionalProperties: false`) per task — an
124unknown key is a hard `422 validation_failed` with `type: "extra_forbidden"`,
125never ignored:
127| Task | `options` keys |
128|---|---|
129| promoter | `threshold` (0–1, default 0.5) |
130| splice | `threshold` (0–1, default 0.5), `site_types` (subset of `["donor","acceptor"]`, default both) |
131| enhancer | *(none)* |
132| chromatin | `threshold` (0–1, default 0.5) |
133| annotation | `batch_size` (1–128, default 8), `shift_coordinates`, `reverse_complement` (default true) |
134| expression | `description` — **required**, and the only key |
136`Prefer: respond-async` is a declared header on **all six** predict operations
137and on the composite, not just `annotation` — see [Async](#async-any-task-recommended-for-annotation).
139**Omit `model` and the API uses the task's default** — that is the recommended
140call. Default model IDs are intentionally **not** documented here: defaults
141change and retired IDs fail hard, so never hardcode one. To pin a model, or to
142pick a non-human one (Drosophila, yeast, and Arabidopsis models exist for several
143tasks), discover IDs at call time with `GET /v1/tasks/{task}/models` (REST) or
144`list_models` (MCP) — and **never invent one**. Full per-task output shapes are
147`expression` is the strictest of the six: alone among them its schema requires
148`options` as well as `sequence`. Three hard rules it enforces — every violation
149is a `422`, nothing is padded or clamped, and there is no opt-out flag, header,
150or query parameter:
152- **It always scores exactly one 9,198 bp TSS-centred window** —
153 `sequence[tss_index-4599 : tss_index+4599]`. The endpoint itself accepts
154 **9,198–500,000 bp**; anything below 9,198 bp is rejected outright.
155- **`tss_index` is required unless the sequence is exactly 9,198 bp.** It is the
156 0-based TSS offset into the **whitespace-stripped** sequence, bounded by
157 `4599 ≤ tss_index ≤ len(sequence) − 4599`. At exactly 9,198 bp it defaults to
158 4,599, the only legal value there. So you may submit a whole locus (up to
159 500 kb) and let the server cut the window — but the server does **not**
160 discover the TSS for you (that is the composite workflow's job), and does
161 **not** reverse-complement: submit gene-sense sequence.
162- **`options.description`** — a cell-type / assay string (e.g. `"K562 cells"`) —
163 is required, and is the **only** key `expression` accepts inside `options`.
164 Unknown top-level body fields are rejected too.
166> Note: the legal `tss_index` range is wide, so an offset that is merely
167> *wrong* (counted over raw FASTA characters including newlines, or relative to
168> a locus start rather than the submitted slice) does not error — it returns a
169> confident `200` for the wrong window. Assert on
170> `meta.task_specific_counts.scored_window` / `.tss_index` in the response.
171> The submitted length is `meta.sequence_length`; the scored width is always
172> 9,198, i.e. `scored_window[1] - scored_window[0]`. In revision 16,
173> `data.input` contains only `sequence_name`, `description`, and `tss_index`;
174> it does not contain the submitted length or scored window.
176> Both `tss_index` violations — "required unless exactly 9,198 bp" and the range
177> check — come from a whole-model validator, so they surface at the body level
178> rather than under `tss_index`. Match on `error.code == "validation_failed"`
179> and use the message for display only. Any `loc` tuple quoted in this skill is
180> illustrative of that shape, not part of the contract: it is not published in
181> the schema and must not be branched on.

The six tasks

Each task is its own published operation with its own request schema, its own minimum length, and its own closed options object — POST /v1/tasks/promoter/predict, /v1/tasks/splice/predict, /v1/tasks/enhancer/predict, /v1/tasks/chromatin/predict, /v1/tasks/annotation/predict, /v1/tasks/expression/predict. Each path is a literal string, so nothing needs to be constructed, and there is no shared PredictRequest schema. Body is {sequence, sequence_name?, model?, options?}, returning a {data, meta} envelope. What differs per task:

TaskRecommended modeAccepted lengthcontext_window_bpNotes
promotersync300–500,000 bp2,000 bpsliding-window promoter regions
splicesync100–500,000 bp15,000 bpdonor/acceptor sites (long-context BigBird); strand-specific — feed transcript orientation
enhancersync50–500,000 bp249 bpdev + housekeeping scores (DeepSTARR, Drosophila)
chromatinsync200–500,000 bp1,000 bphundreds of tracks (DeepSEA)
expressionsync9,198–500,000 bpn/a (trained_window_bp 9,198)log(TPM+1); needs tss_index unless exactly 9,198 bp, plus a cell-type description
annotationasync1,000–500,000 bpn/ade-novo transcripts; submit + poll; sync JSON above 200,000 bp is 413 sync_too_large

Recommended mode is guidance, not a constraint — every task accepts both. Omit Prefer for a synchronous 200; send Prefer: respond-async for a 202 plus GET /v1/tasks/jobs/{job_id}. The one enforced limit is per operation: where /v1/openapi.json publishes x-sync-limit-bp on a POST, a synchronous JSON request above that length is 413 sync_too_large — 200,000 bp on annotation and 50,000 bp on the composite workflow in contract revision 16. Read the field rather than memorising the numbers. Annotation BED/GFF3 stays synchronous at any admitted length and can time out; the other five predict tasks have no hard sync cap.

The minimum is admission control, not regime. A request above the floor but shorter than the selected model's bio_spec.context_window_bp is accepted and scored — against a window padded out to the context window. Enhancer is the sharp case: the floor is 50 bp but the context window is 249 bp, so 50–248 bp is scored mostly on padding. Compare your length against context_window_bp from GET /v1/tasks/{task}/models to know whether the model saw real sequence. Longer-than-context input is fine — the scanner steps a prediction window at a time and pads only the final partial window.

Under the floor and over the 500,000 bp cap are **both 422 validation_failed** at loc ["body","sequence"]; over-length is not a 413. All lengths are measured after whitespace is stripped, so a line-wrapped FASTA body can be pasted verbatim (a > header line still fails the alphabet check).

options is typed and closed (additionalProperties: false) per task — an unknown key is a hard 422 validation_failed with type: "extra_forbidden", never ignored:

Taskoptions keys
promoterthreshold (0–1, default 0.5)
splicethreshold (0–1, default 0.5), site_types (subset of ["donor","acceptor"], default both)
enhancer(none)
chromatinthreshold (0–1, default 0.5)
annotationbatch_size (1–128, default 8), shift_coordinates, reverse_complement (default true)
expressiondescription — required, and the only key

Prefer: respond-async is a declared header on all six predict operations and on the composite, not just annotation — see Async.

**Omit model and the API uses the task's default — that is the recommended call. Default model IDs are intentionally not** documented here: defaults change and retired IDs fail hard, so never hardcode one. To pin a model, or to pick a non-human one (Drosophila, yeast, and Arabidopsis models exist for several tasks), discover IDs at call time with GET /v1/tasks/{task}/models (REST) or list_models (MCP) — and never invent one. Full per-task output shapes are in references/tasks.md.

expression is the strictest of the six: alone among them its schema requires options as well as sequence. Three hard rules it enforces — every violation is a 422, nothing is padded or clamped, and there is no opt-out flag, header, or query parameter:

  • It always scores exactly one 9,198 bp TSS-centred window — sequence[tss_index-4599 : tss_index+4599]. The endpoint itself accepts 9,198–500,000 bp; anything below 9,198 bp is rejected outright.
  • **tss_index is required unless the sequence is exactly 9,198 bp. It is the 0-based TSS offset into the whitespace-stripped** sequence, bounded by 4599 ≤ tss_index ≤ len(sequence) − 4599. At exactly 9,198 bp it defaults to 4,599, the only legal value there. So you may submit a whole locus (up to 500 kb) and let the server cut the window — but the server does not discover the TSS for you (that is the composite workflow's job), and does not reverse-complement: submit gene-sense sequence.
  • **options.description** — a cell-type / assay string (e.g. "K562 cells") — is required, and is the only key expression accepts inside options. Unknown top-level body fields are rejected too.
Note: the legal tss_index range is wide, so an offset that is merely wrong (counted over raw FASTA characters including newlines, or relative to a locus start rather than the submitted slice) does not error — it returns a confident 200 for the wrong window. Assert on meta.task_specific_counts.scored_window / .tss_index in the response. The submitted length is meta.sequence_length; the scored width is always 9,198, i.e. scored_window[1] - scored_window[0]. In revision 16, data.input contains only sequence_name, description, and tss_index; it does not contain the submitted length or scored window. Both tss_index violations — "required unless exactly 9,198 bp" and the range check — come from a whole-model validator, so they surface at the body level rather than under tss_index. Match on error.code == "validation_failed" and use the message for display only. Any loc tuple quoted in this skill is illustrative of that shape, not part of the contract: it is not published in the schema and must not be branched on.
Raw lines 87 to 182
Uses an outside tool MCP :144
Reads a file references/tasks.md :145
183## Sequence acquisition
185You rarely start from a raw 9,198 bp string. Acquire sequence first:
187- **From a gene symbol** → MCP `fetch_ensembl_sequence(gene=...)`; **from
188 coordinates** → `fetch_region(region=...)`. Both acquire public reference sequence (no key), using a bundled coordinate
189 catalog, cache, UCSC, or Ensembl; retain the returned provenance. REST users can query Ensembl REST directly. (`find_genes` is
190 the annotation task, not an acquisition tool.)
191- **For `expression`** → use the TSS-centred fetch so the window is exactly
192 9,198 bp. MCP: `fetch_gene_for_expression` (handles the centring). Otherwise
193 fetch a wider locus and pass the TSS as `tss_index` so the server cuts the
194 window — but compute that offset on the stripped nucleotide string, not on
195 file characters.
196- **From a local FASTA** → MCP `store_inline_sequence`, or read the file yourself
197 for REST. (`load_local_fasta` exists only in local deployments, not on the
198 hosted server.)
199- **A demo sequence** → MCP `load_demo_sequence(name=...)` returns a ready handle
200 for a keyless smoke test; `name` is required.
202See `references/sequence-acquisition.md` for the exact Ensembl calls and the
203expression-window math.

Sequence acquisition

You rarely start from a raw 9,198 bp string. Acquire sequence first:

  • From a gene symbol → MCP fetch_ensembl_sequence(gene=...); from coordinates → fetch_region(region=...). Both acquire public reference sequence (no key), using a bundled coordinate catalog, cache, UCSC, or Ensembl; retain the returned provenance. REST users can query Ensembl REST directly. (find_genes is the annotation task, not an acquisition tool.)
  • **For expression** → use the TSS-centred fetch so the window is exactly 9,198 bp. MCP: fetch_gene_for_expression (handles the centring). Otherwise fetch a wider locus and pass the TSS as tss_index so the server cuts the window — but compute that offset on the stripped nucleotide string, not on file characters.
  • From a local FASTA → MCP store_inline_sequence, or read the file yourself for REST. (load_local_fasta exists only in local deployments, not on the hosted server.)
  • A demo sequence → MCP load_demo_sequence(name=...) returns a ready handle for a keyless smoke test; name is required.

See references/sequence-acquisition.md for the exact Ensembl calls and the expression-window math.

Raw lines 183 to 204
Uses an outside tool MCP :187
205## Core REST workflow
207The following transport recipe was tested with mocked responses, not authenticated
208inference. Supply a task-appropriate `seq` before calling it. Use the exact
209expression-context wording consistently when comparing predictions.
211```python
212import os
213import time
214import requests
216BASE = os.environ.get("GI_BASE_URL", "https://api.genomicintelligence.ai").rstrip("/")
217HEADERS = {"Authorization": f"Bearer {os.environ['GI_API_KEY']}"}
218TASKS = {"promoter", "splice", "enhancer", "chromatin", "annotation", "expression"}
220def predict(task, sequence, sequence_name, model=None, options=None, tss_index=None):
221 if task not in TASKS:
222 raise ValueError("Unknown GI task")
223 body = {"sequence": sequence, "sequence_name": sequence_name}
224 if model is not None:
225 body["model"] = model
226 if options is not None:
227 body["options"] = options
228 if tss_index is not None:
229 if task != "expression":
230 raise ValueError("tss_index is expression-only")
231 body["tss_index"] = tss_index
232 r = requests.post(f"{BASE}/v1/tasks/{task}/predict", headers=HEADERS,
233 json=body, timeout=(10, 300))
234 r.raise_for_status()
235 if r.status_code != 200:
236 raise RuntimeError(f"Unexpected prediction status {r.status_code}")
237 return r.json()
239# After acquiring and checking an appropriate promoter sequence:
240# out = predict("promoter", seq, "TP53_region")
241# print(out["meta"]["task_specific_counts"]["regions_found"])
243# A validated gene-sense expression window, or longer locus with known TSS:
244# out = predict("expression", locus_seq, "HBB", tss_index=tss_offset,
245# options={"description": "polyA plus RNA-seq; Homo sapiens K562"})
246# assert out["meta"]["sequence_length"] == len("".join(locus_seq.split()))
247# assert out["meta"]["task_specific_counts"]["scored_window"] == [tss_offset-4599, tss_offset+4599]
248# print(out["data"]["prediction"]["expression_log_tpm"])
249```
251`data.summary` is for display: its keys may change without a contract revision.
252Use the declared fields in `data` and `meta.task_specific_counts` for computation.
253A timeout or proxy error may have a non-JSON body; it does not establish that the
254inference never ran. Preserve the request ID and avoid blind POST resubmission.

Core REST workflow

The following transport recipe was tested with mocked responses, not authenticated inference. Supply a task-appropriate seq before calling it. Use the exact expression-context wording consistently when comparing predictions.

import os
import time
import requests

BASE = os.environ.get("GI_BASE_URL", "https://api.genomicintelligence.ai").rstrip("/")
HEADERS = {"Authorization": f"Bearer {os.environ['GI_API_KEY']}"}
TASKS = {"promoter", "splice", "enhancer", "chromatin", "annotation", "expression"}

def predict(task, sequence, sequence_name, model=None, options=None, tss_index=None):
    if task not in TASKS:
        raise ValueError("Unknown GI task")
    body = {"sequence": sequence, "sequence_name": sequence_name}
    if model is not None:
        body["model"] = model
    if options is not None:
        body["options"] = options
    if tss_index is not None:
        if task != "expression":
            raise ValueError("tss_index is expression-only")
        body["tss_index"] = tss_index
    r = requests.post(f"{BASE}/v1/tasks/{task}/predict", headers=HEADERS,
                      json=body, timeout=(10, 300))
    r.raise_for_status()
    if r.status_code != 200:
        raise RuntimeError(f"Unexpected prediction status {r.status_code}")
    return r.json()

# After acquiring and checking an appropriate promoter sequence:
# out = predict("promoter", seq, "TP53_region")
# print(out["meta"]["task_specific_counts"]["regions_found"])

# A validated gene-sense expression window, or longer locus with known TSS:
# out = predict("expression", locus_seq, "HBB", tss_index=tss_offset,
#               options={"description": "polyA plus RNA-seq; Homo sapiens K562"})
# assert out["meta"]["sequence_length"] == len("".join(locus_seq.split()))
# assert out["meta"]["task_specific_counts"]["scored_window"] == [tss_offset-4599, tss_offset+4599]
# print(out["data"]["prediction"]["expression_log_tpm"])

data.summary is for display: its keys may change without a contract revision. Use the declared fields in data and meta.task_specific_counts for computation. A timeout or proxy error may have a non-JSON body; it does not establish that the inference never ran. Preserve the request ID and avoid blind POST resubmission.

Raw lines 205 to 255
256### Async (any task; recommended for annotation)
258Send `Prefer: respond-async` on any of the six tasks or the composite. A `202`
259is `{data: {job_id, status: "accepted", links}, meta}`. `Content-Location` and
260`X-Job-Id` identify the same job. Async is JSON-only; text format plus async is
261`400`. Save the job ID before polling. This bounded polling example surfaces
262HTTP failures (including `429` and `410`) for the caller to handle:
264```python
265def submit_annotation(sequence, sequence_name):
266 r = requests.post(f"{BASE}/v1/tasks/annotation/predict",
267 headers={**HEADERS, "Prefer": "respond-async"},
268 json={"sequence": sequence, "sequence_name": sequence_name},
269 timeout=(10, 30))
270 r.raise_for_status()
271 if r.status_code != 202:
272 raise RuntimeError(f"Unexpected submission status {r.status_code}")
273 return r.json()["data"]["job_id"]
275def wait_for_job(job_id, max_polls=120):
276 if max_polls < 1:
277 raise ValueError("max_polls must be positive")
278 for attempt in range(max_polls):
279 r = requests.get(f"{BASE}/v1/tasks/jobs/{job_id}", headers=HEADERS,
280 timeout=(10, 30))
281 r.raise_for_status() # failed job -> its underlying 4xx/5xx, not 200
282 if r.status_code == 200:
283 return r.json()
284 if r.status_code != 202:
285 raise RuntimeError(f"Unexpected polling status {r.status_code}")
286 if attempt + 1 < max_polls:
287 time.sleep(5)
288 raise TimeoutError(f"Polling stopped; resume this job rather than resubmit: {job_id}")
290# job_id = submit_annotation(seq, "TP53_region") # persist this ID
291# result = wait_for_job(job_id)
292# assert result["data"]["task"] == "annotation"
293# transcripts = result["data"]["transcripts"]
294```
296`200` is completion; `202` contains `data.status` and `data.progress`.
297Unknown/not-owned jobs are `404`; expired jobs are `410 job_expired`.
298Results are documented as retained 24 hours from last activity; save results
299locally. Job listing is a recent, bounded list, not a paginated archive.

Async (any task; recommended for annotation)

Send Prefer: respond-async on any of the six tasks or the composite. A 202 is {data: {job_id, status: "accepted", links}, meta}. Content-Location and X-Job-Id identify the same job. Async is JSON-only; text format plus async is 400. Save the job ID before polling. This bounded polling example surfaces HTTP failures (including 429 and 410) for the caller to handle:

def submit_annotation(sequence, sequence_name):
    r = requests.post(f"{BASE}/v1/tasks/annotation/predict",
                      headers={**HEADERS, "Prefer": "respond-async"},
                      json={"sequence": sequence, "sequence_name": sequence_name},
                      timeout=(10, 30))
    r.raise_for_status()
    if r.status_code != 202:
        raise RuntimeError(f"Unexpected submission status {r.status_code}")
    return r.json()["data"]["job_id"]

def wait_for_job(job_id, max_polls=120):
    if max_polls < 1:
        raise ValueError("max_polls must be positive")
    for attempt in range(max_polls):
        r = requests.get(f"{BASE}/v1/tasks/jobs/{job_id}", headers=HEADERS,
                         timeout=(10, 30))
        r.raise_for_status()  # failed job -> its underlying 4xx/5xx, not 200
        if r.status_code == 200:
            return r.json()
        if r.status_code != 202:
            raise RuntimeError(f"Unexpected polling status {r.status_code}")
        if attempt + 1 < max_polls:
            time.sleep(5)
    raise TimeoutError(f"Polling stopped; resume this job rather than resubmit: {job_id}")

# job_id = submit_annotation(seq, "TP53_region")  # persist this ID
# result = wait_for_job(job_id)
# assert result["data"]["task"] == "annotation"
# transcripts = result["data"]["transcripts"]

200 is completion; 202 contains data.status and data.progress. Unknown/not-owned jobs are 404; expired jobs are 410 job_expired. Results are documented as retained 24 hours from last activity; save results locally. Job listing is a recent, bounded list, not a paginated archive.

Raw lines 256 to 300
301## MCP workflow (handle-based)
303On an MCP host, acquire a handle, then predict against it — sequences stay out of
304the context:
306```
307# 1. Acquire a sequence handle (each returns data.ref, passed as sequence_ref):
308load_demo_sequence(name="promoter_tp53") # keyless smoke test; name is required
309fetch_ensembl_sequence(gene="TP53", flank_bp=5000) # include regulatory context
310fetch_region(region="chr11:5,225,000-5,235,000") # coordinates -> handle
311fetch_gene_for_expression(gene="HBB") # TSS-centred 9,198 bp handle for expression
313# 2. Predict against the handle:
314predict_promoter(sequence_ref=<ref>)
315predict_expression(sequence_ref=<ref>, description="K562 cells")
316predict_splice(sequence_ref=<ref>) # + predict_enhancer / predict_chromatin
318# 3. Annotation on MCP is `find_genes` (there is no predict_annotation).
319# It takes a handle, not a region, and runs async internally:
320find_genes(sequence_ref=<ref>) # wait=True (default) returns the result
321find_genes(sequence_ref=<ref>, wait=False) # own key only -> job_id; poll get_job(job_id)
323# Acquisition returns data.ref; use that value as sequence_ref.
324# Discover models with list_models(task); reference context lives in the
325# gi://models, gi://docs/tasks, and gi://account MCP resources.
326```
328The shared demo disables `get_job`, `list_jobs`, and detached `wait=False`.
329Keep `wait=True` there; a wait timeout is an error, not a recoverable job handle.
330See [MCP details](references/mcp.md) for resources, result envelopes and lifetimes.

MCP workflow (handle-based)

On an MCP host, acquire a handle, then predict against it — sequences stay out of the context:

# 1. Acquire a sequence handle (each returns data.ref, passed as sequence_ref):
load_demo_sequence(name="promoter_tp53")  # keyless smoke test; name is required
fetch_ensembl_sequence(gene="TP53", flank_bp=5000)  # include regulatory context
fetch_region(region="chr11:5,225,000-5,235,000")   # coordinates -> handle
fetch_gene_for_expression(gene="HBB")     # TSS-centred 9,198 bp handle for expression

# 2. Predict against the handle:
predict_promoter(sequence_ref=<ref>)
predict_expression(sequence_ref=<ref>, description="K562 cells")
predict_splice(sequence_ref=<ref>)        # + predict_enhancer / predict_chromatin

# 3. Annotation on MCP is `find_genes` (there is no predict_annotation).
#    It takes a handle, not a region, and runs async internally:
find_genes(sequence_ref=<ref>)            # wait=True (default) returns the result
find_genes(sequence_ref=<ref>, wait=False)  # own key only -> job_id; poll get_job(job_id)

# Acquisition returns data.ref; use that value as sequence_ref.
# Discover models with list_models(task); reference context lives in the
# gi://models, gi://docs/tasks, and gi://account MCP resources.

The shared demo disables get_job, list_jobs, and detached wait=False. Keep wait=True there; a wait timeout is an error, not a recoverable job handle. See MCP details for resources, result envelopes and lifetimes.

Raw lines 301 to 331
Uses an outside tool MCP :301
Reads a file references/mcp.md :330
332## Composite: find genes, then predict expression
334To answer "what genes are in this region and how are they expressed?", use the
335composite:
337- **MCP:** `find_genes_and_predict_expression(sequence_ref=..., description=...)`
338 — takes a **handle, not a region** (acquire one with `fetch_region` first);
339 `description` is required. Finds genes in the sequence and returns an
340 expression prediction for each.
341- **REST:** one call — `POST /v1/workflows/find-genes-and-predict-expression`,
342 body `{sequence, options}` with `sequence` 1,000–500,000 bp and
343 `options.description` (cell type / assay) required; a missing or empty
344 description is a `422 validation_failed`. It annotates, centres a 9,198 bp
345 window on each discovered gene's TSS (padding with `N` up to half the window
346 rather than dropping an edge gene), and returns a prediction per gene.
347 `meta.task_specific_counts` = `{genes_found, genes_predicted, genes_skipped}`
348 with `genes_predicted + genes_skipped == genes_found`; per-gene causes in
349 `data.expression_predictions[].skip_reason`. Above **50,000 bp** (its `x-sync-limit-bp`) it forces
350 async: a synchronous request over that size is `413 sync_too_large` with
351 `error.details = {sequence_length, threshold}` — retry the same body with
352 `Prefer: respond-async`.
354The API also publishes a separate, under-development VCF workflow. Its outputs
355are not established model results when `meta.model` is absent; see
356[the bounded contract note](references/api-and-auth.md#vcf-workflow-boundary).

Composite: find genes, then predict expression

To answer "what genes are in this region and how are they expressed?", use the composite:

  • MCP: find_genes_and_predict_expression(sequence_ref=..., description=...) — takes a handle, not a region (acquire one with fetch_region first); description is required. Finds genes in the sequence and returns an expression prediction for each.
  • REST: one call — POST /v1/workflows/find-genes-and-predict-expression, body {sequence, options} with sequence 1,000–500,000 bp and options.description (cell type / assay) required; a missing or empty description is a 422 validation_failed. It annotates, centres a 9,198 bp window on each discovered gene's TSS (padding with N up to half the window rather than dropping an edge gene), and returns a prediction per gene. meta.task_specific_counts = {genes_found, genes_predicted, genes_skipped} with genes_predicted + genes_skipped == genes_found; per-gene causes in data.expression_predictions[].skip_reason. Above 50,000 bp (its x-sync-limit-bp) it forces async: a synchronous request over that size is 413 sync_too_large with error.details = {sequence_length, threshold} — retry the same body with Prefer: respond-async.

The API also publishes a separate, under-development VCF workflow. Its outputs are not established model results when meta.model is absent; see the bounded contract note.

Raw lines 332 to 357
Uses an outside tool MCP :337
358## Errors
360| Code | `error.code` | Meaning | Action |
361|---|---|---|---|
362| 400 | `bad_request` | Malformed request | Check the body shape |
363| 401 / 403 | `unauthorized` / `forbidden` | Missing/invalid key (REST) | Set `GI_API_KEY`; or use the keyless MCP demo |
364| 404 | `not_found` | **Unknown task** (`/v1/tasks/bogus/predict`) or unknown job | Check the task name — an unrecognised task is a 404, not a 422 |
365| 413 | `payload_too_large` | Raw request body over **16 MiB** | Split the input — this is the body cap, not the sequence cap |
366| 410 | `job_expired` | Result retention elapsed | Recover saved results or deliberately submit new work |
367| 413 | `sync_too_large` | Synchronous JSON request above the operation's `x-sync-limit-bp` (200,000 bp on `annotation`, 50,000 bp on the composite) | Retry with `Prefer: respond-async` |
368| 415 | `unsupported_format` | Unsupported `format` query value | Use a format the task supports; there is no silent fallback to JSON |
369| 422 | `validation_failed` | The most common failure: sequence **under the task floor or over 500,000 bp**, expression below 9,198 bp, a missing/out-of-range `tss_index`, a missing `options.description`, or **any unknown body or `options` key**; also the splice response cap | Read the message; fix the body |
370| 429 | `rate_limited` / `too_many_requests` | Rate / concurrency cap | Back off (honour `Retry-After`); ask GI to raise your tier |
371| 5xx | `internal_error` / `service_unavailable` / `model_loading` / `timeout` | Server error | Preserve request/job IDs; retry polling with backoff, avoid blind POST resubmission |
373`error.code` is a closed 21-value enum (`bad_request`, `unauthorized`,
374`forbidden`, `not_found`, `conflict`, `job_expired`, `payload_too_large`,
375`sync_too_large`, `unsupported_format`, `validation_failed`,
376`too_many_requests`, `rate_limited`, `internal_error`, `timeout`,
377`insufficient_memory`, `model_not_found`, `task_not_supported_by_model`,
378`model_loading`, `service_unavailable`, `http_error`, `unknown`); treat an
379unlisted value as a generic failure, not a parse error.
381**Branch first on `code`, never on message text or `loc`.** Pydantic request
382failures usually carry `details.errors`; the splice response cap instead carries
383`record_count`, `maximum_records`, `sequence_length`, and `threshold`. Handle
384these as distinct optional detail shapes. More than 20,000 splice records causes
385`422 validation_failed`, not a truncated result; raise the threshold and record
386that changed analysis setting. See [task caveats](references/tasks.md).
388For correlation, `error.request_id` and the `X-Request-Id` **header** are both
389documented on API responses, and success envelopes carry `meta.request_id`. Reading
390the header first remains a safe default.
391API responses document `RateLimit-Limit`, `RateLimit-Remaining`,
392`RateLimit-Reset`, `RateLimit-Policy`; a `429` adds `Retry-After`. The limit is a
393burst bucket, not rpm: the published `x-rate-limit-burst-divisor` is 6, so the
394sustained minute allowance is six times that header. Proxy failures may omit
395these headers and the usual JSON error envelope.
397Reviewed 2026-10-01 against live OpenAPI `info.version` **2026.09.22.2
398(af902d84)**, `x-contract-revision: 16`, and `gi-mcp` **0.1.0a21**.
399Record the contract revision, resolved model ID, assembly, strand/TSS
400provenance, options, and experimental description with results. Hash the exact
401submitted bases when available. A handle-only MCP acquisition returns a preview,
402not the full bases or a checksum: preserve its acquisition parameters and source
403release metadata, and do not invent a hash or claim byte-level verification.
404[Review evidence and limits](references/review.md) distinguish public discovery,
405source review, and mocked examples from inference validation.

Errors

Codeerror.codeMeaningAction
400bad_requestMalformed requestCheck the body shape
401 / 403unauthorized / forbiddenMissing/invalid key (REST)Set GI_API_KEY; or use the keyless MCP demo
404not_foundUnknown task (/v1/tasks/bogus/predict) or unknown jobCheck the task name — an unrecognised task is a 404, not a 422
413payload_too_largeRaw request body over 16 MiBSplit the input — this is the body cap, not the sequence cap
410job_expiredResult retention elapsedRecover saved results or deliberately submit new work
413sync_too_largeSynchronous JSON request above the operation's x-sync-limit-bp (200,000 bp on annotation, 50,000 bp on the composite)Retry with Prefer: respond-async
415unsupported_formatUnsupported format query valueUse a format the task supports; there is no silent fallback to JSON
422validation_failedThe most common failure: sequence under the task floor or over 500,000 bp, expression below 9,198 bp, a missing/out-of-range tss_index, a missing options.description, or **any unknown body or options key**; also the splice response capRead the message; fix the body
429rate_limited / too_many_requestsRate / concurrency capBack off (honour Retry-After); ask GI to raise your tier
5xxinternal_error / service_unavailable / model_loading / timeoutServer errorPreserve request/job IDs; retry polling with backoff, avoid blind POST resubmission

error.code is a closed 21-value enum (bad_request, unauthorized, forbidden, not_found, conflict, job_expired, payload_too_large, sync_too_large, unsupported_format, validation_failed, too_many_requests, rate_limited, internal_error, timeout, insufficient_memory, model_not_found, task_not_supported_by_model, model_loading, service_unavailable, http_error, unknown); treat an unlisted value as a generic failure, not a parse error.

**Branch first on code, never on message text or loc.** Pydantic request failures usually carry details.errors; the splice response cap instead carries record_count, maximum_records, sequence_length, and threshold. Handle these as distinct optional detail shapes. More than 20,000 splice records causes 422 validation_failed, not a truncated result; raise the threshold and record that changed analysis setting. See task caveats.

For correlation, error.request_id and the X-Request-Id header are both documented on API responses, and success envelopes carry meta.request_id. Reading the header first remains a safe default. API responses document RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, RateLimit-Policy; a 429 adds Retry-After. The limit is a burst bucket, not rpm: the published x-rate-limit-burst-divisor is 6, so the sustained minute allowance is six times that header. Proxy failures may omit these headers and the usual JSON error envelope.

Reviewed 2026-10-01 against live OpenAPI info.version 2026.09.22.2 (af902d84), x-contract-revision: 16, and gi-mcp 0.1.0a21. Record the contract revision, resolved model ID, assembly, strand/TSS provenance, options, and experimental description with results. Hash the exact submitted bases when available. A handle-only MCP acquisition returns a preview, not the full bases or a checksum: preserve its acquisition parameters and source release metadata, and do not invent a hash or claim byte-level verification. Review evidence and limits distinguish public discovery, source review, and mocked examples from inference validation.

Raw lines 358 to 406
Uses an outside tool MCP :363
Reads a file references/tasks.md :386
Reads a file references/review.md :404
407## Reference files
409- `references/tasks.md` — per-task output shapes, model registries, the async
410 annotation contract.
411- `references/api-and-auth.md` — REST endpoints, the `{data, meta}` envelope,
412 auth, base-URL override, tiers.
413- `references/mcp.md` — the hosted MCP tool list, the handle-based flow, and the
414 `gi://` resources.
415- `references/sequence-acquisition.md` — Ensembl fetch calls and the
416 expression-window (9,198 bp, TSS-centred) math, including `tss_index`.

Reference files

Raw lines 407 to 416
Reads a file references/tasks.md :409
Reads a file references/api-and-auth.md :411
Reads a file references/mcp.md :413
Uses an outside tool MCP :413

From K-Dense-AI/scientific-agent-skills at 5f2bc03, 2026-10-26. Not read by a model yet. Summary and techniques by Claude Opus 5.5 on 2026-11-06.