Skip to content

feat(genomic-intelligence-nim): hosted DNA-sequence prediction skill - #12

Open
boldakov wants to merge 27 commits into
NVIDIA-BioNeMo:mainfrom
genomicintelligence:feat/genomic-intelligence-nim
Open

feat(genomic-intelligence-nim): hosted DNA-sequence prediction skill#12
boldakov wants to merge 27 commits into
NVIDIA-BioNeMo:mainfrom
genomicintelligence:feat/genomic-intelligence-nim

Conversation

@boldakov

@boldakov boldakov commented Jul 16, 2026

Copy link
Copy Markdown

Summary

Adds a nim-skills/genomic-intelligence-nim skill that wraps Genomic Intelligence's hosted DNA language models, following the existing nim-skills/ pattern (a SKILL.md driving a bearer-authed REST call). One skill, six tasks over the shared POST /v1/tasks/{task}/predict contract:

  • promoter - promoter regions
  • splice - splice donor/acceptor sites
  • enhancer - developmental & housekeeping enhancer activity
  • chromatin - chromatin state across hundreds of tracks
  • expression - sequence -> expression, log(TPM+1)
  • annotation - de-novo gene/transcript structure

Inference is hosted - no local weights or GPU; the only dependency is requests.

Third-party hosted endpoint

Unlike the existing nim-skills/ (which target NVIDIA-operated endpoints), this skill calls an endpoint operated by Genomic Intelligence. The interface is identical to the hosted-NIM shape (HTTPS + Authorization: Bearer + JSON); it is hosted-only. Flagging it explicitly so you can weigh in, happy to adjust naming. Context in #5.

Why a scripts/ runner (not inline-only)

The published nim-skills/ inline their Python in SKILL.md. This skill instead ships a small, self-contained requests-only runner, because the surface (six tasks, async jobs, and an exact 9,198 bp window for expression task) does not inline cleanly. Shipping scripts/ is not prohibited by CONTRIBUTING, and other categories ship scripts too. SKILL.md still includes a minimal inline example for the simple sync case.

