Skip to content

Chapter04

Chapter 4: Six Tools

In 1974, computer scientist Christopher Alexander published A Pattern Language, a catalogue of 253 design patterns for buildings and towns. The book's argument was not that architects should memorize 253 patterns. It was that good design recurs -- that the same solutions to the same problems appear across different scales and contexts, and that naming them makes them easier to recognize, teach, and apply. The patterns ranged from urban planning ("City Country Fingers") to room layout ("The Flow Through Rooms") to the placement of a window seat. Each was a named, composable solution to a recurring problem.

What Alexander discovered, and what software engineers rediscovered twenty years later when they adapted his framework for code, is that the value of a pattern library is not in its size. It is in the coverage-to-complexity ratio. A small set of well-chosen patterns that together cover the full space of common problems is more useful than a large set that covers the same space redundantly or inconsistently. The goal is completeness with economy.

BFS-QL has six tools. The choice is not arbitrary and not conservative -- it is the result of asking, for every candidate tool, whether it covers something the others do not, and whether the space it covers is one an LLM actually needs.

Why Six

The full space of what an LLM needs to do with a knowledge graph can be decomposed into six operations, each distinct, together exhaustive:

Orientation. The LLM arrives at a graph it has never seen. It does not know what kinds of entities the graph contains, what relationships are represented, or how they are named. Before it can navigate, it needs a map. This is describe_schema.

Resolution. The LLM has a name -- a drug, a disease, an author. It needs the canonical ID that the graph uses for that entity. Names are ambiguous; canonical IDs are not. The operation of mapping a name to an ID is fundamental and cannot be collapsed into traversal without introducing the hallucination problem Chapter 2 described. This is search_entities.

Traversal. The LLM has a seed -- one or more canonical IDs. It wants to know what they connect to. This is the core operation, the one that makes graph knowledge accessible. Everything else is setup or follow-up. This is bfs_query.

Expansion. The traversal returns stubs -- lightweight placeholders for nodes that were present in the topology but did not warrant full metadata. The LLM sees that something is there and wants to know what it is. For a single stub, this is describe_entity.

Batch expansion. A single bfs_query call typically surfaces several stubs worth inspecting. Calling describe_entity on each in sequence means one round-trip per entity: the LLM issues a call, waits, reads the result, decides to expand the next stub, and repeats. Each round-trip carries the full overhead of a tool invocation in an LLM session -- not a database round-trip, but a model reasoning step. In practice, Claude Code flagged this explicitly as friction: sequential single-entity expansion is slow and accumulates latency when several stubs warrant attention. describe_entities accepts a list of IDs and returns full records for all of them in a single call. It is not a convenience alias for a loop; it is the operation that makes batch expansion a first-class primitive rather than an emergent pattern the model has to construct.

Intersection. The LLM has a set of seeds and wants to know what is common to all of them -- not the union of their neighborhoods, but the nodes reachable from every seed simultaneously. bfs_query returns the union; the LLM cannot reliably do the set intersection itself over hundreds of nodes. This is intersect_subgraphs.

Orient, resolve, traverse, expand, batch-expand, intersect. The protocol has grown by one each time a real gap appeared -- intersect_subgraphs when multi-seed reasoning proved unreliable without it, describe_entities when sequential single-entity expansion proved too costly. Candidate additions like "find shortest path" or "list all entities of type X" reduce to compositions of existing tools without material cost, or add query-oriented answers rather than navigational handles. The bar is real demonstrated need, not speculation.

The Session Workflow

The six tools define a natural sequence that a well-behaved LLM follows against any BFS-QL graph:

1. describe_schema()
   → learn entity types, predicates, graph description

2. search_entities(name, node_types=[...])
   → resolve a name to one or more canonical IDs
   → pass node_types to avoid noise in results
   → inspect entity_type to disambiguate if needed

3. bfs_query(seeds, max_hops, ...)
   → traverse from the resolved ID
   → start with topology_only=True for large graphs, OR
   → use exclude_node_types=["paper","author"], min_mentions=2
     for a concept-only result on literature graphs
   → use node_types and predicates to focus metadata detail

4. describe_entities([id, id, ...])
   → expand any stubs that warrant closer inspection
   → batch multiple IDs in a single call

Steps 1 and 2 may be partially redundant if the BFS-QL server injects schema into tool descriptions at startup -- in that case the LLM may skip the explicit describe_schema call. Steps 3 and 4 are iterative: the output of one bfs_query call identifies stubs that motivate describe_entity calls, which may motivate further bfs_query calls seeded at newly discovered nodes. The workflow is a loop, not a pipeline.

This matters for how the tools were designed. Each tool must be callable in any order, with the outputs of earlier calls serving as inputs to later ones. bfs_query takes canonical IDs -- which search_entities produces. describe_entity takes canonical IDs -- which appear in bfs_query results. The interface is compositional by construction.

What Is Not a Tool

The choice of six tools is also a choice of what not to include. Some candidates worth examining:

A "shortest path" tool. Useful for certain graph analyses. Not needed for LLM reasoning, which doesn't navigate to specific destinations -- it explores neighborhoods. An LLM that needs to know whether two entities are connected can issue a multi-hop bfs_query and inspect the result. The two-step answer is not materially worse than a dedicated tool, and adding the tool adds one more surface for the LLM to reason about.

A "list all entities of type X" tool. The medlit demo has 119 disease entities. A tool that returns all of them is not useful to an LLM trying to reason; it is a context flood. The right operation is bfs_query from a relevant seed with node_types=["disease"], which returns the disease entities that are connected to something the LLM already cares about. Relevance is structural, not taxonomic.

A "count" tool. Useful for human analysts building dashboards. Not useful for LLM reasoning. An LLM that receives "there are 119 disease entities" has not learned anything it can act on. The count tells it nothing about which diseases matter, how they connect, or what the graph structure implies about the domain.

The pattern in all three cases is the same: the candidate tool answers a query-oriented question rather than a traversal-oriented one. It gives the LLM a fact rather than a navigational handle. BFS-QL is designed for navigation. The six tools reflect that.