Map claims to concepts, propose typed edges between them, then regenerate the OKF bundle and the researcher/grant relation file.
/ref:weave <pmid…> | <selector>
/ref:weave --regenerate-only
/ref:weave review <relation-id> <type> --rationale "…"
/ref:weave neighbors <concept-slug>
Arguments
- <pmid…> | selector
- The papers whose claims to weave. With nothing given, the command skips to regeneration.
- --regenerate-only
- Skip claim comparison and only rebuild derived views. Useful after
/ref:verify or an /ref:extract rerun changes data without adding claims.
- review <id> <type>
- Promote or demote a relation, e.g.
potential_conflict → contradicts. contradicts is refused without a non-empty --rationale. A review also clears stale.
- neighbors <slug>
- Every relation touching a concept, split into
outgoing and incoming, with the supporting-claim trail.
What happens
- Resolves the library root and the selector to a PMID list (
lib_selector.py).
- For each active claim, maps its intervention/outcome (or whichever fields it is about) to concepts, done inline in the session since no subagent covers this. It always runs
concept.py find first, adds a near-match wording as an alias, and creates a concept only when nothing matches.
- For claim pairs from different PMIDs on the same concept pair, runs
relation.py propose. It returns null unless directions are increase vs decrease and comparator, effect_measure, timepoint and population all match. A proposal is persisted with create.
- When the agent reads two papers and judges that one supports, extends or replicates the other, it uses
relation.py create-manual --type … and states its reasoning. Weave never marks contradicts.
- Always regenerates:
okf_emit.py writes okf/, and graph_people.py writes graph/people_relations.jsonl.
Scope: only claims already mapped to the same concept pair, within the selected papers. A library-wide sweep is a separate operation, batched by selector.
Reads & writes
Reads papers/<pmid>/claim_registry.json, meta.json, authorship.json, funding.json, people/, grants/. Writes graph/concepts.jsonl, graph/relations.jsonl, graph/people_relations.jsonl, and okf/concepts/<id>.md, okf/papers/<citekey>.md, okf/people/<slug>.md, okf/grants/<slug>.md, okf/index.md, okf/log.md.
Example
output from a scratch library
› /ref:weave 38000001 38000002
# two RCT claims on drug X → SBP, same population/comparator/12 weeks, opposite directions
{
"relation_id": "rel-026322c5ae88",
"type": "potential_conflict",
"subject_concept_id": "drug-x",
"object_concept_id": "sbp",
"supporting_claims": [
{"pmid": "38000001", "claim_id": "c-9ba8993385ec"},
{"pmid": "38000002", "claim_id": "c-cd1ab0018c9f"}
],
"source_version_ids": ["v-12d4ca1b6864", "v-913f399b38b5"],
"review_state": "unreviewed",
"rationale": null,
"stale": false,
…
}
{
"concepts": 3,
"papers": 3,
"people": 0,
"grants": 0,
"relations_available": true
}
› /ref:weave review rel-026322c5ae88 contradicts
error: promoting to 'contradicts' requires a non-empty rationale -- never auto-promoted from potential_conflict (§4a)
› /ref:weave review rel-026322c5ae88 contradicts --rationale "Same population, comparator and 12-week timepoint; directions genuinely oppose."
{
"relation_id": "rel-026322c5ae88",
"type": "contradicts",
"review_state": "reviewed",
"rationale": "Same population, comparator and 12-week timepoint; directions genuinely oppose.",
"reviewer": "user",
"reviewed_at": "2026-09-15T10:15:49.128008+00:00",
"stale": false,
…
}
The generated okf/concepts/sbp.md lists its aliases and relations with review state, e.g. **potential_conflict** -> [drug-x](../concepts/drug-x.md) [unreviewed].
Under the hood
concept.py find|create|add-alias, relation.py propose|create|create-manual|review|neighbors, okf_emit.py --repo, graph_people.py --repo. relation.py also has refresh (marks edges stale when a supporting claim is superseded or excluded), show and list.
See also
/ref:concept · /ref:gaps · /ref:verify · /ref:extract · Graph discovery tutorial
Look up, create and alias the stable concept nodes in graph/concepts.jsonl.
/ref:concept find "<name or alias>"
/ref:concept create <slug> "<canonical name>"
/ref:concept add-alias <slug> "<alias>" [--source "claim:<pmid>:<claim_id>" | manual]
/ref:concept show <slug>
/ref:concept list
Arguments
- find
- Case-insensitive, whitespace-normalized match against every concept's name and aliases. Prints the concept, or
null. Always run this before minting a concept.
- create
- Mints a concept. Refused if
find would already match the name, and refused if the slug is taken.
- add-alias
- Adds a wording with provenance (
--source, default manual). A no-op if it already resolves here; refused if it resolves to a different concept, because that is a merge decision.
- show / list
- One concept by slug, or all of them.
Why a slug you choose? Aliases pile up over time, so "MI" and "myocardial infarction" end up on one node. The key has to be stable and independent of any single wording.
What happens
- Resolves the library root.
- Runs the matching
concept.py action and prints its JSON verbatim.
Reads & writes
Reads and writes graph/concepts.jsonl under the library lock (find, show, list are read-only). Rows: concept_id, name, aliases, alias_provenance, created_at, updated_at.
Example
output from a scratch library
› /ref:concept find "drug X"
null
› /ref:concept create sbp "systolic blood pressure"
› /ref:concept add-alias sbp "SBP"
{
"alias_provenance": {
"SBP": {"source": "manual", "added_at": "2026-09-15T10:15:24.748773+00:00"}
},
"aliases": ["SBP"],
"concept_id": "sbp",
"created_at": "2026-09-15T10:15:24.666167+00:00",
"name": "systolic blood pressure",
"updated_at": "2026-09-15T10:15:24.749358+00:00"
}
› /ref:concept create sbp2 "sbp"
error: a concept already matches 'sbp': 'sbp' (name='systolic blood pressure') -- use find_concept()/add_alias() instead of minting a duplicate
Under the hood
concept.py {find|create|add-alias|show|list} --repo <root> [--id <slug>] [--name …] [--alias …] [--source …]
See also
/ref:weave · /ref:hypothesize · Concepts: identifiers
/ref:gaps
phase 9read-only
Run deterministic structural gap queries over a selected set of papers, with every finding pointing at the claims, relations or PMIDs it rests on.
/ref:gaps <selector> [--intervention-concept <slug>] [--types <type…>]
Arguments
- selector
- Required: bare
<pmid…>, --project, --study, --search, --from-file, and the rest of the shared grammar.
- --intervention-concept
- Needed only for
population_outcome_gap; omit to skip that query.
- --types
- Any of
single_study_fragile, unresolved_conflicts, co_mentioned_ungrouped, population_outcome_gap. Default: all applicable.
What happens
- single_study_fragile: an active claim is fragile unless its paper is in a study group with ≥2 PMIDs, or it shares a
supports/extends/replicates edge with a claim from another PMID.
- unresolved_conflicts: unreviewed
potential_conflict edges, plus contradicts edges that have gone stale, touching the selected PMIDs.
- co_mentioned_ungrouped: two concepts whose names or aliases both appear in one claim's
evidence_span with no edge between them.
- population_outcome_gap: for the intervention concept, a population × outcome combination with no claim while sibling combinations have one.
No LLM judgment. Missing edges describe this library's coverage, not an established gap in the literature, and results should be presented that way.
Reads & writes
Reads claim_registry.json, graph/concepts.jsonl, graph/relations.jsonl, studies/studies.jsonl. Writes nothing.
Example
output from a scratch library
› /ref:gaps 38000001 38000002 38000003 --intervention-concept drug-x
{
"selector_expression": "--pmid 38000001 38000002 38000003",
"gaps": {
"single_study_fragile": [
{"gap_type": "single_study_fragile", "claim_id": "c-9ba8993385ec", "pmid": "38000001",
"outcome": "systolic blood pressure", "intervention": "drug X"},
…
],
"unresolved_conflicts": [
{
"gap_type": "unresolved_conflict",
"relation_id": "rel-026322c5ae88",
"type": "potential_conflict",
"stale": false,
"subject_concept_id": "drug-x",
"object_concept_id": "sbp",
"supporting_claims": [
{"claim_id": "c-9ba8993385ec", "pmid": "38000001"},
{"claim_id": "c-cd1ab0018c9f", "pmid": "38000002"}
]
}
],
"co_mentioned_ungrouped": [],
"population_outcome_gap": []
}
}
Under the hood
gaps.py --repo <root> <selector…> [--intervention-concept <slug>] [--types …]. Note: the shared --concept selector still raises "not available until phase 8" in lib_selector.py, so select by PMIDs, project, study or search instead.
See also
/ref:weave · /ref:hypothesize · /ref:study · /ref:related
/ref:hypothesize
phase 9PubMedread-only
Find untested A–C concept pairs by two-hop traversal and check each against PubMed, producing a ranked reading list with full evidence chains.
/ref:hypothesize --concept <slug>
Arguments
- --concept <slug>
- Required. The starting concept A.
What happens
hypothesize.py candidates traverses A→B→C with no network. A chain is skipped when both edges rest on the exact same PMID set, and any C already joined to A directly is excluded.
- Candidates are ranked by the number of independent chains, with ties broken alphabetically by C. If there are none, the command says so and stops.
- For each candidate, the agent calls PubMed
search_articles with a query combining A's and C's names or aliases, and records the query, an ISO timestamp and the result PMIDs.
- Those checks go into a JSON file keyed by
concept_c, which is passed to hypothesize.py finalize. Candidates the agent didn't check are left out rather than filled in.
- Output is printed verbatim. Zero hits reads "Not found by this search", never "novel" or "confirmed gap".
Reads & writes
Reads graph/relations.jsonl and a temporary checks file. Writes nothing to the library.
Example
output from a scratch library (PubMed check supplied by hand)
› /ref:hypothesize --concept drug-x
# candidates: 1 · PubMed: "drug X" AND "cognitive decline" → 0 results
[
{
"concept_a": "drug-x",
"concept_c": "cognitive-decline",
"chain_count": 1,
"chains": [
{
"concept_b": "sbp",
"a_b_relation_id": "rel-026322c5ae88",
"a_b_type": "potential_conflict",
"a_b_supporting_claims": [ … 38000001, 38000002 … ],
"b_c_relation_id": "rel-1211e326b079",
"b_c_type": "supports",
"b_c_supporting_claims": [ … 38000003 … ]
}
],
"pubmed_check": {
"query": "\"drug X\" AND \"cognitive decline\"",
"retrieved_at": "2026-09-15T10:00:00+00:00",
"result_pmids": []
},
"pubmed_note": "Not found by this search ('\"drug X\" AND \"cognitive decline\"') -- untested in the library, and this specific search found no evidence it's studied in the wider literature either. Not proof of novelty: a different query might find something this one didn't.",
"limitations": "Candidate derived from 1 independent A-B/B-C chain(s) in this library's graph between 'drug-x' and 'cognitive-decline'. Missing edges describe library coverage, not established literature gaps (§5b). This is a reading-list candidate, not a validated claim."
}
]
Under the hood
hypothesize.py candidates --repo <root> --concept <slug>, then hypothesize.py finalize --repo <root> --concept <slug> --checks-file <json>. The PubMed call is made by the command, never by the script.
See also
/ref:gaps · /ref:search-pubmed · /ref:add · Graph discovery tutorial