spoke-knowledge-graph — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited spoke-knowledge-graph (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill whenever the user wants to explore relationships between biomedical entities — diseases, genes, drugs/compounds, proteins, pathways, anatomy, cell types, side effects, symptoms, biological processes — in the SPOKE knowledge graph (a 43M-node Neo4j graph queried with Cypher).
get_spoke_schema a single time at the start. It returnsa compact node table + an edge_directory of Source →REL→ Target (count) with cost flags. It is cached — do not call it again unless you suspect the schema changed (then pass refresh=true). Use the edge_directory to pick the exact relationship type that connects two entity types.
MATCH.Call resolve_entity("<name or id>", label="<Type>") first. Exact {name: …} matching in SPOKE is case-sensitive ("Warfarin" not "warfarin", "asthma" not "Asthma"), and names often contain apostrophes ("Parkinson's disease"). resolve_entity returns the canonical {label, name, identifier} and also resolves synonyms/brand names and identifiers (DOID, Entrez, Ensembl, DrugBank, UMLS CUI, UBERON, GO). Pick the best candidate and say which one you chose.
the parameters argument, never inline them:
query_spoke(
cypher_query="MATCH (d:Disease {name:$n})-[:ASSOCIATES_DaG]->(g:Gene) RETURN g.name AS gene LIMIT 20",
parameters={"n": "multiple sclerosis"}
)This eliminates case/quoting errors entirely. Matching by identifier (e.g. {identifier:$id}) is equally good — but note Gene.identifier is an integer (Entrez); match genes by their name (HGNC symbol) instead. Each candidate includes a degree (its number of relationships). When an entity has several variant nodes (e.g. "glucose" deg 7 vs the canonical high-degree node), prefer the higher-degree one — especially if a traversal on your first pick is empty.
which node you resolved to (name + identifier), which relationship/direction you traversed, and any limitation (e.g. "SPOKE has no LOCALIZES edge for this disease").
When a query returns 0 rows, or for "how is X connected / what is near X" questions, call `describe_node`. It lists the relationship types a node actually has (with direction, neighbour label, and count). If the edge you expected isn't there (e.g. Parkinson's disease has no PRESENTS_DpS, Crohn's has no LOCALIZES_DlA), report the absence immediately — do not keep trying query variations. It is also the fastest way to scope an open-ended exploration.
| Question | Pattern |
|---|---|
| genes associated with a disease | (:Disease)-[:ASSOCIATES_DaG]->(:Gene) |
| drugs that treat a disease | (:Compound)-[:TREATS_CtD]->(:Disease) (edge has phase,purpose) |
| drugs contraindicated in a disease | (:Compound)-[:CONTRAINDICATES_CcD]->(:Disease) |
| drugs in clinical trials for a disease | (:Compound)-[:IN_CLINICAL_TRIALS_FOR_CictD]->(:Disease) |
| drugs that target a gene | (:Compound)-[:BINDS_CbP]->(:Protein)<-[:ENCODES_GeP]-(:Gene) — there is NO TARGETS_CtG edge |
| side effects of a drug | (:Compound)-[:CAUSES_CcSE]->(:SideEffect) |
| symptoms of a disease | (:Disease)-[:PRESENTS_DpS]->(:Symptom) |
| genes/symptoms | (:Gene)-[:ASSOCIATES_GaS]->(:Symptom) |
| disease localised to anatomy | (:Disease)-[:LOCALIZES_DlA]->(:Anatomy) |
| genes expressed in anatomy | (:Anatomy)-[:EXPRESSES_AeG]->(:Gene) |
| gene → pathway / process / function | [:PARTICIPATES_GpPW] / [:PARTICIPATES_GpBP] / [:PARTICIPATES_GpMF] |
| gene → protein | (:Gene)-[:ENCODES_GeP]->(:Protein) |
| compound up/down-regulates gene | [:UPREGULATES_CuG] / [:DOWNREGULATES_CdG] (have zscore,pvalue) |
| disease resembles / is-a disease | [:RESEMBLES_DrD] / [:ISA_DiD] |
| pharmacologic class → compounds | (:PharmacologicClass)-[:INCLUDES_PCiC]->(:Compound) |
| miRNA → gene | (:MiRNA)-[:TARGETS_MtG]->(:Gene) |
Disease = DOID (also omim_list, mesh_list); Gene = Entrez integer identifier, HGNC symbol name, ensembl; Compound = inchikey:/CHEBI: identifier, DrugBank/ ChEMBL/PubChem in xrefs; Protein = UniProt; SideEffect = UMLS CUI; Symptom = MeSH; Anatomy = UBERON; BiologicalProcess/MolecularFunction/CellularComponent = GO. resolve_entity understands all of these — feed it the id directly.
has 43M nodes; an unanchored MATCH over Protein/Compound or an expensive edge (flagged in the schema, >1M: INTERACTS_PiC, PARTOF_PDpP, ENCODES_OeP, INTERACTS_PiP, TARGETS_MtG) can time out.
org_ncbi_id) — it is unindexed over 39Mnodes (≈26 s). Reach human proteins via (:Gene)-[:ENCODES_GeP]->(:Protein).
query_spoke auto-applies a safety LIMIT to unbounded non-aggregate queries andenforces a transaction timeout, but you should still add explicit LIMIT/filters.
vestige = true. Add WHERE NOT coalesce(p.vestige, false) for pathways.
MATCH p = shortestPath((a)-[*..4]-(b)) with both ends anchored by identifier.
resolve_entity the names, confirm theedge type/direction in the edge_directory, then retry once.
use the `find_path` tool (not hand-written shortestPath queries). It resolves both endpoints and returns the shortest path(s) as node + relationship sequences in one call — read the mechanism straight off the result. Increase max_hops only if no path is found. Don't keep probing individual multi-hop patterns by hand.
target", "genes shared by two diseases"), write ONE anchored graph query with a co-occurrence pattern — do not resolve and test candidate entities one-by-one: MATCH (a {name:$a})-[:BINDS_CbP]->(p:Protein)<-[:BINDS_CbP]-(b:Compound) WHERE b<>a RETURN DISTINCT b.name LIMIT 10.
regulation z-score). The schema usage_notes lists the important ones; to confirm an edge's properties run MATCH ()-[r:REL]->() WITH r LIMIT 1 RETURN keys(r) once.
"What drugs target the protein encoded by EGFR?"
get_spoke_schema → confirm BINDS_CbP (Compound→Protein) and ENCODES_GeP (Gene→Protein).resolve_entity("EGFR", label="Gene") → {name:"EGFR", identifier:1956}.query_spoke("MATCH (g:Gene {name:$g})-[:ENCODES_GeP]->(p:Protein)<-[:BINDS_CbP]-(c:Compound) RETURN DISTINCT c.name AS drug LIMIT 25", {"g":"EGFR"}).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.