What's included

  • SKILL.md - frontmatter + task table + runner usage + a minimal inline example.
  • scripts/ - gi_predict.py (one CLI, six tasks, sync + async, writes report + JSON), gi_client.py (the /v1 client), and optional gi_fetch.py / gi_ensembl.py (gene/region -> FASTA via public Ensembl, incl. TSS-centring).
  • references/{api,tasks,authentication,sequence-acquisition,errors}.md.
  • evals/{evals,trigger_evals}.json - matching the repo's eval schema (promoter, expression-window, and annotation-async cases).
  • assets/demo/*.fa - one real reference FASTA per task (e.g. TP53 promoter, HBB splice, HBB-K562 9,198 bp expression window).

Auth

Users set GI_API_KEY (partner key, request at contact@genomicintelligence.ai). No key is committed; the skill resolves it from the environment.

Licensing / DCO

Contribution dual-licensed Apache-2.0 OR CC-BY-4.0; commits are DCO signed-off.

boldakov added 3 commits July 15, 2026 15:27
Add a nim-skills/ skill wrapping Genomic Intelligence's hosted DNA
language models over the /v1/tasks/{task}/predict contract: promoter,
splice, enhancer, chromatin, expression, and annotation. Bearer-authed
via GI_API_KEY; ships a requests-only runner (sync + async), references,
demo FASTAs, and evals. Context: NVIDIA-BioNeMo#5.

Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
…lugin-sync coverage

- register genomic-intelligence-nim in skills.sh.json and commit the
  plugin_sync --write payload, so the plugin-sync CI gate passes
- add evals/config.yml (schema_version 1, runtime_env GI_API_KEY) per the
  current nim-skills eval layout; keep one bounded eval case and defer the
  network-heavy expression/annotation cases, mirroring evo2-nim
- correct the enhancer minimum to the user-visible 50 bp and clarify the
  exact 9,198 bp expression window is a client-side guard, not an API limit
- stop pinning default model IDs in references: defaults resolve
  server-side, GET /v1/tasks/{task}/models is authoritative
- fix an off-by-one in the expression fixture header (chr11:5222473-5231670
  spans the 9,198 bases the record actually contains)

Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
@boldakov

Copy link
Copy Markdown
Author

Updated the branch: merged current main and brought the skill in line with the layout changes that landed after this PR was opened.

  • registered the skill in skills.sh.json and committed the plugin_sync --write payload, so the plugin-sync check should now pass
  • added evals/config.yml and trimmed evals.json to a single bounded case, with the network-heavy expression/annotation cases moved to deferred_evals (same shape as evo2-nim)
  • a few contract corrections in the references: the enhancer minimum is the user-visible 50 bp, the exact 9,198 bp expression window is documented as a client-side guard rather than an API limit, default model ids are no longer hardcoded, and an off-by-one in the expression fixture header is fixed

Two questions we didn't want to guess on:

  1. Placement. The repo has since moved to components.d entries sourcing from product-owned repos, with nim-skills/ described as BioNeMo NIM bundles. If you'd rather take this through components.d, we can publish the skill in a public genomicintelligence-owned repo and register an entry there instead.
  2. Eval credentials. The runnable eval needs GI_API_KEY (free partner key) instead of NGC_API_KEY, which would make it the first skill here whose eval uses a third-party credential. If that doesn't fit the harness, we can move that case to deferred_evals too.

Also glad to add a row to the README skill catalog, or leave that to you. Broader scope question is still #5.

boldakov and others added 11 commits August 18, 2026 23:02
…s_index

Genomic Intelligence shipped a new expression contract to production on
2026-08-18 (gpu_service 2026.08.18.1). Expression now has its own
published OpenAPI operation (POST /v1/tasks/expression/predict, schema
ExpressionPredictRequest) instead of the generic templated one, and the
request body gained a fifth field.

What the skill got wrong:

- the bound is 9,198-500,000 bp, not "exactly 9,198 bp". The model still
  scores exactly one 9,198 bp TSS-centred window, but the server slices
  it (sequence[tss_index-4599 : tss_index+4599]) before tokenizing;
- tss_index (0-based, into the whitespace-stripped sequence, bounded by
  4599 <= tss_index <= len-4599) is required unless the sequence is
  exactly 9,198 bp;
- options is a closed object whose only, required, key is description;
- violations are 422 validation_failed with no opt-out.

Code changes: gi_client.predict() forwards tss_index; gi_predict.py
gains --tss-index, replaces the exact-length gate with the real
floor/ceiling plus tss_index bounds checks, threads the flag through the
reproducibility command, and surfaces the response's tss_index /
scored_window so a caller can catch an in-range-but-wrong offset (which
returns a confident 200 for the wrong window).

The other five tasks, gi_fetch --for-expression (still builds an exact
9,198 bp window, so it needs no tss_index), and the bundled 9,198 bp
expression_hbb_k562.fa fixture are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
…teral paths

Correct the skill against the settled /v1 contract (DEV 2026.08.19.4):

- gi_predict.py: replace the 1 bp placeholder floor with the real per-task
  minimums (promoter 300, splice 100, enhancer 50, chromatin 200,
  annotation 1,000; expression unchanged at 9,198) and label them a mirror of
  gpu_service/core/limits.py, which the server publishes as minLength.
- gi_predict.py: stop forwarding --description on non-expression tasks. Every
  *Options object is now additionalProperties:false, so that was a hard 422
  rather than a no-op.
- Document the six literal predict operations, the published composite
  workflow, the typed per-task options, the Prefer header, the closed error
  code enum, and the new bio_spec fields (request_max_bp, context_window_bp,
  trained_window_bp).
- Correct "413 too long": an over-length sequence is 422 validation_failed;
  413 means the 16 MiB body cap or the composite's sync_too_large.
- Correct the "all six tasks share one request shape" claim in
  references/api.md, which contradicted the same file's own endpoint list.
- State the floor-is-not-regime rule (a request above the floor but below
  context_window_bp is scored against a padded window) rather than implying
  the bound is biologically meaningful.
- Note that tss_index errors report at loc ["body"], never body.tss_index, so
  clients must match on error.code.
- Make the locus + --tss-index example producible: fetch out/locus.fa with
  gi_fetch --region first, and replace the invented offset with a computed
  placeholder.

Verified against the live DEV schema (info.version 2026.08.19.4 (a5b1f88),
11 operations, no PredictRequest component).

Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
…ndow fields

- 504 is `timeout`, not `upstream_timeout`. The published enum has 21
  values and no `upstream_timeout`; the wrong string could never match.
- The locus quick-start fetched HBB with the `--region` default of
  `--strand 1`. HBB is minus-strand and expression never reverse-
  complements, so the example silently scored the antisense window.
  Adds `--strand -1` and the minus-strand offset formula
  (REGION_END - TSS, not TSS - REGION_START).
- `--tss-index <offset>` was unquoted inside a bash fence, so the shell
  read it as a redirection. Replaced with a computed TSS_INDEX variable;
  the whole fence now passes `bash -n`.
- Expression reports `context_window_bp: null`; 9,198 is
  `trained_window_bp`. Tables listed 9,198 under the context-window
  column. Column relabelled to the field it holds.
- Dropped the default-model-ID inventory; `GET /v1/tasks/{task}/models`
  owns it. Architecture descriptors kept.

Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
… the locus example

The published schema conveys strand zero times on the direct expression
path: the operation description never mentions it, and reverse_complement
is reachable only through the composite. The example therefore has to
carry the requirement itself rather than lean on the contract -- a
wrong-strand window returns a confident number, not an error.

Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
…c from staging

Keeps the exact-window tripwire on the default path (no --tss-index still
requires exactly 9,198 bp, because a mis-centred window returns a confident
200 with no client-side tell) and keeps --tss-index as the explicit opt-in
to the 9,198-500,000 bp range the API allows.

Also corrects expression's context_window_bp in the task table: it is null,
not 9,198 -- that number is trained_window_bp. Adds the tests/ and
evals/config.yml that only existed in the staging copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
…ask property

Every predict operation declares the Prefer header and both 200 and 202.
Verified live: promoter submitted with Prefer: respond-async returns 202 and
polls to a normal payload; annotation with no Prefer returns 200 synchronously.

Relabels the task table's "Mode" column "Recommended mode" and states the rule
under the table. Renames TaskSpec.async_mode to async_default so the code stops
asserting a constraint the API never had -- no behaviour change, annotation
still defaults to async.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
… runner docstring

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
…Id header

413 sync_too_large omits request_id from the error body, so the client
rendered "(request_id=None)" on it. The X-Request-Id header is set on every
response, so GIError now falls back to it. Corrects the docs, which claimed
request_id was always populated, and replaces the "req_..." placeholder with
a real UUID shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
…08.19.5

Today's GI ship landed after this text was written, so several claims went
stale within hours:

- 422 details is the declared {"errors": [...]} object, not a bare array.
  Defensive handling of either shape is kept; only the prose was wrong.
- error.request_id is populated on every error path, including both 413
  variants; success envelopes carry meta.request_id.
- bio_spec.max_seq_length_bp is retired and absent from the live response;
  request_max_bp is the cap.
- Version notes hedging that PROD may still serve an older build are moot.
- Sync/async is a per-request delivery choice, never a task property; the
  composite's 50,000 bp sync limit is the only forced case.

Also renames the client's synthesized non_json error code to http_error,
which is in the published 21-value enum; client-origin errors remain
distinguishable by carrying no request_id.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
Both sides had unique work, so this merges rather than replacing either.

From the remote (2026-08-10): the upstream main merge, the generated
plugin payload under plugins/, evals/trigger_evals.json, and the move
away from citing the private monorepo as the contract's source of truth.

From this branch (2026-08-19): the per-task floors (promoter 300, splice
100, enhancer 50, chromatin 200, annotation 1,000, expression 9,198), the
--tss-index opt-in that keeps the exact-window tripwire on the default
path, timeout in place of the non-existent upstream_timeout, the HBB
sense-strand example and its demo-header off-by-one, delivery mode as a
per-request choice rather than a task property, the request_id header
fallback, and tests/.

Conflicts in SKILL.md, references/api.md, references/tasks.md and
scripts/gi_predict.py resolved in favour of the 2026-08-19 work, which
supersedes the remote on every contested line. Two remote claims were
dropped as incorrect against the live contract: that the request floor is
1 bp, and that the API truncates or pads a mis-sized expression window
(it returns 422 validation_failed).

The remote's removal of gpu_service/ source references was kept and
extended to the four places this branch still carried them - partner docs
must not cite a private repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
scripts/plugin_sync.py --write, so plugins/bionemo-agent-toolkit/ carries
the 2026-08-19 contract corrections instead of the 2026-08-10 snapshot.
The payload is generated, not hand-maintained, and plugin-sync.yml fails a
PR on drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a hosted Genomic Intelligence skill for six DNA prediction tasks, including sequence acquisition, synchronous and asynchronous inference, validation, reporting, evaluations, and a synchronized plugin payload.

  • Adds strict single-record FASTA parsing and task-specific input validation.
  • Adds canonical-TSS expression-window acquisition with strand-aware coordinate handling.
  • Adds response-envelope and nested-field validation with stable hosted-service diagnostics.
  • Publishes the skill through skills.sh.json and mirrors it into the generated plugin tree.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains in the previously reported paths.

No blocking failure remains.

Important Files Changed

Filename Overview
nim-skills/genomic-intelligence-nim/scripts/gi_client.py Implements bearer-authenticated prediction and polling, strict response-envelope checks, and single-record FASTA validation; the displayed parser and response-handling threads are addressed.
nim-skills/genomic-intelligence-nim/scripts/gi_ensembl.py Resolves Ensembl loci and constructs canonical-transcript, strand-aware expression windows; the prior fallback and off-by-one issues are addressed.
nim-skills/genomic-intelligence-nim/scripts/gi_predict.py Provides task validation, sync/async execution, nested response validation, reporting, and stable diagnostics for expected hosted-service failures.
nim-skills/genomic-intelligence-nim/tests/test_input_validation.py Covers the previously reported FASTA, TSS-centering, envelope, nested-field, and CLI error-handling regressions.
plugins/bionemo-agent-toolkit/skills/genomic-intelligence-nim/SKILL.md Adds the generated plugin-facing copy of the hosted genomic prediction skill.
skills.sh.json Adds the new skill to the reviewed distribution catalog.

Sequence Diagram

sequenceDiagram
  participant User
  participant Fetch as gi_fetch.py
  participant Ensembl
  participant Predict as gi_predict.py
  participant GI as Hosted Genomic Intelligence API
  User->>Fetch: Gene, region, or expression request
  Fetch->>Ensembl: Resolve locus and fetch sequence
  Ensembl-->>Fetch: FASTA sequence
  Fetch-->>User: Single-record FASTA
  User->>Predict: Task, FASTA, and task options
  Predict->>GI: Bearer-authenticated prediction request
  alt Annotation async
    GI-->>Predict: Job identifier
    loop Until completion
      Predict->>GI: Poll job
      GI-->>Predict: Progress or result
    end
  else Synchronous task
    GI-->>Predict: Prediction envelope
  end
  Predict-->>User: report.md, result.json, and JSON summary
Loading

Reviews (12): Last reviewed commit: "chore(genomic-intelligence-nim): include..." | Re-trigger Greptile

Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_ensembl.py Outdated
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_predict.py
boldakov and others added 2 commits August 19, 2026 18:07
…ot a bare array

The sample error envelope showed details: [], which was true until
gpu_service 2026.08.19.5. The service now emits the object the schema has
always declared: {errors: [{loc: [...], msg: ...}]}. Verified against
live PROD.

Regenerated the plugin-sync payload so the plugin-sync check stays green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
…iring it

Three defects, all of which produced a confident, well-formed, wrong result
with nothing for the caller to notice.

read_fasta silently deleted every character outside ACGTN and concatenated
multi-record files into one chimeric sequence under the first record's name.
Deleting an IUPAC ambiguity code shifts every base after it, so the model
scored a sequence the caller never supplied. Both now raise FastaError
(a ValueError subclass) naming the offending characters or record count.

GeneLocus.tss fell back to gene-body coordinates when no canonical transcript
was resolved. The field comments already condemned this — ACTB's gene body
sits 33,301 bp from its TSS — and the fallback is invisible downstream: the
window is still exactly 9,198 bp, so the client-side size gate passes, no
tss_index is sent, and the API scores the wrong locus at full confidence.
Correctly sized, wrongly centred, no client-side tell. It now raises.

The runner caught only GIError, so connection failures, job timeouts and
malformed async responses exited as tracebacks on routine hosted-service
failures. They now surface as the runner's normal stderr diagnostic with a
non-zero exit, matching the mapping gi_ensembl already did.

SKILL.md and references/errors.md updated to describe refusal rather than
repair. Plugin payload regenerated; all six bundled demo fixtures still parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <alexboldakov@gmail.com>
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_ensembl.py Outdated
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
boldakov and others added 4 commits August 20, 2026 16:03
…omments

Audited against AUTHORING.md. Removes every pointer at the private service
codebase (retired `gpu_service/*` paths, internal constants and symbols) and
the internal release stamps that went with them, restating each as what the
Genomic Intelligence API serves, with the published OpenAPI document as the
authority. Also normalizes the research-use wording, removes pinned model IDs
and prediction values from prose, and makes comment voice consistent.

No contract facts changed; every bound, field and status code re-verified
against the live schema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…trands

Ensembl reverse-complements a minus-strand region, so a window built as if the
sequence always read low-to-high put the TSS at offset 4,598. The API scores
sequence[tss_index-4599 : tss_index+4599] and defaults tss_index to 4,599 for a
9,198 bp submission, so every minus-strand gene was scored one base off. The
window was still exactly 9,198 bp, so the client-side size gate passed and the
API returned a confident score with nothing for the caller to notice. Confirmed
live on HBB before fixing. Window arithmetic is now a pure helper with a
regression test on both strands.

Also: --demo must be asked for. Omitting --input silently ran the bundled
fixture and produced a full report, real request id and all, for a sequence the
caller never supplied.

Plugin payload regenerated with scripts/plugin_sync.py --write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…for input

Regression guard for the fallback removed in the previous commit: no --input and
no --demo must exit, and --demo must still resolve the bundled fixture.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…envelopes

Two defects from the review on PR NVIDIA-BioNeMo#12.

read_fasta absorbed sequence lines appearing before the first '>' header into
the first record. Such a file has exactly one header, so the multi-record check
passed, and the API scored bases the caller never named while returning
coordinates that did not describe what was submitted. The name-is-None guard
existed but sat in the header branch, so nothing reached it.

Successful responses were returned unvalidated. A non-JSON 2xx was turned into
an error-shaped dict and handed back as a result, so a caller saw {"error": ...}
with ok=true; an empty or non-object 200 reached the report writer and failed
there as an AttributeError. Predict and job results now require the {data, meta}
envelope. /health and /tasks/{task}/models are deliberately un-enveloped and are
not checked.

Also aligns whitespace handling with the API, which strips newlines, spaces and
tabs before measuring length. A space-grouped body was rejected as a bad
character, making the client stricter than the service it guards.

Guards added for all three in tests/test_input_validation.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
Addresses: NVIDIA-BioNeMo#12 (comment)

The previous commit wrapped only wait_for_job. predict() still returned
_check() straight through, so a synchronous 200 with no data key reached the
report writer, which wrote an empty report and reported ok=true. The intended
edit was in that commit and matched no text, so it silently did nothing.

Also tightens the check itself: presence of a data key is not enough, since a
null or non-object data passes a presence test and then fails in the summarizer
as an AttributeError. It must be an object.

Both are pinned by tests, including one asserting predict() calls the validator
so the same silent no-op cannot recur.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
@boldakov

Copy link
Copy Markdown
Author

Good catch, that one was mine. The previous commit wrapped only wait_for_job; the edit intended for predict() matched no text and silently did nothing, so the sync path stayed unguarded while the async path was fixed.

3a95615 wraps predict() and tightens the check itself, since a null or non-object data passed the presence test and then failed in the summarizer. Both are pinned by tests, including one asserting predict() calls the validator so the same no-op cannot recur.

Addresses: NVIDIA-BioNeMo#12 (comment)

submit_async read body["data"]["job_id"] straight off _check, so a malformed 2xx
surfaced as KeyError when data was missing and TypeError when data was not an
object. The CLI catches the first and not the second, and neither reads to the
caller as a bad response.

Found by the daily open-PR sweep, not by us: the earlier reply answered only the
synchronous half of the reviewer's finding. Present in all four copies of the
client, including ClawBio, which the reviewer cannot see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
boldakov added a commit to genomicintelligence/ClawBio that referenced this pull request Aug 20, 2026
Addresses: NVIDIA-BioNeMo/bionemo-agent-toolkit#12 (comment)

submit_async read body["data"]["job_id"] straight off _check, so a malformed 2xx
surfaced as KeyError when data was missing and TypeError when data was not an
object. The CLI catches the first and not the second, and neither reads to the
caller as a bad response.

Found by the daily open-PR sweep, not by us: the earlier reply answered only the
synchronous half of the reviewer's finding. Present in all four copies of the
client, including ClawBio, which the reviewer cannot see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a junction

The reference listed data.sites' start/end without saying what the pair means.
It bounds one variable-width tokenizer token (4-10 bp observed), comes with a
token_index, and the exon/intron junction sits inside it, so reading either
coordinate as the boundary base is wrong by up to about 10 bp in either
orientation with nothing in the response to signal it.

Payload regenerated with scripts/plugin_sync.py --write; --check reports the
plugins/ copy byte-identical to source. Verified against the live contract
(info.version 2026.08.20.11) and the pinned HBB fixture, whose eight sites span
4, 5, 6, 7 and 8 bp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
… not truthiness

`_require_envelope` guarantees `data` is an object and deliberately stops
there — it is shared by six tasks and must not encode any one task's schema.
Everything nested inside `data` was then guarded by `x or {}` / `x or []`,
which covers absent and null but not a *truthy* value of the wrong type.

A `data.summary` arriving as the string "all good" passes `or {}` unchanged
and raises AttributeError on the next `.get`. `or []` fails the same way in
the other direction: a bare string is iterable, so the row loop iterates its
characters and each `r.get` raises. Both surface as tracebacks rather than the
documented API-error diagnostic, because `_summarize` and `_write_report` run
after main()'s try/except has closed.

`_as_obj` / `_as_objs` replace the idiom at all 13 call sites in this file —
`data`, `summary`, `prediction`, `meta`, `meta.task_specific_counts`,
`regions`, `sites` and `transcripts`, in `_summarize`, `_headline_lines`,
`_write_report` and `main`. A malformed field is dropped and the report still
writes. Re-grepped: no `or {}` / `or []` remains outside the docstrings.

Sized before fixing, per the reviewer report naming only `summary`,
`prediction` and `meta`: the same shape is present in three further packagings
we ship — ClawBio `clawbio/gi/` (11 sites), proto-tools
`sequence_scoring/genomic_intelligence/` (29 across 8 files) and Phylo
`gi_predict.py` (10). Those are tracked as GI-057 and are not touched here.

Pinned by `TestNestedFieldsOfTheWrongType`, which drives `_summarize` and
`_write_report` with nine malformed bodies across every task and asserts the
report still writes, plus one well-formed body asserting the rows are still
rendered. Behavioural, not an `inspect.getsource` assertion — a source grep is
what missed the last one of these.

140 passed, 1 skipped; `plugin_sync.py --check` clean.

Addresses: NVIDIA-BioNeMo#12 (comment)
Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_predict.py Outdated
Comment thread nim-skills/genomic-intelligence-nim/scripts/gi_client.py Outdated
`_require_envelope` accepted `{"data": {}, "meta": {...}}`. All three of its
call sites read content out of `data` — a prediction payload, `data.job_id`,
a finished job's result — so an empty object is malformed for every one of
them. On the sync and job-result paths nothing else caught it: the runner
wrote a zero-valued report and printed `"ok": true` with no prediction in it.

Require `data` to be a non-empty object. The `data.job_id` check in
submit_async stays, since a non-empty `data` can still lack it.

Addresses: NVIDIA-BioNeMo#12 (comment)
Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
…'t coerce it

The previous commit replaced `x or {}` with helpers that substituted an empty
object or array whenever a nested response field arrived with the wrong type.
That fixed the AttributeError traceback and introduced a worse failure: the
report writer runs after main()'s try/except has closed, so a `data.summary`
that arrives as a string produced a zero-valued report and printed
`"ok": true`. A malformed response became indistinguishable from a real
prediction of nothing — a silent wrong answer in place of a loud one.

`_as_obj` / `_as_objs` now raise `ResponseShapeError` naming the field, and
main() catches it around `_summarize` / `_write_report` and exits 2 with the
same "unexpected API response shape" diagnostic it gives any other malformed
body. Absent and null stay legitimate — a task with no `prediction` omits it —
so only a present, wrong-typed field is refused.

The tests that asserted `_summarize` *survives* a malformed body encoded the
behaviour being removed and now assert the refusal instead, including one that
drives main() end to end: GI-055 was a source-level edit that matched nothing
and still read as correct, so the durable check is the exit code, not a grep.

Suite: 150 passed, 1 skipped. Payload regenerated with plugin_sync.py --write.

Addresses: NVIDIA-BioNeMo#12 (comment)
Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@boldakov

Copy link
Copy Markdown
Author

Could a maintainer approve the workflow runs on this PR? As a fork PR they need a manual approval, and all 36 runs since the PR opened are sitting at action_required, so plugin-sync, skill-security and Request NVSkills CI have never executed against this branch.

Locally the skill's suite is 150 passed / 1 skipped and scripts/plugin_sync.py --check reports the plugins/ payload matching source, but that is my evidence rather than yours.

…in payload

Upstream NVIDIA-BioNeMo#15 (47315cc) changed the plugin payload contract: plugin_sync.py's
STRIP_FROM_PAYLOAD went from {"evals"} to the empty set, so a payload folder must
now be a full copy of its source skill, evals/ included, because NVCARPS Tier 3
validates the payload and needs the eval dataset there.

This skill's payload was generated under the previous rule and predates that
change, so it carries no evals/. Running scripts/plugin_sync.py --check on the
merge of this branch into upstream/main f62542c reports:

  [freshness] genomic-intelligence-nim: missing in payload: evals/config.yml
  [freshness] genomic-intelligence-nim: missing in payload: evals/evals.json
  [freshness] genomic-intelligence-nim: missing in payload: evals/trigger_evals.json

The three files are copied byte for byte from nim-skills/genomic-intelligence-nim/evals/.
After this commit the same check reports nothing against this skill.

It does not report nothing overall. Four freshness failures remain on evo2-nim
(SKILL.md, evals/evals.json, references/api.md, references/examples.md), and
those reproduce on a pristine upstream/main worktree at f62542c with this branch
absent, so plugin-sync is currently red on main independently of this PR.

Signed-off-by: Alexander Boldakov <boldakov@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant