Tutorial
From claims to a concept graph, gaps, and candidate hypotheses
You'll turn extracted claims into concepts and typed edges, review a conflict before you call it a contradiction, ask the graph where your evidence is thin, run a Swanson ABC traversal, and snowball outward from one paper. Commands used: /ref:weave, /ref:concept, /ref:gaps, /ref:hypothesize, and /ref:related.
The journey
/ref:weave has no dedicated subagent. Claude maps claims to concepts inline, and the scripts only validate and persist what it decides.The example concepts are tdcs, working-memory, attention and adhd, the PMIDs 38000101–38000103 are placeholders, and the claim IDs and relation IDs come from the scratch run. Outputs labelled scratch library are real. In that library, claims for the relation scripts were supplied as small JSON files, which is how /ref:weave passes them in too.
Steps
-
Weave a set of papers
Weaving needs claims first, from
/ref:addat the abstract tier or/ref:extractat the full tier. Then give/ref:weavea selector. For each active claim, Claude maps the intervention and outcome to concepts (step 2). For claims from different papers that land on the same concept pair, it proposes an edge (step 3). It finishes by regenerating the OKF views.illustrative output
› /ref:weave --project tdcs-review # Claude reports each concept created or aliased and each edge proposed or created, # then prints okf_emit.py and graph_people.py output verbatim
Scope on purpose. Weave only compares claims within the papers the selector resolved to, and only when they map to the same concept pair. A library-wide sweep is something you run deliberately, one project at a time. -
Find before you mint
Concept IDs are slugs you choose, and aliases accumulate onto them, so "WM" and "working memory" end up as one concept. Matching is exact, ignoring case and extra whitespace. The scripts never fuzzy-match.
output from a scratch library (timestamps trimmed)
› /ref:concept find "working memory" null › /ref:concept create working-memory "working memory" { "concept_id": "working-memory", "name": "working memory", "aliases": [], "alias_provenance": {}, … } › /ref:concept find "WM" null › /ref:concept add-alias working-memory "WM" --source "claim:38000102:c-0002" { "alias_provenance": { "WM": { "source": "claim:38000102:c-0002", "added_at": "2026-09-15T10:17:06.235929+00:00" } }, "aliases": ["WM"], "concept_id": "working-memory", "name": "working memory", … } › /ref:concept find " wm " { … "concept_id": "working-memory", … } › /ref:concept create wm "WM" error: a concept already matches 'WM': 'working-memory' (name='working memory') -- use find_concept()/add_alias() instead of minting a duplicate
An alias can't be taken from another concept. That would be a merge, and merges are your decision:
output from a scratch library
› /ref:concept add-alias adhd "attention" error: alias 'attention' already resolves to a DIFFERENT concept 'attention' -- this is a merge decision, not an automatic alias addition
-
Let the edges be earned
The automatic check only ever proposes
potential_conflict. It needs opposite directions,increaseagainstdecrease, plus identical comparator, effect measure, timepoint and population. A value ofunknownon either side counts as a mismatch. Here the population and timepoint differ, so there's no edge:output from a scratch library
# claim 38000101:c-0001 (adults with ADHD, post-session, increase) # vs 38000103:c-0001 (children, 4 weeks, decrease) null # vs 38000102:c-0002 (adults with ADHD, post-session, decrease): comparable, opposite { "relation_id": "rel-de5834755e5b", "type": "potential_conflict", "subject_concept_id": "tdcs", "object_concept_id": "working-memory", "supporting_claims": [ { "pmid": "38000101", "claim_id": "c-0001" }, { "pmid": "38000102", "claim_id": "c-0002" } ], "source_version_ids": ["v-aaa", "v-bbb"], "review_state": "unreviewed", "rationale": null, "stale": false, … }
Edges like
supports,extendsandreplicatescome from reading the papers, not from comparing PICO fields. When Claude judges that one exists, it creates the edge manually and tells you why:output from a scratch library (trimmed)
# weave, manual edges: tdcs → attention, attention → adhd (supports); working-memory → adhd (extends) { "relation_id": "rel-7f125c245e2f", "type": "supports", "subject_concept_id": "tdcs", "object_concept_id": "attention", … } { "relation_id": "rel-835bc46c4fed", "type": "supports", "subject_concept_id": "attention", "object_concept_id": "adhd", … } { "relation_id": "rel-754640c3886b", "type": "extends", "subject_concept_id": "working-memory", "object_concept_id": "adhd", … } # and the one type nothing can mint directly: error: create_relation() cannot mint a 'contradicts' edge directly -- create it as 'potential_conflict' (or another type) first, then promote via review_relation() with an explicit rationale (§4a)
These five edge types ( RELATION_TYPESinlib_schema.py) are the ones implemented. Every edge carries its supporting(pmid, claim_id)pairs, and it goesstalewhen one of those claims is superseded or excluded. -
Review a conflict before calling it a contradiction
Two comparable studies pointing in opposite directions is a potential conflict. Maybe one was underpowered, or the samples differ in ways the claim fields don't capture. Promoting the edge takes an explicit review with a reason. That review lives on the relation record, through
/ref:weave review, not in any single paper'scorrections.json.output from a scratch library
› /ref:weave review rel-de5834755e5b contradicts error: promoting to 'contradicts' requires a non-empty rationale -- never auto-promoted from potential_conflict (§4a) › /ref:weave review rel-de5834755e5b contradicts --rationale "Same population, comparator, measure and timepoint; opposite direction in two adequately powered samples" { "relation_id": "rel-de5834755e5b", "type": "contradicts", "review_state": "reviewed", "rationale": "Same population, comparator, measure and timepoint; opposite direction in two adequately powered samples", "reviewed_at": "2026-09-15T10:17:08.193883+00:00", "stale": false, … }
Relation review vs/ref:verify./ref:verifycovers claims, grant links, author contributions and identity matches. Relations are reviewed with/ref:weave review. If a supporting claim changes later, the reviewed edge keeps its rationale but is markedstaleuntil you review it again.To inspect one concept's neighbourhood:
output from a scratch library (trimmed)
› /ref:weave neighbors working-memory { "concept_id": "working-memory", "outgoing": [ { "relation_id": "rel-754640c3886b", "type": "extends", "object_concept_id": "adhd", … } ], "incoming": [ { "relation_id": "rel-de5834755e5b", "type": "contradicts", "subject_concept_id": "tdcs", … } ] }
-
Ask where the evidence is thin
/ref:gapsruns four deterministic queries over a selector.single_study_fragilefinds claims backed by one study.unresolved_conflictsfinds unreviewedpotential_conflictedges and stalecontradictsedges.co_mentioned_ungroupedfinds concepts named together in evidence text but never linked.population_outcome_gapneeds--intervention-concept. Every finding cites the IDs it rests on. This run happened before the review in step 4:output from a scratch library
› /ref:gaps 38000101 38000102 38000103 { "selector_expression": "--pmid 38000101 38000102 38000103", "gaps": { "single_study_fragile": [], "unresolved_conflicts": [ { "gap_type": "unresolved_conflict", "relation_id": "rel-de5834755e5b", "type": "potential_conflict", "stale": false, "subject_concept_id": "tdcs", "object_concept_id": "working-memory", "supporting_claims": [ { "claim_id": "c-0001", "pmid": "38000101" }, { "claim_id": "c-0002", "pmid": "38000102" } ] } ], "co_mentioned_ungrouped": [] } }
The empty lists here only mean the scratch library has no extracted claim registries. They don't mean the evidence is robust.
--conceptas a selector currently fails. The shared selector code still has an early-phase guard./ref:gaps --concept tdcsraisesNotAvailableError: --concept is not available until phase 8 (concept graph), even though the graph exists. Use<pmid…>,--project,--study,--searchor--from-fileinstead. -
Run a Swanson ABC traversal
Starting from concept A, the traversal follows two hops, A–B and then B–C, and keeps any C with no direct A–C edge. Candidates are ranked by how many independent chains reach them.
Partial overlap still counts as independent. Here, 38000101 backs both hops of chain 1, but the two edges' PMID sets aren't identical. Requiring fully separate papers would be too strict once one well-studied paper backs many edges. output from a scratch library (trimmed)
› /ref:hypothesize --concept tdcs # 1. graph traversal, no network [ { "concept_a": "tdcs", "concept_c": "adhd", "chain_count": 2, "chains": [ { "concept_b": "working-memory", "a_b_relation_id": "rel-de5834755e5b", "a_b_type": "contradicts", "b_c_relation_id": "rel-754640c3886b", "b_c_type": "extends", … }, { "concept_b": "attention", "a_b_relation_id": "rel-7f125c245e2f", "a_b_type": "supports", "b_c_relation_id": "rel-835bc46c4fed", "b_c_type": "supports", … } ] } ] # 2. Claude searches PubMed for each candidate and records query, time and PMIDs # (in the scratch run, this check result was supplied by hand) [ { "concept_a": "tdcs", "concept_c": "adhd", "chain_count": 2, … "pubmed_check": { "query": "\"transcranial direct current stimulation\" AND \"attention-deficit/hyperactivity disorder\"", "retrieved_at": "2026-09-15T10:30:00+00:00", "result_pmids": [] }, "pubmed_note": "Not found by this search ('\"transcranial direct current stimulation\" AND \"attention-deficit/hyperactivity disorder\"') -- 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 2 independent A-B/B-C chain(s) in this library's graph between 'tdcs' and 'adhd'. Missing edges describe library coverage, not established literature gaps (§5b). This is a reading-list candidate, not a validated claim." } ]
Zero hits isn't novelty. Hits aren't a dismissal. A search returning nothing reads "not found by this search", nothing more. A search returning PMIDs means the connection may already be studied, which is useful to know, and the candidate stays in the list. The output is a reading list, not a set of findings. -
Snowball from a paper
Backward snowballing reads the References section of the paper's committed full text, with no network needed. Forward snowballing calls PubMed's
find_related_articles. Both only surface candidates. Nothing gets added.output from a scratch library
› /ref:related 38000101 # backward, which needs full text: { "available": false, "reason": "no committed full-text version for this PMID (run /ref:fetch or /ref:attach first)" } # forward, with the link_type the MCP tool used recorded on every row: { "count": 2, "rows": [ { "candidate": "38000102", "resolved": true, "direction": "forward", "method": "find_related_articles:pubmed_pubmed", "query": { "link_type": "pubmed_pubmed", "source_pmid": "38000101" }, "retrieved_at": "2026-09-15T10:17:08.776383+00:00", "already_in_library": true }, { "candidate": "38000555", "resolved": true, "direction": "forward", "method": "find_related_articles:pubmed_pubmed", …, "already_in_library": false } ], "zero_results": false, "note": null, "persisted": { "total_candidates": 2, "already_in_library": 1, "not_yet_added": 1 } }
Forward means similar, not citing.pubmed_pubmedweights words in titles, abstracts and MeSH terms. It isn't a cited-by graph, and no link type available in this environment is one. Every row names its method, so the result can't pass for citation data. A zero-result forward run returnszero_results: truewith the note "not evidence of no related work, just this query's result". Backward candidates are raw citation strings (resolved: false) until you look one up. To bring a candidate in, run/ref:addyourself.
What you now have
<LIBRARY>/
graph/
concepts.jsonl # tdcs, working-memory, attention, adhd, with alias provenance
relations.jsonl # 1 reviewed contradicts (with rationale), 2 supports, 1 extends
people_relations.jsonl # regenerated by /ref:weave
okf/ # generated OKF v0.2 bundle; never hand-edit
papers/38000101/
related.json # forward/backward candidates, upserted by (direction, method, candidate)
# /ref:gaps and /ref:hypothesize write nothing: their output is the report in your session
Command reference: /ref:weave, /ref:concept, /ref:gaps, /ref:hypothesize, /ref:related.