@inflexa-ai/harness 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/conversation-agent.d.ts +3 -0
- package/dist/agents/conversation-agent.js +5 -2
- package/dist/agents/report-builder.d.ts +2 -0
- package/dist/agents/report-builder.js +2 -0
- package/dist/agents/sandbox/shared.d.ts +3 -0
- package/dist/agents/sandbox/shared.js +5 -0
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/app/chat-turn.d.ts +2 -0
- package/dist/app/chat-turn.js +11 -2
- package/dist/app/spawn-report-session.d.ts +85 -0
- package/dist/app/spawn-report-session.js +83 -0
- package/dist/app/synthesize-run.d.ts +3 -0
- package/dist/app/synthesize-run.js +2 -1
- package/dist/citations/__fixtures__/resolver.d.ts +3 -0
- package/dist/citations/__fixtures__/resolver.js +9 -0
- package/dist/citations/aggregate.d.ts +3 -0
- package/dist/citations/aggregate.js +44 -0
- package/dist/citations/clients/arxiv.d.ts +6 -0
- package/dist/citations/clients/arxiv.js +34 -0
- package/dist/citations/clients/common.d.ts +11 -0
- package/dist/citations/clients/common.js +43 -0
- package/dist/citations/clients/crossref.d.ts +8 -0
- package/dist/citations/clients/crossref.js +98 -0
- package/dist/citations/clients/doi-registry.d.ts +3 -0
- package/dist/citations/clients/doi-registry.js +95 -0
- package/dist/citations/clients/pubmed.d.ts +6 -0
- package/dist/citations/clients/pubmed.js +83 -0
- package/dist/citations/clients/semantic-scholar.d.ts +6 -0
- package/dist/citations/clients/semantic-scholar.js +60 -0
- package/dist/citations/compare.d.ts +9 -0
- package/dist/citations/compare.js +72 -0
- package/dist/citations/match.d.ts +11 -0
- package/dist/citations/match.js +173 -0
- package/dist/citations/normalize.d.ts +31 -0
- package/dist/citations/normalize.js +187 -0
- package/dist/citations/plan.d.ts +2 -0
- package/dist/citations/plan.js +59 -0
- package/dist/citations/resolve.d.ts +28 -0
- package/dist/citations/resolve.js +238 -0
- package/dist/citations/types.d.ts +583 -0
- package/dist/citations/types.js +129 -0
- package/dist/contracts/chat-events.d.ts +9 -0
- package/dist/contracts/index.d.ts +6 -2
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/organ-system.d.ts +49 -0
- package/dist/contracts/organ-system.js +67 -0
- package/dist/contracts/report-blocks.d.ts +564 -0
- package/dist/contracts/report-blocks.js +117 -0
- package/dist/contracts/report-reference.d.ts +459 -0
- package/dist/contracts/report-reference.js +256 -0
- package/dist/contracts/schemas/chat-events.d.ts +2 -0
- package/dist/contracts/schemas/chat-events.js +2 -0
- package/dist/contracts/schemas/chat-parts.d.ts +8 -8
- package/dist/contracts/severity.d.ts +15 -0
- package/dist/contracts/severity.js +10 -0
- package/dist/contracts/target-dossier.d.ts +8673 -12970
- package/dist/contracts/target-dossier.js +534 -591
- package/dist/data/safety-panel-schema.d.ts +18 -5
- package/dist/data/safety-panel-schema.js +4 -4
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/report-runner.js +1 -3
- package/dist/execution/run-synthesis.d.ts +3 -0
- package/dist/execution/run-synthesis.js +3 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +22 -0
- package/dist/lib/async-utils.d.ts +16 -4
- package/dist/lib/async-utils.js +20 -15
- package/dist/lib/cache.d.ts +8 -0
- package/dist/lib/cache.js +35 -0
- package/dist/lib/host-concurrency.js +1 -0
- package/dist/lib/rate-limit.d.ts +35 -0
- package/dist/lib/rate-limit.js +121 -0
- package/dist/literature/sources/arxiv.d.ts +22 -0
- package/dist/literature/sources/arxiv.js +103 -0
- package/dist/literature/sources/http.d.ts +28 -0
- package/dist/literature/sources/http.js +97 -0
- package/dist/literature/sources/pubmed.d.ts +68 -0
- package/dist/literature/sources/pubmed.js +266 -0
- package/dist/literature/sources/semantic-scholar.d.ts +24 -0
- package/dist/literature/sources/semantic-scholar.js +71 -0
- package/dist/loop/__fixtures__/scripted-provider.js +23 -2
- package/dist/loop/run-agent.js +58 -10
- package/dist/loop/tool-detail.d.ts +40 -0
- package/dist/loop/tool-detail.js +35 -3
- package/dist/loop/types.d.ts +13 -0
- package/dist/memory/thread-history.d.ts +10 -0
- package/dist/memory/thread-history.js +12 -1
- package/dist/memory/thread-store.d.ts +142 -25
- package/dist/memory/thread-store.js +228 -40
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +2 -0
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
- package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
- package/dist/prompts/target-assessment/investigation/index.js +3 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
- package/dist/providers/__fixtures__/session.d.ts +5 -4
- package/dist/providers/__fixtures__/session.js +4 -4
- package/dist/report-model/block-walk.d.ts +56 -0
- package/dist/report-model/block-walk.js +89 -0
- package/dist/report-model/draft-finish.d.ts +60 -0
- package/dist/report-model/draft-finish.js +56 -0
- package/dist/report-model/draft-operations.d.ts +125 -0
- package/dist/report-model/draft-operations.js +341 -0
- package/dist/report-model/draft-read.d.ts +59 -0
- package/dist/report-model/draft-read.js +109 -0
- package/dist/report-model/draft.d.ts +51 -0
- package/dist/report-model/draft.js +32 -0
- package/dist/report-model/fixture-resolver.d.ts +8 -0
- package/dist/report-model/fixture-resolver.js +252 -0
- package/dist/report-model/mint-snapshot.d.ts +30 -0
- package/dist/report-model/mint-snapshot.js +40 -0
- package/dist/report-model/reference-resolver.d.ts +113 -0
- package/dist/report-model/reference-resolver.js +60 -0
- package/dist/report-model/structural-validation.d.ts +22 -0
- package/dist/report-model/structural-validation.js +68 -0
- package/dist/report-model/validate.d.ts +40 -0
- package/dist/report-model/validate.js +80 -0
- package/dist/report-render/chart.d.ts +33 -0
- package/dist/report-render/chart.js +538 -0
- package/dist/report-render/page.d.ts +127 -0
- package/dist/report-render/page.js +307 -0
- package/dist/report-render/references.d.ts +32 -0
- package/dist/report-render/references.js +44 -0
- package/dist/report-render/render.d.ts +25 -0
- package/dist/report-render/render.js +125 -0
- package/dist/report-render/script-json.d.ts +13 -0
- package/dist/report-render/script-json.js +15 -0
- package/dist/report-render/types.d.ts +39 -0
- package/dist/report-render/types.js +10 -0
- package/dist/report-render/views/chart-view.d.ts +11 -0
- package/dist/report-render/views/chart-view.js +23 -0
- package/dist/report-render/views/page-view.d.ts +20 -0
- package/dist/report-render/views/page-view.js +49 -0
- package/dist/report-render/views/prose.d.ts +28 -0
- package/dist/report-render/views/prose.js +75 -0
- package/dist/report-render/views/references-view.d.ts +20 -0
- package/dist/report-render/views/references-view.js +64 -0
- package/dist/report-render/views/values.d.ts +42 -0
- package/dist/report-render/views/values.js +43 -0
- package/dist/runtime/assemble.d.ts +50 -4
- package/dist/runtime/assemble.js +47 -3
- package/dist/runtime/boot.d.ts +3 -3
- package/dist/runtime/boot.js +22 -5
- package/dist/runtime/dbos.d.ts +4 -4
- package/dist/state/artifacts.d.ts +16 -0
- package/dist/state/artifacts.js +18 -0
- package/dist/state/init.js +77 -1
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-versions.d.ts +132 -0
- package/dist/state/report-versions.js +194 -0
- package/dist/state/schema.d.ts +2 -2
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -1
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/keys.d.ts +4 -2
- package/dist/tools/bio/keys.js +3 -2
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.d.ts +3 -3
- package/dist/tools/bio/opentargets.js +4 -3
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/pubmed.js +4 -4
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/target-safety.d.ts +4 -6
- package/dist/tools/bio/target-safety.js +7 -6
- package/dist/tools/define-tool.d.ts +19 -12
- package/dist/tools/define-tool.js +4 -2
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/lib/chembl-client.d.ts +2 -0
- package/dist/tools/lib/chembl-client.js +1 -0
- package/dist/tools/lib/monarch-client.d.ts +42 -0
- package/dist/tools/lib/monarch-client.js +100 -0
- package/dist/tools/lib/ncbi-utils.d.ts +3 -58
- package/dist/tools/lib/ncbi-utils.js +2 -286
- package/dist/tools/lib/pubmed-client.d.ts +5 -12
- package/dist/tools/lib/pubmed-client.js +20 -79
- package/dist/tools/report/build-report.js +1 -0
- package/dist/tools/report/mint-preview-url.js +1 -0
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report/submit-report.js +1 -0
- package/dist/tools/report/version-fs.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
- package/dist/tools/report-authoring/authoring-tools.js +352 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +3 -2
- package/dist/tools/research/generate-plan.js +3 -0
- package/dist/tools/research/index.d.ts +1 -0
- package/dist/tools/research/index.js +1 -0
- package/dist/tools/research/inspect-data-profile.js +4 -0
- package/dist/tools/research/inspect-run.d.ts +1 -1
- package/dist/tools/research/literature-reviewer.d.ts +3 -0
- package/dist/tools/research/literature-reviewer.js +3 -0
- package/dist/tools/research/resolve-citation.d.ts +3 -0
- package/dist/tools/research/resolve-citation.js +15 -0
- package/dist/tools/research/search-arxiv.d.ts +3 -19
- package/dist/tools/research/search-arxiv.js +10 -105
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.d.ts +8 -33
- package/dist/tools/research/search-semantic-scholar.js +30 -88
- package/dist/tools/sandbox/list-available-packages.js +23 -0
- package/dist/tools/sandbox/list-available-refs.js +37 -2
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/workspace/file-stat.js +1 -0
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +8 -1
- package/dist/workflows/execute-analysis.js +6 -5
- package/dist/workflows/execute-target-assessment.d.ts +13 -2
- package/dist/workflows/execute-target-assessment.js +78 -16
- package/dist/workflows/sandbox-step.d.ts +5 -0
- package/dist/workflows/sandbox-step.js +1 -0
- package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
- package/dist/workflows/target-assessment/assemblers/index.js +1 -1
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
- package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
- package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
- package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
- package/dist/workflows/target-assessment/collectors/index.js +48 -5
- package/dist/workflows/target-assessment/coverage.d.ts +43 -27
- package/dist/workflows/target-assessment/coverage.js +33 -29
- package/dist/workflows/target-assessment/fanout/index.js +1 -0
- package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
- package/dist/workflows/target-assessment/investigation/index.js +419 -0
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
- package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
- package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
- package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
- package/dist/workflows/target-assessment/metrics.d.ts +1 -6
- package/dist/workflows/target-assessment/metrics.js +1 -13
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +2149 -809
- package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
- package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
- package/dist/workflows/target-assessment/phase5-persist.js +56 -173
- package/dist/workflows/target-assessment/schemas.d.ts +48 -3
- package/dist/workflows/target-assessment/schemas.js +19 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +27 -63
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +28 -155
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
- package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
- package/dist/workflows/target-assessment/synthesis/index.js +4 -1
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
- package/package.json +4 -1
|
@@ -10,20 +10,24 @@
|
|
|
10
10
|
* the vocabulary is conversation-shaped on purpose — reaching for it inside
|
|
11
11
|
* a workflow step feels immediately wrong.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* A thread may name another as its parent, so the rows under one analysis form
|
|
14
|
+
* a forest rather than a flat list. The three lifecycle verbs each answer that
|
|
15
|
+
* edge differently, and the differences are the substance of this module.
|
|
16
|
+
*
|
|
17
|
+
* `archiveThread` is recoverable: it stamps `deleted_at` on the named thread
|
|
18
|
+
* and on every descendant, and because `getThread` and the default
|
|
15
19
|
* `listThreads` filter `deleted_at IS NULL` an archived thread is
|
|
16
20
|
* indistinguishable from an absent one while its row and every one of its
|
|
17
|
-
* `messages` rows stay in storage. `unarchiveThread` clears the stamp
|
|
18
|
-
* thread reads as it did; a listing widened with `includeArchived`
|
|
19
|
-
* way to obtain the id it takes, so the recovery is reachable by a
|
|
20
|
-
* holds no thread ids it obtained elsewhere.
|
|
21
|
+
* `messages` rows stay in storage. `unarchiveThread` clears the stamp on ONE
|
|
22
|
+
* row and the thread reads as it did; a listing widened with `includeArchived`
|
|
23
|
+
* is the only way to obtain the id it takes, so the recovery is reachable by a
|
|
24
|
+
* host that holds no thread ids it obtained elsewhere.
|
|
21
25
|
* `purgeThread` is not recoverable: it removes the `messages` rows and the
|
|
22
|
-
* metadata
|
|
23
|
-
* thread stripped of its transcript, nor a transcript
|
|
24
|
-
* It is the thread-scoped member of this package's
|
|
25
|
-
* `purgeAnalysis` reclaims an analysis's whole
|
|
26
|
-
* `purgeThread` one
|
|
26
|
+
* metadata rows of the whole subtree in one transaction, so a failure partway
|
|
27
|
+
* leaves both — never a thread stripped of its transcript, nor a transcript
|
|
28
|
+
* with nothing naming it. It is the thread-scoped member of this package's
|
|
29
|
+
* reclamation vocabulary — `purgeAnalysis` reclaims an analysis's whole
|
|
30
|
+
* persisted footprint, `purgeThread` one subtree's.
|
|
27
31
|
*
|
|
28
32
|
* A purge creates no orphan of its own, but it is NOT serialized against a
|
|
29
33
|
* concurrent `appendTurn`: `messages` carries no foreign key to
|
|
@@ -31,16 +35,66 @@
|
|
|
31
35
|
* metadata row. A turn committing after the purge therefore persists messages
|
|
32
36
|
* under a `thread_id` that no longer resolves to an analysis, leaving them
|
|
33
37
|
* unreachable by any later thread- or analysis-scoped reclamation. Stop writes
|
|
34
|
-
* to
|
|
35
|
-
* store cannot observe a host's in-flight turns,
|
|
38
|
+
* to every thread in the subtree — unbind each from any live conversation —
|
|
39
|
+
* before purging its root; this store cannot observe a host's in-flight turns,
|
|
40
|
+
* so it does not enforce that.
|
|
36
41
|
*/
|
|
37
|
-
import { okAsync } from "neverthrow";
|
|
42
|
+
import { errAsync, okAsync } from "neverthrow";
|
|
38
43
|
import { tryMutation, tryQuery, withTransaction } from "../lib/db-result.js";
|
|
44
|
+
/**
|
|
45
|
+
* The kind of session a thread holds. `conversation` is the analysis
|
|
46
|
+
* conversation the product has always had; `report` is a report session spawned
|
|
47
|
+
* from one.
|
|
48
|
+
*
|
|
49
|
+
* The set is closed rather than free-form because a thread's type is what
|
|
50
|
+
* selects the agent that runs it. That resolution is a separate capability, and
|
|
51
|
+
* it can only be exhaustive over a membership something can enumerate — a
|
|
52
|
+
* free-form column pushes an unmatched value into a fallback no reader has the
|
|
53
|
+
* list to write.
|
|
54
|
+
*
|
|
55
|
+
* The array is the single declaration and the union is derived from it, so the
|
|
56
|
+
* run-time check `createThread` needs — for a value that reached the store from
|
|
57
|
+
* outside the compiler's reach — cannot drift from the type callers program
|
|
58
|
+
* against.
|
|
59
|
+
*/
|
|
60
|
+
const THREAD_TYPES = ["conversation", "report"];
|
|
61
|
+
function isThreadType(value) {
|
|
62
|
+
return THREAD_TYPES.some((known) => known === value);
|
|
63
|
+
}
|
|
64
|
+
const _assertDomainError = true;
|
|
65
|
+
/**
|
|
66
|
+
* The projection every read of a thread row shares. Fixed text, never caller
|
|
67
|
+
* input, and written once so no statement can return a row that is short a
|
|
68
|
+
* column `toThread` reads — a gap that would surface as an `undefined` field on
|
|
69
|
+
* a `Thread`, not as a failure.
|
|
70
|
+
*/
|
|
71
|
+
const THREAD_COLUMNS = "thread_id, analysis_id, title, thread_type, parent_thread_id, parent_seq::text AS parent_seq, created_at, updated_at, deleted_at";
|
|
72
|
+
/**
|
|
73
|
+
* The named thread plus every descendant reachable through `parent_thread_id`,
|
|
74
|
+
* at any depth. Fixed text taking the thread id as `$1`, and the single
|
|
75
|
+
* definition the subtree statements share, so archive and purge cannot come to
|
|
76
|
+
* walk to different depths.
|
|
77
|
+
*
|
|
78
|
+
* `UNION`, not `UNION ALL`: deduplicating against the rows already collected
|
|
79
|
+
* makes the walk terminate even if a row ever came to name an ancestor as its
|
|
80
|
+
* parent — a shape no write path here produces, and one the column does not
|
|
81
|
+
* forbid either.
|
|
82
|
+
*/
|
|
83
|
+
const SUBTREE_CTE = `WITH RECURSIVE subtree(thread_id) AS (
|
|
84
|
+
SELECT thread_id FROM cortex_analysis_threads WHERE thread_id = $1
|
|
85
|
+
UNION
|
|
86
|
+
SELECT child.thread_id
|
|
87
|
+
FROM cortex_analysis_threads child
|
|
88
|
+
JOIN subtree ON child.parent_thread_id = subtree.thread_id
|
|
89
|
+
)`;
|
|
39
90
|
function toThread(row) {
|
|
40
91
|
return {
|
|
41
92
|
threadId: row.thread_id,
|
|
42
93
|
analysisId: row.analysis_id,
|
|
43
94
|
title: row.title,
|
|
95
|
+
threadType: row.thread_type,
|
|
96
|
+
parentThreadId: row.parent_thread_id,
|
|
97
|
+
parentSeq: row.parent_seq === null ? null : Number(row.parent_seq),
|
|
44
98
|
createdAt: row.created_at,
|
|
45
99
|
updatedAt: row.updated_at,
|
|
46
100
|
deletedAt: row.deleted_at,
|
|
@@ -54,22 +108,100 @@ const MAX_PER_PAGE = 200;
|
|
|
54
108
|
* `cortex_analysis_threads` table is provisioned by the state-init DDL.
|
|
55
109
|
*/
|
|
56
110
|
export function createThreadStore(pool) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
111
|
+
/**
|
|
112
|
+
* The parent's own analysis scope, or `null` when no such row exists.
|
|
113
|
+
*
|
|
114
|
+
* Absence is deliberately not a verdict here: a `parentThreadId` naming no
|
|
115
|
+
* row falls through to the insert, where the foreign key refuses it and
|
|
116
|
+
* `tryMutation` classifies that refusal as the `constraint_violation`
|
|
117
|
+
* variant of `DbError`. Pre-empting the constraint with an existence query
|
|
118
|
+
* would buy a round trip and a window between the check and the insert in
|
|
119
|
+
* which the parent could vanish, for a verdict the constraint already
|
|
120
|
+
* reaches on its own.
|
|
121
|
+
*
|
|
122
|
+
* The lookup does not filter `deleted_at IS NULL`: an archived parent is a
|
|
123
|
+
* real row holding a real transcript, so a child anchored into it is
|
|
124
|
+
* describing something that exists.
|
|
125
|
+
*/
|
|
126
|
+
function parentAnalysisOf(parentThreadId) {
|
|
127
|
+
return tryQuery("thread-store.createThread.parentScope", () => pool.query("SELECT analysis_id FROM cortex_analysis_threads WHERE thread_id = $1", [parentThreadId])).map(({ rows }) => rows[0]?.analysis_id ?? null);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The write itself, past the rules the store enforces before it.
|
|
131
|
+
*
|
|
132
|
+
* Idempotency wins over everything the caller supplied: `ON CONFLICT
|
|
133
|
+
* (thread_id) DO NOTHING` short-circuits before any constraint is evaluated
|
|
134
|
+
* and reads no supplied parent, so a repeat create returns the existing row
|
|
135
|
+
* unchanged — even one naming a parent that does not exist. A caller that
|
|
136
|
+
* needs its type, parent, or anchor persisted compares them against the
|
|
137
|
+
* returned row.
|
|
138
|
+
*/
|
|
139
|
+
function insertThread(input) {
|
|
140
|
+
return tryMutation("thread-store.createThread.insert", () => pool.query(`INSERT INTO cortex_analysis_threads (thread_id, analysis_id, title, thread_type, parent_thread_id, parent_seq)
|
|
141
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
60
142
|
ON CONFLICT (thread_id) DO NOTHING
|
|
61
|
-
RETURNING
|
|
143
|
+
RETURNING ${THREAD_COLUMNS}`, [input.threadId, input.analysisId, input.title ?? null, input.type ?? "conversation", input.parentThreadId ?? null, input.parentSeq ?? null])).andThen(({ rows }) => {
|
|
62
144
|
if (rows[0])
|
|
63
145
|
return okAsync(toThread(rows[0]));
|
|
64
146
|
// Row already existed — ON CONFLICT DO NOTHING returns nothing. Read it
|
|
65
147
|
// back (it may be archived; return it regardless so the caller sees
|
|
66
148
|
// the existing row's identity).
|
|
67
|
-
return tryQuery("thread-store.createThread.readback", () => pool.query(`SELECT thread_id
|
|
68
|
-
|
|
149
|
+
return tryQuery("thread-store.createThread.readback", () => pool.query(`SELECT ${THREAD_COLUMNS} FROM cortex_analysis_threads WHERE thread_id = $1`, [input.threadId])).map(({ rows: existing }) => toThread(existing[0]));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function createThread(input) {
|
|
153
|
+
// Widened on purpose: the compile-time union covers a caller inside this
|
|
154
|
+
// package's type graph, and this check is for the value that reached the
|
|
155
|
+
// store from outside it — across a package boundary, out of a JSON body,
|
|
156
|
+
// off a row some other writer put there.
|
|
157
|
+
const requestedType = input.type;
|
|
158
|
+
if (requestedType !== undefined && !isThreadType(requestedType)) {
|
|
159
|
+
return errAsync({
|
|
160
|
+
type: "unknown_thread_type",
|
|
161
|
+
op: "thread-store.createThread",
|
|
162
|
+
threadId: input.threadId,
|
|
163
|
+
threadType: requestedType,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const parentThreadId = input.parentThreadId ?? null;
|
|
167
|
+
const parentSeq = input.parentSeq ?? null;
|
|
168
|
+
// Both halves of the edge or neither: the anchor is the point in the
|
|
169
|
+
// parent's transcript this thread was spawned from, and an edge without
|
|
170
|
+
// one leaves a consumer no way to tell which prefix of the parent the
|
|
171
|
+
// child stands on. Every rule below is gated on a value being supplied,
|
|
172
|
+
// so a create that names neither reaches none of them and takes the same
|
|
173
|
+
// path a first-turn conversation has always taken.
|
|
174
|
+
if ((parentThreadId === null) !== (parentSeq === null)) {
|
|
175
|
+
return errAsync({
|
|
176
|
+
type: "parent_anchor_unpaired",
|
|
177
|
+
op: "thread-store.createThread",
|
|
178
|
+
threadId: input.threadId,
|
|
179
|
+
parentThreadId,
|
|
180
|
+
parentSeq,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (parentThreadId === null)
|
|
184
|
+
return insertThread(input);
|
|
185
|
+
// A data-integrity rule, not an authorization one: it keeps a parent edge
|
|
186
|
+
// inside a single analysis, so a subtree walk can never cross into
|
|
187
|
+
// another analysis's rows. The store never compares a caller's request
|
|
188
|
+
// scope against a row.
|
|
189
|
+
return parentAnalysisOf(parentThreadId).andThen((parentAnalysisId) => {
|
|
190
|
+
if (parentAnalysisId !== null && parentAnalysisId !== input.analysisId) {
|
|
191
|
+
return errAsync({
|
|
192
|
+
type: "parent_analysis_mismatch",
|
|
193
|
+
op: "thread-store.createThread",
|
|
194
|
+
threadId: input.threadId,
|
|
195
|
+
analysisId: input.analysisId,
|
|
196
|
+
parentThreadId,
|
|
197
|
+
parentAnalysisId,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return insertThread(input);
|
|
69
201
|
});
|
|
70
202
|
}
|
|
71
203
|
function getThread(threadId) {
|
|
72
|
-
return tryQuery("thread-store.getThread", () => pool.query(`SELECT
|
|
204
|
+
return tryQuery("thread-store.getThread", () => pool.query(`SELECT ${THREAD_COLUMNS}
|
|
73
205
|
FROM cortex_analysis_threads
|
|
74
206
|
WHERE thread_id = $1 AND deleted_at IS NULL`, [threadId])).map(({ rows }) => (rows[0] ? toThread(rows[0]) : null));
|
|
75
207
|
}
|
|
@@ -86,19 +218,42 @@ export function createThreadStore(pool) {
|
|
|
86
218
|
`UPDATE cortex_analysis_threads
|
|
87
219
|
SET title = $2, updated_at = GREATEST(updated_at, clock_timestamp())
|
|
88
220
|
WHERE thread_id = $1 AND deleted_at IS NULL
|
|
89
|
-
RETURNING
|
|
221
|
+
RETURNING ${THREAD_COLUMNS}`, [threadId, title])).map(({ rows }) => (rows[0] ? toThread(rows[0]) : null));
|
|
90
222
|
}
|
|
91
223
|
function archiveThread(threadId) {
|
|
92
224
|
return tryMutation("thread-store.archiveThread", () => pool.query(
|
|
225
|
+
// The stamp reaches the whole subtree because a hidden thread must
|
|
226
|
+
// not leave a visible child behind — and to any depth, since the
|
|
227
|
+
// column permits a grandchild and a one-level sweep would leave one
|
|
228
|
+
// listed under a parent the user can no longer see.
|
|
229
|
+
//
|
|
93
230
|
// The tombstone guard is what makes a second archive change nothing:
|
|
94
|
-
// the stamp records when
|
|
95
|
-
//
|
|
96
|
-
|
|
231
|
+
// the stamp records when a thread left view, and re-stamping it would
|
|
232
|
+
// push that moment forward on every repeat. It sits on the UPDATE and
|
|
233
|
+
// not on the walk, because a walk that stopped at an already-archived
|
|
234
|
+
// child would never reach the live grandchild underneath it.
|
|
235
|
+
//
|
|
236
|
+
// `NOW()` is the transaction's clock, so every row this sweep reaches
|
|
237
|
+
// carries the same stamp — one archive action reads back as one moment.
|
|
238
|
+
//
|
|
239
|
+
// `updated_at` is untouched, for the reason `unarchiveThread` carries:
|
|
240
|
+
// it orders the listing by conversation activity, and moving a thread
|
|
241
|
+
// out of view is not activity.
|
|
242
|
+
`${SUBTREE_CTE}
|
|
243
|
+
UPDATE cortex_analysis_threads
|
|
97
244
|
SET deleted_at = NOW()
|
|
98
|
-
WHERE thread_id
|
|
245
|
+
WHERE thread_id IN (SELECT thread_id FROM subtree) AND deleted_at IS NULL`, [threadId])).map(() => undefined);
|
|
99
246
|
}
|
|
100
247
|
function unarchiveThread(threadId) {
|
|
101
248
|
return tryMutation("thread-store.unarchiveThread", () => pool.query(
|
|
249
|
+
// One row, against an archive that sweeps the subtree. The asymmetry
|
|
250
|
+
// is what keeps the schema honest: a symmetric cascade would restore
|
|
251
|
+
// a child the user had archived on its own before the parent was, so
|
|
252
|
+
// a row would have to record whether a cascade or a deliberate action
|
|
253
|
+
// set its tombstone — a fourth column carrying nothing else. With the
|
|
254
|
+
// asymmetry no row carries that distinction, and every archived
|
|
255
|
+
// thread is recovered the same way, by naming it.
|
|
256
|
+
//
|
|
102
257
|
// `updated_at` is untouched on purpose: it orders the listing by
|
|
103
258
|
// conversation activity, and restoring a thread to view is not
|
|
104
259
|
// activity — a restored thread lands back where its last turn left it.
|
|
@@ -111,8 +266,21 @@ export function createThreadStore(pool) {
|
|
|
111
266
|
// analysis only by joining through `cortex_analysis_threads`, so a metadata
|
|
112
267
|
// row removed without its messages strands them beyond the reach of any
|
|
113
268
|
// later reclamation. Either the pair goes or neither does.
|
|
114
|
-
|
|
115
|
-
|
|
269
|
+
//
|
|
270
|
+
// The explicit message delete must reach the same depth the database
|
|
271
|
+
// cascade reaches. The cascade on `parent_thread_id` removes a
|
|
272
|
+
// descendant's row recursively, but it cannot touch that descendant's
|
|
273
|
+
// `messages` — no foreign key connects them — so a one-level delete here
|
|
274
|
+
// would strand a grandchild's transcript exactly as a bare cascade strands
|
|
275
|
+
// a child's.
|
|
276
|
+
//
|
|
277
|
+
// Messages go first: the walk both statements share reads the very rows
|
|
278
|
+
// the second one removes, so the reverse order would leave the subtree
|
|
279
|
+
// unresolvable by the time its transcripts were named.
|
|
280
|
+
return withTransaction(pool, "thread-store.purgeThread", (client) => tryMutation("thread-store.purgeThread.messages", () => client.query(`${SUBTREE_CTE}
|
|
281
|
+
DELETE FROM messages WHERE thread_id IN (SELECT thread_id FROM subtree)`, [threadId]))
|
|
282
|
+
.andThen(() => tryMutation("thread-store.purgeThread.thread", () => client.query(`${SUBTREE_CTE}
|
|
283
|
+
DELETE FROM cortex_analysis_threads WHERE thread_id IN (SELECT thread_id FROM subtree)`, [threadId])))
|
|
116
284
|
.map(() => undefined));
|
|
117
285
|
}
|
|
118
286
|
function listThreads(input) {
|
|
@@ -121,25 +289,45 @@ export function createThreadStore(pool) {
|
|
|
121
289
|
const offset = page * perPage;
|
|
122
290
|
// The count and the page are two statements over what has to be one set:
|
|
123
291
|
// a total drawn from a different predicate than the page's would report a
|
|
124
|
-
// size the caller can never page to.
|
|
125
|
-
//
|
|
292
|
+
// size the caller can never page to. Building the row scope once — the
|
|
293
|
+
// fragment AND the values it binds — makes them unable to disagree. Every
|
|
294
|
+
// predicate is fixed text and every value rides as a bound parameter, so
|
|
295
|
+
// no caller string ever reaches the SQL.
|
|
126
296
|
//
|
|
127
|
-
// The widened scope is the one that cannot use the
|
|
128
|
-
// partial on `deleted_at IS NULL`: dropping the predicate drops
|
|
129
|
-
// with it, so an archived-inclusive listing scans. Deliberate —
|
|
130
|
-
// listing is the hot path and keeps its index, while widening is a
|
|
131
|
-
// user-initiated recovery. A deployment that made it routine would
|
|
132
|
-
// plain `analysis_id` index beside the partial one, not this listing
|
|
133
|
-
|
|
297
|
+
// The widened scope is the one that cannot use the `analysis_id` index,
|
|
298
|
+
// which is partial on `deleted_at IS NULL`: dropping the predicate drops
|
|
299
|
+
// the index with it, so an archived-inclusive listing scans. Deliberate —
|
|
300
|
+
// the live listing is the hot path and keeps its index, while widening is a
|
|
301
|
+
// rare, user-initiated recovery. A deployment that made it routine would
|
|
302
|
+
// want a plain `analysis_id` index beside the partial one, not this listing
|
|
303
|
+
// changed. A listing narrowed by parent is indexed either way: the
|
|
304
|
+
// `parent_thread_id` index carries no predicate, for the subtree walk's sake.
|
|
305
|
+
const scopeValues = [input.analysisId];
|
|
306
|
+
const predicates = ["analysis_id = $1"];
|
|
307
|
+
if (!input.includeArchived)
|
|
308
|
+
predicates.push("deleted_at IS NULL");
|
|
309
|
+
if (input.type !== undefined) {
|
|
310
|
+
scopeValues.push(input.type);
|
|
311
|
+
predicates.push(`thread_type = $${scopeValues.length}`);
|
|
312
|
+
}
|
|
313
|
+
if (input.parentThreadId !== undefined) {
|
|
314
|
+
scopeValues.push(input.parentThreadId);
|
|
315
|
+
predicates.push(`parent_thread_id = $${scopeValues.length}`);
|
|
316
|
+
}
|
|
317
|
+
const scope = predicates.join(" AND ");
|
|
318
|
+
// The page's two extra placeholders sit past the scope's, so a filter
|
|
319
|
+
// added to the scope shifts them rather than colliding with them.
|
|
320
|
+
const limitPlaceholder = scopeValues.length + 1;
|
|
321
|
+
const offsetPlaceholder = scopeValues.length + 2;
|
|
134
322
|
return tryQuery("thread-store.listThreads.count", () => pool.query(`SELECT COUNT(*)::text AS count
|
|
135
323
|
FROM cortex_analysis_threads
|
|
136
|
-
WHERE ${scope}`,
|
|
324
|
+
WHERE ${scope}`, scopeValues)).andThen((totalResult) => {
|
|
137
325
|
const total = Number(totalResult.rows[0].count);
|
|
138
|
-
return tryQuery("thread-store.listThreads.page", () => pool.query(`SELECT
|
|
326
|
+
return tryQuery("thread-store.listThreads.page", () => pool.query(`SELECT ${THREAD_COLUMNS}
|
|
139
327
|
FROM cortex_analysis_threads
|
|
140
328
|
WHERE ${scope}
|
|
141
329
|
ORDER BY updated_at DESC, thread_id
|
|
142
|
-
LIMIT
|
|
330
|
+
LIMIT $${limitPlaceholder} OFFSET $${offsetPlaceholder}`, [...scopeValues, perPage, offset])).map(({ rows }) => ({
|
|
143
331
|
threads: rows.map(toThread),
|
|
144
332
|
total,
|
|
145
333
|
page,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const literatureReviewerPrompt = "# Literature Reviewer\n\nYou are a systematic biology investigator. You receive a research brief\ncontaining genes, pathways, or biological features to investigate, along\nwith the experimental context. Your job is to use your tools to build a\ncomprehensive evidence profile and return a structured report.\n\nYou are task-oriented \u2014 you do NOT interact with the user. You receive\na brief, investigate, and return results.\n\n## Tool usage notes (read before calling any tool)\n\nSeveral tools have strict input shapes. Mis-shaping your call wastes a\nturn and forces a retry.\n\n- `pubmed({action:\"search\"})` \u2014 pass a focused query string. Shape: `{ action: \"search\", query: \"BRCA1 AND breast cancer\", maxResults: 10 }`.\n- `pubmed({action:\"details\"})` \u2014 MUST be called with `pmids`, a non-empty array of\n PMID strings. Shape: `{ action: \"details\", pmids: [\"12345678\", \"87654321\"] }`.\n- `search_interactions` \u2014 accepts up to **100 identifiers per call**. If you\n need more, **batch across multiple calls** rather than truncating your\n list. Shape: `{ identifiers: [\"TP53\", \"BRCA1\", ...], limit: 50 }`.\n- `search_gene` \u2014 batches up to 200 symbols; `lookup_annotation` takes one\n term or accession per call.\n- `gene_preclinical_profile` \u2014 single human gene symbol per call; returns\n baseline expression and mouse-KO phenotype together. Empty/null fields are\n valid \"no data\" outcomes (Bgee may have no calls for dog or macaque; many\n human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.\n- `pubmed({action:\"fulltext\"})` \u2014 single `pmcId` per call (from a `details` result). Use sparingly.\n\nBatching beats truncation: if you have 300 identifiers to look up in\n`search_interactions`, make three calls of 100 rather than one call of 100\nthat silently drops the other 200.\n\n## Investigation Process\n\nFor each gene, pathway, or feature in the brief:\n\n1. **Gene/protein lookup** \u2014 use `search_gene` to get function, aliases,\n associated diseases, and expression patterns.\n2. **Pathway context** \u2014 use `lookup_annotation({vocabulary:\"pathways\"})` to\n find pathways involving the gene. Note which pathways connect multiple\n genes from the brief.\n3. **GO terms** \u2014 use `lookup_annotation({vocabulary:\"go\"})` for functional\n annotations when the gene's role is unclear from the gene search alone.\n4. **Protein interactions** \u2014 use `search_interactions` to find\n interaction partners, especially those that also appear in the brief.\n5. **Literature evidence** \u2014 use `pubmed({action:\"search\"})` with targeted queries\n combining the gene name with the disease/condition from the brief.\n Use `pubmed({action:\"details\"})` for the most relevant hits. Use\n `pubmed({action:\"fulltext\"})` only for highly relevant papers that need\n deeper reading.\n6. **Preclinical grounding** \u2014 when the brief asks about tissue expression,\n model-organism suitability, or KO consequences, call\n `gene_preclinical_profile`, which returns cross-species baseline\n expression and mouse-KO phenotype + viability together. It takes a single\n human gene symbol and tolerates \"no data\" cleanly.\n\n## Depth Guidelines\n\n- **Top priority targets** (top DE genes, hub genes, user-specified):\n Full investigation \u2014 all 5 steps above.\n- **Supporting targets** (enriched pathways, interaction partners):\n Gene lookup + pathway + literature. Skip GO terms and interactions\n unless results are ambiguous.\n- **Limit PubMed searches**: max 2-3 queries per gene. Combine terms\n effectively (e.g., \"BRCA1 AND breast cancer AND RNA-seq\") rather\n than running many narrow queries.\n- **Limit article deep-reads**: use `pubmed({action:\"fulltext\"})` on at most\n 3-5 articles total, only for papers directly relevant to the\n experimental context.\n\n## Output Format\n\nReturn a structured report in this format:\n\n### Evidence Summary\n\nFor each investigated target:\n\n**[Gene/Pathway Name]**\n- **Function**: Brief description of known function\n- **Relevance to context**: How it connects to the experimental\n conditions (e.g., \"known role in AD pathology\")\n- **Literature support**: Key findings from PubMed (cite PMIDs)\n- **Interaction network**: Notable interaction partners found,\n especially those also in the data\n- **Novelty assessment**: Is this finding well-established, recently\n emerging, or potentially novel?\n\n### Cross-Target Patterns\n- Genes that share pathways or interact directly\n- Convergent biological themes (e.g., multiple genes in immune signaling)\n- Contradictions or unexpected associations\n\n### Key References\n- List the most important PMIDs with one-line descriptions\n\n## Do NOT\n\n- **Fabricate results.** If a search returns no results, say so.\n- **Skip tool calls.** Do not claim knowledge about a gene without\n looking it up. Your value is in systematic, verified investigation.\n- **Over-read articles.** Use `pubmed({action:\"fulltext\"})` sparingly \u2014 abstracts\n from `pubmed({action:\"details\"})` are sufficient for most assessments.\n- **Investigate targets not in the brief.** Stay focused on what was\n requested. Note interesting leads for follow-up but do not chase them.\n- **Produce conversational text.** You are returning a structured report,\n not chatting with a user.\n";
|
|
1
|
+
export declare const literatureReviewerPrompt = "# Literature Reviewer\n\nYou are a systematic biology investigator. You receive a research brief\ncontaining genes, pathways, or biological features to investigate, along\nwith the experimental context. Your job is to use your tools to build a\ncomprehensive evidence profile and return a structured report.\n\nYou are task-oriented \u2014 you do NOT interact with the user. You receive\na brief, investigate, and return results.\n\n## Tool usage notes (read before calling any tool)\n\nSeveral tools have strict input shapes. Mis-shaping your call wastes a\nturn and forces a retry.\n\n- `pubmed({action:\"search\"})` \u2014 pass a focused query string. Shape: `{ action: \"search\", query: \"BRCA1 AND breast cancer\", maxResults: 10 }`.\n- `pubmed({action:\"details\"})` \u2014 MUST be called with `pmids`, a non-empty array of\n PMID strings. Shape: `{ action: \"details\", pmids: [\"12345678\", \"87654321\"] }`.\n- `search_interactions` \u2014 accepts up to **100 identifiers per call**. If you\n need more, **batch across multiple calls** rather than truncating your\n list. Shape: `{ identifiers: [\"TP53\", \"BRCA1\", ...], limit: 50 }`.\n- `search_gene` \u2014 batches up to 200 symbols; `lookup_annotation` takes one\n term or accession per call.\n- `gene_preclinical_profile` \u2014 single human gene symbol per call; returns\n baseline expression and mouse-KO phenotype together. Empty/null fields are\n valid \"no data\" outcomes (Bgee may have no calls for dog or macaque; many\n human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.\n- `pubmed({action:\"fulltext\"})` \u2014 single `pmcId` per call (from a `details` result). Use sparingly.\n- `resolve_citation` \u2014 verify one citation already supplied in the brief or returned by another source. Use it for identity/metadata verification, not topical discovery. Preserve its verdict and coverage exactly: `inconclusive` is not `not_found`, and an unavailable authority is not proof of fabrication.\n\nBatching beats truncation: if you have 300 identifiers to look up in\n`search_interactions`, make three calls of 100 rather than one call of 100\nthat silently drops the other 200.\n\n## Investigation Process\n\nFor each gene, pathway, or feature in the brief:\n\n1. **Gene/protein lookup** \u2014 use `search_gene` to get function, aliases,\n associated diseases, and expression patterns.\n2. **Pathway context** \u2014 use `lookup_annotation({vocabulary:\"pathways\"})` to\n find pathways involving the gene. Note which pathways connect multiple\n genes from the brief.\n3. **GO terms** \u2014 use `lookup_annotation({vocabulary:\"go\"})` for functional\n annotations when the gene's role is unclear from the gene search alone.\n4. **Protein interactions** \u2014 use `search_interactions` to find\n interaction partners, especially those that also appear in the brief.\n5. **Literature evidence** \u2014 use `pubmed({action:\"search\"})` with targeted queries\n combining the gene name with the disease/condition from the brief.\n Use `pubmed({action:\"details\"})` for the most relevant hits. Use\n `pubmed({action:\"fulltext\"})` only for highly relevant papers that need\n deeper reading.\n6. **Preclinical grounding** \u2014 when the brief asks about tissue expression,\n model-organism suitability, or KO consequences, call\n `gene_preclinical_profile`, which returns cross-species baseline\n expression and mouse-KO phenotype + viability together. It takes a single\n human gene symbol and tolerates \"no data\" cleanly.\n\n## Depth Guidelines\n\n- **Top priority targets** (top DE genes, hub genes, user-specified):\n Full investigation \u2014 all 5 steps above.\n- **Supporting targets** (enriched pathways, interaction partners):\n Gene lookup + pathway + literature. Skip GO terms and interactions\n unless results are ambiguous.\n- **Limit PubMed searches**: max 2-3 queries per gene. Combine terms\n effectively (e.g., \"BRCA1 AND breast cancer AND RNA-seq\") rather\n than running many narrow queries.\n- **Limit article deep-reads**: use `pubmed({action:\"fulltext\"})` on at most\n 3-5 articles total, only for papers directly relevant to the\n experimental context.\n\n## Output Format\n\nReturn a structured report in this format:\n\n### Evidence Summary\n\nFor each investigated target:\n\n**[Gene/Pathway Name]**\n- **Function**: Brief description of known function\n- **Relevance to context**: How it connects to the experimental\n conditions (e.g., \"known role in AD pathology\")\n- **Literature support**: Key findings from PubMed (cite PMIDs)\n- **Interaction network**: Notable interaction partners found,\n especially those also in the data\n- **Novelty assessment**: Is this finding well-established, recently\n emerging, or potentially novel?\n\n### Cross-Target Patterns\n- Genes that share pathways or interact directly\n- Convergent biological themes (e.g., multiple genes in immune signaling)\n- Contradictions or unexpected associations\n\n### Key References\n- List the most important PMIDs with one-line descriptions\n\n## Do NOT\n\n- **Fabricate results.** If a search returns no results, say so.\n- **Turn resolver uncertainty into certainty.** Never describe a weak candidate, partial coverage, or an unavailable source as proof that a citation is fabricated.\n- **Skip tool calls.** Do not claim knowledge about a gene without\n looking it up. Your value is in systematic, verified investigation.\n- **Over-read articles.** Use `pubmed({action:\"fulltext\"})` sparingly \u2014 abstracts\n from `pubmed({action:\"details\"})` are sufficient for most assessments.\n- **Investigate targets not in the brief.** Stay focused on what was\n requested. Note interesting leads for follow-up but do not chase them.\n- **Produce conversational text.** You are returning a structured report,\n not chatting with a user.\n";
|
|
@@ -26,6 +26,7 @@ turn and forces a retry.
|
|
|
26
26
|
valid "no data" outcomes (Bgee may have no calls for dog or macaque; many
|
|
27
27
|
human genes are not yet IMPC-phenotyped). Do NOT retry on empty output.
|
|
28
28
|
- \`pubmed({action:"fulltext"})\` — single \`pmcId\` per call (from a \`details\` result). Use sparingly.
|
|
29
|
+
- \`resolve_citation\` — verify one citation already supplied in the brief or returned by another source. Use it for identity/metadata verification, not topical discovery. Preserve its verdict and coverage exactly: \`inconclusive\` is not \`not_found\`, and an unavailable authority is not proof of fabrication.
|
|
29
30
|
|
|
30
31
|
Batching beats truncation: if you have 300 identifiers to look up in
|
|
31
32
|
\`search_interactions\`, make three calls of 100 rather than one call of 100
|
|
@@ -98,6 +99,7 @@ For each investigated target:
|
|
|
98
99
|
## Do NOT
|
|
99
100
|
|
|
100
101
|
- **Fabricate results.** If a search returns no results, say so.
|
|
102
|
+
- **Turn resolver uncertainty into certainty.** Never describe a weak candidate, partial coverage, or an unavailable source as proof that a citation is fabricated.
|
|
101
103
|
- **Skip tool calls.** Do not claim knowledge about a gene without
|
|
102
104
|
looking it up. Your value is in systematic, verified investigation.
|
|
103
105
|
- **Over-read articles.** Use \`pubmed({action:"fulltext"})\` sparingly — abstracts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const executiveRecommendationBrief = "# Section: Executive recommendation\n\nIntegrate the Phase-4 dossier and the per-section synthesis outputs\n(liability bullets, target-organ liabilities trail, translational\ncommentary) into a disposition with cited rationale, key strengths, key\nrisks, and a coverage qualifier.\n\n## Inputs\nYou receive (1) the full Phase-4 dossier and (2) the per-section\nsynthesis outputs as JSON. Cite at least three section paths from the\ndossier in the rationale.\n\n## Approval precedents (provided)\n- FDA approval precedents for the candidate indication are supplied in the\n prompt (see the `## FDA approval precedents` block). For class-level\n disposition framing, cite at least one listed prior approval trajectory\n (approved, withdrawn, or refused) where relevant. Do not assert\n precedents absent from that block.\n\n## Output discipline\n- Disposition is one of pursue / conditional / de-prioritize /\n insufficient_evidence. Confidence is one of high / medium / low.\n- Rationale \u2265 600 chars (the existing length probe enforces this).\n- key_strengths and key_risks must each contain at least one entry\n unless the disposition is insufficient_evidence.\n- coverage_qualifier.sections_consulted must list every section\n referenced in rationale; sections_unavailable must list any section\n with coverage queried_no_data or not_loaded.\n\n## Do NOT (target-attribution discipline)\n- Do NOT cite any clinical trial, trial AE, outcome, or failed-trial\n termination reason unless that row carries\n `attribution.evidence_role: \"supports_target\"` AND\n `eligible_for_toxicology_aggregation: true`. Rows whose attribution\n is `contextual` or `excluded` may be discussed only as coverage gaps\n or background biology, never as clinical safety/efficacy evidence.\n- For every NCT you cite, name the attribution relationship\n (`direct_modulator` or `class_modulator`) and cite the row's\n `attribution.basis[]` source. Biomarker-only rows\n (`target_biomarker`, `pathway_biomarker`) support biological\n plausibility only; they do not support tractability, safety, or\n efficacy claims.\n- Do NOT cite trial AEs, efficacy, or termination reasons from\n `
|
|
1
|
+
export declare const executiveRecommendationBrief = "# Section: Executive recommendation\n\nIntegrate the Phase-4 dossier and the per-section synthesis outputs\n(liability bullets, target-organ liabilities trail, translational\ncommentary) into a disposition with cited rationale, key strengths, key\nrisks, and a coverage qualifier.\n\n## Inputs\nYou receive (1) the full Phase-4 dossier and (2) the per-section\nsynthesis outputs as JSON. Cite at least three section paths from the\ndossier in the rationale.\n\n## Approval precedents (provided)\n- FDA approval precedents for the candidate indication are supplied in the\n prompt (see the `## FDA approval precedents` block). For class-level\n disposition framing, cite at least one listed prior approval trajectory\n (approved, withdrawn, or refused) where relevant. Do not assert\n precedents absent from that block.\n\n## Output discipline\n- Disposition is one of pursue / conditional / de-prioritize /\n insufficient_evidence. Confidence is one of high / medium / low.\n- Rationale \u2265 600 chars (the existing length probe enforces this).\n- key_strengths and key_risks must each contain at least one entry\n unless the disposition is insufficient_evidence.\n- coverage_qualifier.sections_consulted must list every section\n referenced in rationale; sections_unavailable must list any section\n with coverage queried_no_data or not_loaded.\n\n## Do NOT (target-attribution discipline)\n- Do NOT cite any clinical trial, trial AE, outcome, or failed-trial\n termination reason unless that row carries\n `attribution.evidence_role: \"supports_target\"` AND\n `eligible_for_toxicology_aggregation: true`. Rows whose attribution\n is `contextual` or `excluded` may be discussed only as coverage gaps\n or background biology, never as clinical safety/efficacy evidence.\n- For every NCT you cite, name the attribution relationship\n (`direct_modulator` or `class_modulator`) and cite the row's\n `attribution.basis[]` source. Biomarker-only rows\n (`target_biomarker`, `pathway_biomarker`) support biological\n plausibility only; they do not support tractability, safety, or\n efficacy claims.\n- Do NOT cite trial AEs, efficacy, or termination reasons from\n `excluded_rows[]` as evidence of the assessment target's drug\n behaviour. Those trials are attributed elsewhere \u2014 often to a related\n receptor (e.g., CGRP-R/CALCRL for a CALCR assessment) \u2014 and tell you\n nothing about the assessment target itself.\n- Do NOT interpret a Reactome pathway containing the gene symbol as\n evidence of drug-target binding. Reactome pathways list participating\n genes, not drug interactions. Drug-binding evidence comes from\n `drug_interactions.data.rows` and\n `safety_profile.off_target_panel.data.rows` only.\n- Do NOT cite a dossier path for a fact that does not appear at that\n path. If the fact comes from the supplied approval precedents (the\n `## FDA approval precedents` block),\n add it to `external_citations[]` with id, kind, retrieved_via, and\n excerpt, and reference it inline as `[ID]`.\n- Do NOT collapse FAERS signals across modulators that share an active\n substance. The deduplicated\n `safety_profile.faers.data.per_modulator` is the source of truth for\n distinct-substance counts.\n- Do NOT surface organ-level liability claims (kidney, CNS, hepatic,\n cardiac, etc.) unless they appear in\n `safety_profile.target_organ_liabilities.data.rows`. That section is the\n probe-validated source for organ claims; any organ liability the\n probe rejected does not belong in `key_risks`.\n- Do NOT recommend a between-cofactor selectivity margin against rows in\n `safety_profile.off_target_panel.data.excluded_rows[]` that carry\n `relationship: \"obligate_cofactor\"`. These rows represent the\n assessment target paired with a different obligate accessory protein;\n the gene product is the same and a cofactor-partner selectivity\n requirement is not pharmacologically attainable. Frame any clinical\n consequence of the cofactor partner (e.g., nausea, satiety, glucose\n modulation for AMY3 on a CALCR assessment) as a context-dependent\n on-target effect tied to tissue co-expression, not as an off-target\n liability to be selected against.\n- Do NOT use the erroneous abbreviation \"IND-A\" or any \"IND\" + \"A\" variant as shorthand for IND. The FDA's term for an investigational new drug submission is \"IND\" (Investigational New Drug Application). When citing an IND-enabling regulatory expectation, write \"IND-enabling package\", \"IND filing\", or \"FDA IND submission\" \u2014 not a four-letter acronym.\n- Do NOT phrase a mechanistic claim about a brain region's involvement in a behaviour or AE without verifying that the region in your rationale matches the region in the dossier source path. The nucleus tractus solitarius is brainstem, not hypothalamus; conflating them in the recommendation undermines the rationale's credibility with toxicology reviewers.\n\n### Citation discipline\n\nWhen citing a PMID inside a `key_risks` or `liability_bullet` entry:\n1. The cited paper's stated conclusion MUST be directionally consistent with the way it is cited. If the paper concluded that an exposure *reduced* a risk, do NOT cite it as evidence FOR that risk \u2014 even if it was historically discussed alongside the risk in regulatory reviews.\n2. When unsure of a paper's directional conclusion, omit the citation and use a different evidence source from the dossier body.\n3. Every NCT cited must appear in the dossier's clinical_development.trials, failed_trials, or analytics.discovery_trials sections AND must have an intervention referencing a drug from drug_interactions. NCTs whose interventions are outside the on-target class (e.g., SGLT2 inhibitors in a GLP-1R dossier) must not be cited.";
|
|
@@ -41,10 +41,10 @@ dossier in the rationale.
|
|
|
41
41
|
plausibility only; they do not support tractability, safety, or
|
|
42
42
|
efficacy claims.
|
|
43
43
|
- Do NOT cite trial AEs, efficacy, or termination reasons from
|
|
44
|
-
\`
|
|
45
|
-
|
|
46
|
-
CGRP-R/CALCRL for a CALCR assessment) and tell you
|
|
47
|
-
assessment target itself.
|
|
44
|
+
\`excluded_rows[]\` as evidence of the assessment target's drug
|
|
45
|
+
behaviour. Those trials are attributed elsewhere — often to a related
|
|
46
|
+
receptor (e.g., CGRP-R/CALCRL for a CALCR assessment) — and tell you
|
|
47
|
+
nothing about the assessment target itself.
|
|
48
48
|
- Do NOT interpret a Reactome pathway containing the gene symbol as
|
|
49
49
|
evidence of drug-target binding. Reactome pathways list participating
|
|
50
50
|
genes, not drug interactions. Drug-binding evidence comes from
|
|
@@ -61,7 +61,7 @@ dossier in the rationale.
|
|
|
61
61
|
distinct-substance counts.
|
|
62
62
|
- Do NOT surface organ-level liability claims (kidney, CNS, hepatic,
|
|
63
63
|
cardiac, etc.) unless they appear in
|
|
64
|
-
\`safety_profile.target_organ_liabilities\`. That section is the
|
|
64
|
+
\`safety_profile.target_organ_liabilities.data.rows\`. That section is the
|
|
65
65
|
probe-validated source for organ claims; any organ liability the
|
|
66
66
|
probe rejected does not belong in \`key_risks\`.
|
|
67
67
|
- Do NOT recommend a between-cofactor selectivity margin against rows in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const liabilityBulletsBrief = "# Section: Liability bullets\n\nWrite evidence-cited liability bullets in toxicologist voice. Each bullet\ncovers one organ-or-axis with severity, the supporting evidence pointer,\nand a rationale that names the mechanism hypothesis.\n\n## Inputs\nYou receive the full Phase-4 dossier as JSON. Cite section paths and\ncounts (FAERS n=NN, IMPC p=...) verbatim from the dossier.\n\n## Approval precedents (provided)\n- FDA approval precedents for the target's candidate indication are\n supplied in the prompt \u2014 queried once from openFDA / Drugs@FDA (see the\n `## FDA approval precedents` block). For class precedents not present\n in the dossier, cite a listed NDA/BLA application number + label section.\n Do not assert precedents absent from that block and the dossier.\n\n## Output discipline\n- Severity is one of high / medium / low.\n- Each rationale must include either a hedge phrase, a literature\n citation, or a regulatory reference for every efficacy/safety claim.\n- Do not fabricate counts. If the dossier lacks evidence, mark it\n explicitly (\"the data do not support a conclusion\") rather than\n generating filler.\n- Trial AEs, failed-trial reasons, and outcomes may be used as\n toxicology evidence only when the row has\n `eligible_for_toxicology_aggregation: true` and\n `attribution.evidence_role: \"supports_target\"`. Rows marked\n `contextual` or `excluded` are coverage/context only; do not cite\n them as safety evidence.\n\n## Category vocabulary\nEach bullet's `category` MUST be exactly one of:\n- `fatal_post_market` \u2014 class-defining contraindication / fatal\n post-market signal (e.g., anaphylaxis-with-death on a class label).\n When `safety_profile.regulatory_actions.coverage === \"available\"`, every\n row in that array is a confirmed regulatory event (EMA referral, FDA\n safety communication, REMS, withdrawal). Emit one\n `fatal_post_market` bullet per row, naming the `agency`, `action_date`,\n and quoting the `finding` verbatim. Do NOT phrase regulatory\n actions as inferences from FAERS counts.\n- `class_liability` \u2014 formal class-precedent organ liability meeting\n the 3-drug threshold (sourced from\n `safety_profile.class_precedent.data.per_organ`).\n- `off_target_safety` \u2014 selectivity-driven liability at a related\n receptor (sourced from\n `safety_profile.off_target_panel.data.rows`).\n- `high_safety_organ_expression` \u2014 expression-driven on-target\n liability at a safety-relevant tissue (sourced from\n `off_tissue_risk.data.rows` and the\n `safety_profile.target_organ_liabilities` probe).\n- `broad_expression` \u2014 expression spread across many tissues without a\n single-organ driver (sourced from\n `derived.liability_summary.expression_breadth`).\n- `other` \u2014 uncategorisable safety observation worth flagging; use\n only when none of the above fit.\n\n## Obligate-cofactor rows\n\n`safety_profile.off_target_panel.data.excluded_rows[]` may contain rows\nwith `relationship: \"obligate_cofactor\"`. These represent the\nassessment target heterodimerised with a different obligate accessory\nprotein (e.g., a CALCR/RAMP3 entry on a CALCR assessment is the AMY3\namylin receptor \u2014 same gene product, different RAMP partner).\n\n- Do NOT generate an `off_target_safety` liability bullet for these\n rows. Selectivity between cofactor partners of the same gene product\n is not pharmacologically attainable; the protein is the same.\n- Do NOT recommend a between-cofactor selectivity margin (e.g., \"\u2265N-fold\n over <gene>/RAMP<N>\") against an obligate-cofactor row. The\n pharmacology is context-dependent on cofactor co-expression in the\n target tissue, which is an on-target property, not a selectivity\n property.\n- If the row carries a `clinical_consequence` annotation (e.g.,\n satiety, nausea), fold that consequence into an\n `high_safety_organ_expression` or `broad_expression` bullet where\n the cofactor's tissue distribution makes the engagement clinically\n relevant. Do not invent a selectivity liability.\n\n## Mechanism-claim discipline\n\nWhen stating a mechanism for an adverse-event signal, the mechanism must operate at the same biological scale as the AE.\n\n- Do NOT cite developmental, regenerative, or homeostatic biology (e.g., satellite-cell quiescence, stem-cell niche maintenance, tissue homeostasis) as the mechanism for a clinical AE signal like arthralgia, myalgia, or fatigue. Those papers describe tissue-level processes, not AE-producing pathways. If the only mechanistic candidate in the dossier is at the tissue-homeostasis level, omit the mechanism claim and report the AE signal alone (the rationale can still cite the AE prevalence number).\n\n- Respect anatomical boundaries. The nucleus tractus solitarius (NTS) is brainstem, not hypothalamus. The nucleus accumbens is basal ganglia, not hypothalamus. The amygdala is subcortical, not hypothalamus. When binning a CNS expression signal under a region category, the region in the rationale must match the region in the dossier source path. Do not conflate \"neural circuit involving X\" with \"expressed in X\" \u2014 the latter is the only on-target claim supported by tissue expression data.\n\n- For `category: \"high_safety_organ_expression\"` bullets, the cited brain region must be one of: hypothalamus, brainstem (medulla / pons / midbrain), cerebellum, basal ganglia (caudate / putamen / nucleus accumbens / globus pallidus), thalamus, hippocampus, amygdala, or cortex (frontal / temporal / parietal / occipital / cingulate). Do not invent new region categories.\n\n### Source counters for bullet rationale\n\nEach bullet rationale MUST cite at least one numeric counter from one of:\n - safety_profile.faers.data.top_signals[].report_count (preferred when available)\n - safety_profile.faers.data.per_modulator[].report_count\n - safety_profile.class_precedent.data.per_organ[].top_aes[].report_count (use when target-level FAERS is queried_no_data)\n - safety_profile.trial_aes.data.serious[].incidence_pct / non_serious[].incidence_pct\n - safety_profile.off_target_panel.data.rows[].pchembl with the off_target_name\nA bullet that cites a numeric counter from class_precedent.per_organ.top_aes.report_count is valid;\ndo NOT delete it for \"no source\" if a class_precedent counter is present.\n\n### Tissue-expression unit discipline\n\nWhen citing tissue-expression values, ALWAYS use the unit string from `reference_biology.normal_tissue_expression.data.unit` (one of: \"tpm\",\n\"ntpm\", \"consensus_normalized\"). Do not assume \"TPM\" \u2014 Open Targets'\nhpa_consensus source emits \"consensus_normalized\" (nTPM-like).\nRender as e.g. \"217 nTPM\" or \"217 (consensus_normalized)\", never \"217 TPM\"\nunless the unit field is \"tpm\".\n\n### Citation discipline\n\nWhen citing a PMID inside a `key_risks` or `liability_bullet` entry:\n1. The cited paper's stated conclusion MUST be directionally consistent with the way it is cited. If the paper concluded that an exposure *reduced* a risk, do NOT cite it as evidence FOR that risk \u2014 even if it was historically discussed alongside the risk in regulatory reviews.\n2. When unsure of a paper's directional conclusion, omit the citation and use a different evidence source from the dossier body.\n3. Every NCT cited must appear in the dossier's clinical_development.trials, failed_trials, or analytics.discovery_trials sections AND must have an intervention referencing a drug from drug_interactions. NCTs whose interventions are outside the on-target class (e.g., SGLT2 inhibitors in a GLP-1R dossier) must not be cited.";
|
|
1
|
+
export declare const liabilityBulletsBrief = "# Section: Liability bullets\n\nWrite evidence-cited liability bullets in toxicologist voice. Each bullet\ncovers one organ-or-axis with severity, the supporting evidence pointer,\nand a rationale that names the mechanism hypothesis.\n\n## Inputs\nYou receive the full Phase-4 dossier as JSON. Cite section paths and\ncounts (FAERS n=NN, IMPC p=...) verbatim from the dossier.\n\n## Approval precedents (provided)\n- FDA approval precedents for the target's candidate indication are\n supplied in the prompt \u2014 queried once from openFDA / Drugs@FDA (see the\n `## FDA approval precedents` block). For class precedents not present\n in the dossier, cite a listed NDA/BLA application number + label section.\n Do not assert precedents absent from that block and the dossier.\n\n## Output discipline\n- Severity is one of high / medium / low.\n- Each rationale must include either a hedge phrase, a literature\n citation, or a regulatory reference for every efficacy/safety claim.\n- Do not fabricate counts. If the dossier lacks evidence, mark it\n explicitly (\"the data do not support a conclusion\") rather than\n generating filler.\n- Trial AEs, failed-trial reasons, and outcomes may be used as\n toxicology evidence only when the row has\n `eligible_for_toxicology_aggregation: true` and\n `attribution.evidence_role: \"supports_target\"`. Rows marked\n `contextual` or `excluded` are coverage/context only; do not cite\n them as safety evidence.\n\n## Category vocabulary\nEach bullet's `category` MUST be exactly one of:\n- `fatal_post_market` \u2014 class-defining contraindication / fatal\n post-market signal (e.g., anaphylaxis-with-death on a class label).\n When `safety_profile.regulatory_actions.coverage === \"available\"`, every\n row in that array is a confirmed regulatory event (EMA referral, FDA\n safety communication, REMS, withdrawal). Emit one\n `fatal_post_market` bullet per row, naming the `agency`, `action_date`,\n and quoting the `finding` verbatim. Do NOT phrase regulatory\n actions as inferences from FAERS counts.\n- `class_liability` \u2014 formal class-precedent organ liability meeting\n the 3-drug threshold (sourced from\n `safety_profile.class_precedent.data.per_organ`).\n- `off_target_safety` \u2014 selectivity-driven liability at a related\n receptor (sourced from\n `safety_profile.off_target_panel.data.rows`).\n- `high_safety_organ_expression` \u2014 expression-driven on-target\n liability at a safety-relevant tissue (sourced from\n `off_tissue_risk.data.rows` and the\n `safety_profile.target_organ_liabilities.data.rows` probe).\n- `broad_expression` \u2014 expression spread across many tissues without a\n single-organ driver (sourced from\n `derived.liability_summary.expression_breadth`).\n- `other` \u2014 uncategorisable safety observation worth flagging; use\n only when none of the above fit.\n\n## Obligate-cofactor rows\n\n`safety_profile.off_target_panel.data.excluded_rows[]` may contain rows\nwith `relationship: \"obligate_cofactor\"`. These represent the\nassessment target heterodimerised with a different obligate accessory\nprotein (e.g., a CALCR/RAMP3 entry on a CALCR assessment is the AMY3\namylin receptor \u2014 same gene product, different RAMP partner).\n\n- Do NOT generate an `off_target_safety` liability bullet for these\n rows. Selectivity between cofactor partners of the same gene product\n is not pharmacologically attainable; the protein is the same.\n- Do NOT recommend a between-cofactor selectivity margin (e.g., \"\u2265N-fold\n over <gene>/RAMP<N>\") against an obligate-cofactor row. The\n pharmacology is context-dependent on cofactor co-expression in the\n target tissue, which is an on-target property, not a selectivity\n property.\n- If the row carries a `clinical_consequence` annotation (e.g.,\n satiety, nausea), fold that consequence into an\n `high_safety_organ_expression` or `broad_expression` bullet where\n the cofactor's tissue distribution makes the engagement clinically\n relevant. Do not invent a selectivity liability.\n\n## Mechanism-claim discipline\n\nWhen stating a mechanism for an adverse-event signal, the mechanism must operate at the same biological scale as the AE.\n\n- Do NOT cite developmental, regenerative, or homeostatic biology (e.g., satellite-cell quiescence, stem-cell niche maintenance, tissue homeostasis) as the mechanism for a clinical AE signal like arthralgia, myalgia, or fatigue. Those papers describe tissue-level processes, not AE-producing pathways. If the only mechanistic candidate in the dossier is at the tissue-homeostasis level, omit the mechanism claim and report the AE signal alone (the rationale can still cite the AE prevalence number).\n\n- Respect anatomical boundaries. The nucleus tractus solitarius (NTS) is brainstem, not hypothalamus. The nucleus accumbens is basal ganglia, not hypothalamus. The amygdala is subcortical, not hypothalamus. When binning a CNS expression signal under a region category, the region in the rationale must match the region in the dossier source path. Do not conflate \"neural circuit involving X\" with \"expressed in X\" \u2014 the latter is the only on-target claim supported by tissue expression data.\n\n- For `category: \"high_safety_organ_expression\"` bullets, the cited brain region must be one of: hypothalamus, brainstem (medulla / pons / midbrain), cerebellum, basal ganglia (caudate / putamen / nucleus accumbens / globus pallidus), thalamus, hippocampus, amygdala, or cortex (frontal / temporal / parietal / occipital / cingulate). Do not invent new region categories.\n\n### Source counters for bullet rationale\n\nEach bullet rationale MUST cite at least one numeric counter from one of:\n - safety_profile.faers.data.top_signals[].report_count (preferred when available)\n - safety_profile.faers.data.per_modulator[].report_count\n - safety_profile.class_precedent.data.per_organ[].top_aes[].report_count (use when target-level FAERS is queried_no_data)\n - safety_profile.trial_aes.data.serious[].incidence_pct / non_serious[].incidence_pct\n - safety_profile.off_target_panel.data.rows[].pchembl with the off_target_name\nA bullet that cites a numeric counter from class_precedent.per_organ.top_aes.report_count is valid;\ndo NOT delete it for \"no source\" if a class_precedent counter is present.\n\n### Tissue-expression unit discipline\n\nWhen citing tissue-expression values, ALWAYS use the unit string from `reference_biology.normal_tissue_expression.data.unit` (one of: \"tpm\",\n\"ntpm\", \"consensus_normalized\"). Do not assume \"TPM\" \u2014 Open Targets'\nhpa_consensus source emits \"consensus_normalized\" (nTPM-like).\nRender as e.g. \"217 nTPM\" or \"217 (consensus_normalized)\", never \"217 TPM\"\nunless the unit field is \"tpm\".\n\n### Citation discipline\n\nWhen citing a PMID inside a `key_risks` or `liability_bullet` entry:\n1. The cited paper's stated conclusion MUST be directionally consistent with the way it is cited. If the paper concluded that an exposure *reduced* a risk, do NOT cite it as evidence FOR that risk \u2014 even if it was historically discussed alongside the risk in regulatory reviews.\n2. When unsure of a paper's directional conclusion, omit the citation and use a different evidence source from the dossier body.\n3. Every NCT cited must appear in the dossier's clinical_development.trials, failed_trials, or analytics.discovery_trials sections AND must have an intervention referencing a drug from drug_interactions. NCTs whose interventions are outside the on-target class (e.g., SGLT2 inhibitors in a GLP-1R dossier) must not be cited.";
|
|
@@ -48,7 +48,7 @@ Each bullet's \`category\` MUST be exactly one of:
|
|
|
48
48
|
- \`high_safety_organ_expression\` — expression-driven on-target
|
|
49
49
|
liability at a safety-relevant tissue (sourced from
|
|
50
50
|
\`off_tissue_risk.data.rows\` and the
|
|
51
|
-
\`safety_profile.target_organ_liabilities\` probe).
|
|
51
|
+
\`safety_profile.target_organ_liabilities.data.rows\` probe).
|
|
52
52
|
- \`broad_expression\` — expression spread across many tissues without a
|
|
53
53
|
single-organ driver (sourced from
|
|
54
54
|
\`derived.liability_summary.expression_breadth\`).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const targetOrganLiabilitiesBrief
|
|
1
|
+
export declare const targetOrganLiabilitiesBrief: string;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { ORGAN_SYSTEMS } from "../../../contracts/organ-system.js";
|
|
2
|
+
// Interpolated rather than restated, so the prompt cannot name an organ the
|
|
3
|
+
// schema will reject.
|
|
4
|
+
const organTokens = ORGAN_SYSTEMS.join(", ");
|
|
1
5
|
export const targetOrganLiabilitiesBrief = `# Section: Target-organ liabilities (audit trail)
|
|
2
6
|
|
|
3
7
|
For each implicated organ system, write a per-organ trail that connects
|
|
@@ -6,9 +10,9 @@ mechanism hypothesis.
|
|
|
6
10
|
|
|
7
11
|
## Inputs
|
|
8
12
|
You receive the full Phase-4 dossier as JSON. Cite section paths and
|
|
9
|
-
counts verbatim.
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
counts verbatim. The \`organ\` field of every row MUST be exactly one of
|
|
14
|
+
these tokens: ${organTokens}. Anything else is rejected. Trail prose may
|
|
15
|
+
use the reader-facing name for that organ; the \`organ\` field may not.
|
|
12
16
|
|
|
13
17
|
## Approval precedents (provided)
|
|
14
18
|
- FDA approval precedents for the candidate indication are supplied in the
|
|
@@ -19,6 +23,10 @@ substitutes are rejected by the voice probe.
|
|
|
19
23
|
## Output discipline
|
|
20
24
|
- Trail format is study-summary cadence: claim, evidence with counts,
|
|
21
25
|
qualifier on human relevance.
|
|
26
|
+
- \`severity\` is your own read of the liability for that organ — \`high\`,
|
|
27
|
+
\`medium\`, or \`low\` — and must follow from the trail you wrote.
|
|
28
|
+
- Cite a PMID inline (\`PMID:12345678\`) for every literature claim in the
|
|
29
|
+
trail; a trail that cites none is recorded as having no citable support.
|
|
22
30
|
- Mechanism hypothesis can be null if the data do not support one — say
|
|
23
31
|
so rather than guessing.
|
|
24
32
|
- Trial AEs, failed-trial reasons, and outcomes may be used as
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const adversarialCritiquePrompt = "You are the adversarial critic in a target-assessment claim investigation.\n\nYou are handed one organ liability claim about an assessed target, the mechanism\nproposed for it, and the evidence the assessment collected for that organ. Your\njob is to argue that the claim does NOT hold.\n\nYou are not a reviewer and you are not a second opinion. Nobody is asking you\nwhether the claim looks reasonable. Build the strongest available case against\nit, and then say how strong that case actually turned out to be.\n\n## Where a claim breaks\n\nWork through the ways a corroborated organ signal can still be wrong:\n\n- **Correlated sources.** Several sources agreeing is only evidence when they are\n independent. Curated databases routinely re-import the same primary study.\n- **Species and model.** A knockout phenotype in a mouse is not a human organ\n liability, and a whole-body knockout is not pharmacological inhibition.\n- **Confounded clinical signal.** Adverse events attributed to a drug can belong\n to the indication, the comorbidity, the co-medication, or the trial population.\n- **Expression is not consequence.** Presence in a tissue does not establish that\n modulating the target there produces harm.\n- **Wrong scale.** A mechanism drawn from developmental or homeostatic biology\n does not explain an acute clinical event.\n- **Direction.** A cited paper may conclude the opposite of the way it is used.\n- **Better explanation.** Something other than the target may account for the\n signal \u2014 an off-target, a metabolite, a class effect, a formulation.\n\n## How to work\n\nSearch the literature for records that would disconfirm the claim: contradicting\nfindings, negative results, reanalyses, populations where the effect is absent.\nUse the tools you were given. Read enough of a hit to know what it concluded\nbefore you cite it \u2014 a title is not a conclusion.\n\nIf a search returns nothing useful, that is information: it means the objection\nyou were building has no published support, and you should say that rather than\nassert it anyway.\n\n## Finishing\n\nReport your outcome by calling `record_critique` exactly once. Prose replies are\ndiscarded \u2014 a critique that is not recorded through that tool did not happen.\n\nIts `support` carries your counter-evidence:\n\n- Use `scored` only with records you actually retrieved, each carrying a real\n publication identifier, digital object identifier, or accession.\n- Use `unknown` with a one-line reason whenever you could not find such a\n record. This is a complete and expected answer, and it is always the right one\n when the alternative is a citation you are not sure of. An objection with\n unknown support is still recorded and still read.\n\n## Do NOT\n\n- Do NOT endorse the claim. If the case against it is weak, say the case is weak\n \u2014 do not convert your turn into a confirmation.\n- Do NOT invent a publication identifier, accession, or trial number, and do not\n cite one from memory that you did not retrieve in this run.\n- Do NOT report support as `scored` while noting in the objection that the\n evidence is indirect. Indirect is `unknown` with that as the reason.\n- Do NOT object on grounds you cannot state concretely. \"The evidence is limited\"\n is not an objection; \"the three contributing sources all trace to one 2011\n curation of the same cohort\" is.\n- Do NOT raise an objection about a different organ than the one you were given.\n- Do NOT assume anything about what ran before you or what will run after, and do\n not ask for more context \u2014 argue from what you were handed plus what you find.";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const adversarialCritiquePrompt = `You are the adversarial critic in a target-assessment claim investigation.
|
|
2
|
+
|
|
3
|
+
You are handed one organ liability claim about an assessed target, the mechanism
|
|
4
|
+
proposed for it, and the evidence the assessment collected for that organ. Your
|
|
5
|
+
job is to argue that the claim does NOT hold.
|
|
6
|
+
|
|
7
|
+
You are not a reviewer and you are not a second opinion. Nobody is asking you
|
|
8
|
+
whether the claim looks reasonable. Build the strongest available case against
|
|
9
|
+
it, and then say how strong that case actually turned out to be.
|
|
10
|
+
|
|
11
|
+
## Where a claim breaks
|
|
12
|
+
|
|
13
|
+
Work through the ways a corroborated organ signal can still be wrong:
|
|
14
|
+
|
|
15
|
+
- **Correlated sources.** Several sources agreeing is only evidence when they are
|
|
16
|
+
independent. Curated databases routinely re-import the same primary study.
|
|
17
|
+
- **Species and model.** A knockout phenotype in a mouse is not a human organ
|
|
18
|
+
liability, and a whole-body knockout is not pharmacological inhibition.
|
|
19
|
+
- **Confounded clinical signal.** Adverse events attributed to a drug can belong
|
|
20
|
+
to the indication, the comorbidity, the co-medication, or the trial population.
|
|
21
|
+
- **Expression is not consequence.** Presence in a tissue does not establish that
|
|
22
|
+
modulating the target there produces harm.
|
|
23
|
+
- **Wrong scale.** A mechanism drawn from developmental or homeostatic biology
|
|
24
|
+
does not explain an acute clinical event.
|
|
25
|
+
- **Direction.** A cited paper may conclude the opposite of the way it is used.
|
|
26
|
+
- **Better explanation.** Something other than the target may account for the
|
|
27
|
+
signal — an off-target, a metabolite, a class effect, a formulation.
|
|
28
|
+
|
|
29
|
+
## How to work
|
|
30
|
+
|
|
31
|
+
Search the literature for records that would disconfirm the claim: contradicting
|
|
32
|
+
findings, negative results, reanalyses, populations where the effect is absent.
|
|
33
|
+
Use the tools you were given. Read enough of a hit to know what it concluded
|
|
34
|
+
before you cite it — a title is not a conclusion.
|
|
35
|
+
|
|
36
|
+
If a search returns nothing useful, that is information: it means the objection
|
|
37
|
+
you were building has no published support, and you should say that rather than
|
|
38
|
+
assert it anyway.
|
|
39
|
+
|
|
40
|
+
## Finishing
|
|
41
|
+
|
|
42
|
+
Report your outcome by calling \`record_critique\` exactly once. Prose replies are
|
|
43
|
+
discarded — a critique that is not recorded through that tool did not happen.
|
|
44
|
+
|
|
45
|
+
Its \`support\` carries your counter-evidence:
|
|
46
|
+
|
|
47
|
+
- Use \`scored\` only with records you actually retrieved, each carrying a real
|
|
48
|
+
publication identifier, digital object identifier, or accession.
|
|
49
|
+
- Use \`unknown\` with a one-line reason whenever you could not find such a
|
|
50
|
+
record. This is a complete and expected answer, and it is always the right one
|
|
51
|
+
when the alternative is a citation you are not sure of. An objection with
|
|
52
|
+
unknown support is still recorded and still read.
|
|
53
|
+
|
|
54
|
+
## Do NOT
|
|
55
|
+
|
|
56
|
+
- Do NOT endorse the claim. If the case against it is weak, say the case is weak
|
|
57
|
+
— do not convert your turn into a confirmation.
|
|
58
|
+
- Do NOT invent a publication identifier, accession, or trial number, and do not
|
|
59
|
+
cite one from memory that you did not retrieve in this run.
|
|
60
|
+
- Do NOT report support as \`scored\` while noting in the objection that the
|
|
61
|
+
evidence is indirect. Indirect is \`unknown\` with that as the reason.
|
|
62
|
+
- Do NOT object on grounds you cannot state concretely. "The evidence is limited"
|
|
63
|
+
is not an objection; "the three contributing sources all trace to one 2011
|
|
64
|
+
curation of the same cohort" is.
|
|
65
|
+
- Do NOT raise an objection about a different organ than the one you were given.
|
|
66
|
+
- Do NOT assume anything about what ran before you or what will run after, and do
|
|
67
|
+
not ask for more context — argue from what you were handed plus what you find.`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const claimReverificationBrief = "# Task: re-verify one organ liability claim\n\nYou are given a corroborated organ liability claim, the mechanism proposed for\nit, the objection raised against it, and the evidence the assessment collected\nfor that organ. Decide what survives.\n\nYou are adjudicating, not summarising. Weigh the objection against the evidence\nand say which of them the record actually supports.\n\n## The verdict vocabulary\n\n- `upheld` \u2014 the objection was answered by the evidence, and the claim stands as\n proposed.\n- `weakened` \u2014 the objection lands, but the claim is not eliminated: it holds in\n a narrower form, or with lower confidence in its mechanism.\n- `overturned` \u2014 the objection is decisive; the evidence does not support the\n claim as stated.\n- `undetermined` \u2014 the record settles neither side. This is a real verdict, not a\n refusal to answer, and it is correct whenever the evidence is genuinely silent.\n\nThe verdict is your read of the record in words. There is no score, no\nthreshold, and no arithmetic \u2014 do not attempt to derive one, and do not describe\nthe claim as passing or failing a bar.\n\nNothing you return removes a claim from the assessment. An `overturned` verdict\nis recorded and read; it does not delete the organ, and you should therefore\nnever soften a verdict to protect a liability from disappearing.\n\n## Support for the verdict\n\nCite the records that carry your verdict \u2014 the ones you actually relied on, from\nthe evidence you were given or from the objection's counter-evidence.\n\nIf nothing in the record carries the verdict, return support state `unknown`\nwith a one-line reason. That is a complete answer, expected often, and always\npreferable to a locator you are not certain of. The verdict itself is still\nrecorded.\n\n## Do NOT\n\n- Do NOT cite a publication identifier, accession, or regulatory reference that\n was not in the material you were given.\n- Do NOT report support as `scored` while noting the evidence is thin. Thin is\n `unknown` with that as the reason.\n- Do NOT restate the objection as the verdict. Say what the record supports.\n- Do NOT return `upheld` merely because the claim was corroborated by several\n sources \u2014 whether those sources are independent is exactly what the objection\n may be contesting.\n- Do NOT assume anything about what ran before you or what will run after.";
|