# swarph memory graph traversal — Design Spec
Board card #42 · Sub-project B of the OKF traversal brain ([parent spec](../../../../swarph-shim/docs/superpowers/specs/2026-07-15-okf-traversal-brain-design.md)) Date: 2026-07-16 · Target: swarph-cli (public PyPI, stdlib-only)
Thesis
swarph memory links gains backlinks and multi-hop, directional graph
traversal over the OKF knowledge corpus. The graph is derived file-native
from page bodies (the [[links]] an author actually wrote), using gbrain only
as the page store (get_page/list_pages over MCP) and the shared
okf_links.parse_okf_links parser as the edge extractor. No gbrain change, no
brain-data mutation, no deploy gate.
Why file-native (the load-bearing finding)
The gbrain graph tools (traverse_graph, get_backlinks, get_links) are
already live MCP tools on the prod brain — but lab's brain has link_count: 0.
The [[links]] in all 198 pages were never reconciled into gbrain's edge table
(gbrain's markdown/wikilink-resolved reconciliation never ran on our corpus).
So the links exist only in the page bodies. Deriving the graph from bodies
therefore both (a) works today against the real corpus, and (b) matches the
parent spec's "file-native for shippability" thesis — it ships to any gbrain
regardless of whether its edge index is populated.
Populating gbrain's edge index is real, mesh-wide value (it would light up
traverse_graph/get_backlinks for brain-ask and the future walker too), but
it is a separate, gated, brain-health task — see Decoupled follow-up below.
traverse_graph remains a documented optional fast-path for a future revision
once edges exist; it is out of scope here.
Scope
In scope (this spec):
1. Migrate swarph memory's link parsing to the shared okf_links.parse_okf_links
(retire the weaker local parse_links; closes the duplication the timeline
sub-project explicitly deferred).
2. File-native traversal engine in memory.py: forward BFS, reverse (backlinks)
index, directional + depth-bounded, cycle-safe, depth-capped.
3. New flags on swarph memory links : --backlinks, --depth N,
--direction in|out|both. Default (no new flags) = today's behaviour exactly.
4. --json emits the parent spec's OKF edge records.
5. Extend the swarph_memory_navigate MCP tool with backlinks/traverse ops
(whitelisted, fail-safe → []).
6. README §memory update + version bump 0.31.0 → 0.32.0.
Out of scope: any gbrain change; wiring to traverse_graph/get_backlinks;
the gbrain edge backfill; the Phase-2 swarph brain unified walker (sub-project C).
Architecture
`
swarph memory links `
Nothing new touches the network transport — memory._mcp_call / brain_ask
endpoint+token resolution are reused verbatim. The only new I/O pattern is the
corpus scan needed for reverse (in/both) traversal.
Components & interfaces
All additions live in src/swarph_cli/commands/memory.py (the file this feature
belongs to; it stays focused — one verb, one responsibility). Signatures:
parse_links(content)— retire the local wiki-only regex; re-export
okf_links.parse_okf_links under the name for any importer, and call the
shared parser everywhere. (One grammar, pinned, tested — no second copy.)
_all_page_slugs(url, token) -> list[str]— every page slug vialist_pages
_forward_targets(url, token, slug) -> list[str]—parse_okf_linksof one
get_page(slug).content). The out-edge primitive.
_reverse_index(url, token) -> dict[str, list[str]]— full-corpus scan:
traverse(url, token, slug, depth, direction) -> list[tuple[str, str, int]]—
(from, to, hop) edges, cycle-safe (visited set),
depth clamped to [1, DEPTH_CAP] (DEPTH_CAP = 10, mirroring gbrain's own
TRAVERSE_DEPTH_CAP). direction ∈ {"out","in","both"}.
backlinks(url, token, slug) -> list[str]— sugar: single-hopintargets
slug), order-preserving.
links(url, token, slug) -> list[str]— unchanged public behaviour
_as_okf_edge(frm, to, direction, hop) -> dict— the OKF edge record (below).
CLI flag semantics (swarph memory links)
--depth N(int, default1) — hops. Clamped to[1, 10].--direction {out,in,both}(defaultout).--backlinks(flag) — sugar for--direction in. Rejected (exit 2, clear
--direction, to avoid an ambiguous
request.
- No new flags →
depth=1, direction=out→ byte-for-byte the current output. - Human output: discovered target slugs, order-preserving de-dupe, one per line
both, out-edges then in-edges.
--json→ a JSON array of OKF edge records.
OKF edge record (--json)
Mirrors the parent spec's node/edge schema and the timeline sub-project's
_as_okf style (hemisphere-tagged, direction-carrying — the exact keys the
Phase-2 walker will read):
`json
{"type": "edge", "hemisphere": "knowledge", "from": "`
direction is "out" or "in" per edge (a both traversal yields a mix).
This changes the links --json shape from the current flat slug-array to edge
records — an intentional, documented enrichment for a young (0.30-era) command
with no external consumers; noted in the README changelog line.
Reverse-scan cost (no silent caps)
--backlinks / --direction in|both perform one full-corpus scan —
list_pages + get_page per page (≈198 calls today). This is O(N) per
invocation and correct; it is not cached across invocations. The README §memory
note states this plainly and points at the (separate) gbrain edge-index backfill
as the eventual O(1) fast-path. out-only traversal never scans the corpus — it
fetches only pages reachable from slug within depth.
Error handling (fail-safe contract, unchanged)
- CLI: every new path stays inside the existing
try/except→ stderr note +
get/list/links).
- Corpus scan: a single page that 404s or fails to parse is skipped; the scan
- MCP:
_memory_navigateswallows everything to[](host never sees a raise).
backlinks/traverse ops are explicitly whitelisted; an
unrecognised op returns [] (same guard the timeline navigate op added).
Testing (TDD)
Unit, stdlib unittest + the repo's pytest runner (venv/bin/python -m pytest),
gbrain stubbed via a fake _mcp_call (no network). Cases:
- shared-parser migration:
[[slug|alias]]→slug,[[slug#h]]→slug,![[embed]],all resolve (proves the upgrade over the old wiki-only regex). traverseout depth-1 ==links(back-compat).- out depth-2 BFS follows the frontier; depth clamp at 10; cycle (A↔B) terminates.
- reverse index: backlinks of a target = every page whose body links it; a page
both= union, out-then-in ordering.--backlinks+--direction→ exit 2.--jsonemits well-formed OKF edge records with correctdirection/hop.- MCP
backlinks/traverseops return records; unknown op →[]; backend
[].
- version pin flips to
0.32.0.
Global constraints
- stdlib-only; public PyPI package; no new dependencies.
- Additive verb-flag change; no hook touch; inert-safe.
- Default
linksoutput unchanged (back-compat). - Publish to PyPI is commander-gated; the plan ends at green + PR. A new
- Stage only named files; never
git add -A; never stage.codegraph/or stray docs.
Decoupled follow-up (separate card — NOT this spec)
gbrain edge backfill. lab's brain is 198 pages / 0 edges. Run gbrain's
designed link reconciliation (managed sources markdown/wikilink-resolved) to
populate the edge table from page bodies, so traverse_graph/get_backlinks
light up for all consumers (brain-ask, the walker). This mutates prod brain
data (write scope) → commander-gated; exact trigger (re-ingest vs sync vs
run_doctor) to be determined. Tracked separately so this shippable CLI feature
never blocks on a gated brain migration.