Skip to content

Repository files navigation

Gigi

An executable registry of graph algorithm semantics.

North star

Can the accumulated knowledge of how computational methods work, fail, relate, and perform become a system that helps humans learn them and machines improve them?

Gigi begins with executable method semantics. Its longer-term direction is a shared system for learning, correct application,and bounded computational discovery. The same reviewed definitions, datasets, tests, and measurements should support each activity.

The same named graph algorithm can return different answers on different backends, because their defaults and semantics differ. Gigi writes those differences down, and then runs them, so the write-up cannot quietly become untrue.

Here is one, measured by this repository's own test suite:

PageRank, weighted-small, no parameters given

  backend       a          b          c
  reference    0.233918   0.333333   0.432749
  networkx     0.118150   0.400793   0.481057    <- 49% off, silently
  igraph       0.233918   0.333333   0.432749
  rustworkx    0.233919   0.333333   0.432748

NetworkX defaults to weight="weight", so it ran weighted PageRank. igraph and rustworkx ignore edge weights unless you ask. Nobody warns you, and the ranking you get depends on which library you imported.

Quick start

Once the first release is published:

pip install "gigi-algo[all]"
gigi ask "what is PageRank useful for?"

To work on Gigi itself, use a checkout instead:

git clone https://github.com/graphgeeks-lab/gigi-algo
cd gigi-algo
pip install -e ".[dev]"
gigi list                                          # what is in the registry
gigi show pagerank                                 # what the registry claims
gigi maths pagerank                                # definition, invariants, choice points
gigi origin pagerank                               # who created it, who built the entry
gigi families                                      # the taxonomy, as questions
gigi people                                        # who has contributed, and to what
gigi export -o registry.json                       # the whole registry, for machines
gigi review pagerank                               # what to check before merging
gigi promote degree_centrality --dry-run           # has it earned the next tier?
gigi version                                       # build, backends, and which registry
gigi typst pagerank --pdf                          # a printable, citable PDF of the entry
gigi typst pagerank --pdf --review                 # same, with the open questions in the margin
gigi backends                                       # what is installed here
gigi inspect datasets/weighted-small               # cheap structural facts
gigi compare pagerank -g weighted-small --defaults # reproduce the table above
gigi verify pagerank                               # check every claim
gigi site build                                    # render it all as HTML

The Python API is the same thing without the terminal:

import gigi

graph  = gigi.load_graph("datasets/weighted-small")
result = gigi.run("pagerank", backend="networkx", graph=graph)

result.requested_parameters   # {'damping': 0.85, 'weight_property': None, ...}
result.effective_parameters   # {'alpha': 0.85, 'weight': 'weight', 'tol': 1e-06, ...}

report = gigi.verify("pagerank")
report.status                 # 'pass'

The CLI, the Python API, and any future agent tooling call the same three functions: run, compare, verify. There is no second code path.

How it works

docs/ARCHITECTURE.md has the diagrams: the three layers, the verify loop, what a result is, and how gigi ask decides. Start there if you want the shape before the detail.

methods/pagerank/method.yaml     the claims
        │
        ├── implementations/           one small file per backend
        │
datasets/*/                            small adversarial fixtures
        │
        ▼
gigi verify pagerank                   runs the claims
        │
        ├── backends agree where the registry says they agree, or CI fails
        └── every declared divergence still reproduces, or CI fails

Two independent questions, deliberately never mixed:

  1. Agreement. With every ambiguous parameter pinned, do the backends match the reference implementation? A difference nothing in the registry accounts for is a build failure.
  2. Reproduction. Does each declared divergence still happen? A divergence that stopped happening is stale documentation, and is also a build failure.

That second check is what turns a backend upgrade into a signal instead of a surprise.

What is in v0.1

Methods pagerank, degree_centrality, connected_components, cosine_similarity
Backends reference, networkx, igraph, rustworkx, scipy, sklearn
Fixtures 11 small adversarial graphs and 3 vector sets, including the degenerate cases of each
Divergences 7, all reproduced by CI
Output kinds node_score, similarity_score, partition
Invariants 12, checked on every backend and fixture
Known answers 35 hand-derived cases the reference must reproduce
Problems 7 questions, including one nothing here answers
Families 17 across 2 domains, covering the roadmap
Attribution three layers: origin, Gigi credits, divergence discovery

Coming next: bfs, which brings path comparison.

Agreement is a finding too

connected_components runs on four backends across eleven fixtures and they all agree, including on the empty graph, isolated node, self loop, and duplicate edge. The same fixtures split three backends three ways on degree_centrality.

That is worth recording rather than shrugging at. A registry that only ever captured disagreement would be a bug tracker with extra steps, and "these four libraries agree here" is information nobody had before it was measured. The reason turns out to be in the maths: components are the equivalence classes of reachability, and reflexivity settles the isolated node and the self loop before an implementation gets a chance to be creative.

What the backends do disagree about is whether to answer at all. NetworkX raises on a directed graph rather than guessing weak or strong; rustworkx raises on strong components of an undirected graph, which igraph answers correctly. This is about strictness, not components, so it is recorded as a choice point and mode makes the question explicit.

Not only graphs

cosine_similarity is here for a reason that is not "more content". Everything Gigi does well, including layered provenance, executed invariants, named choice points, reproduced divergences, and a priced maturity ladder, is about a method, not about a graph. Until PR 2b, that was a claim rather than a fact.

It is now one method's worth of fact. A fixture declares its kind (graph or vectors); a backend declares what it accepts; a result is keyed by node or by pair. The harness is still three functions and gained no branch on kind.

It found something on its first fixture. A zero vector has no direction, so the cosine of any pair involving one is undefined. SciPy answers NaN, scikit-learn answers 0.0, and the reference declines to answer. The scikit-learn convention is the quieter and more dangerous one: a failed embedding is reported as known to be dissimilar to everything, when the truth is that nothing is known about it.

The headline still says graph algorithm semantics, and will until the non-graph content is more than one entry. See ADR 0011.

Contributing

Adding an algorithm means adding one directory. You do not touch gigi/, and you do not write any tests. The conformance suite is generated from the registry, so a new directory is covered automatically.

methods/<your_method>/
├── method.yaml            what you claim, where it came from, who built it
├── maths.md                  the definition
├── notes.md                  what you measured
└── implementations/
    ├── reference.py          readable oracle, no libraries
    └── networkx.py           ~15 lines: call the backend, record its defaults

methods/_template/ is a working example, it implements degree centrality end to end, so you can run it before you change anything, and CI runs it too so it cannot rot. See CONTRIBUTING.md.

You do not need to invent a graph algorithm to contribute. Reporting a divergence, correcting an attribution, or adding an adversarial fixture needs no Python at all, and all three improve a registry whose entire value is being correct.

Attribution

Four questions, kept separate, because collapsing them into inventor: loses most of the truth:

who created the algorithm  !=  who implemented it in Gigi
                           !=  who verified it
                           !=  who found the divergence

provenance: records original authors, the original work, and structured precursors. Use attribution_notes for the parts that resist structure. PageRank is the reason: the 1998 paper names four authors, while the recursive link-ranking idea runs through Pinski & Narin (1976), Bonacich (1972), and Katz (1953).

gigi: records who did the work here, by role, as ids into people/people.yaml. Every id must resolve or the tests fail. Profiles show lineage rather than a score. There is no leaderboard, on purpose.

Reviewing

Review should be short and confident. gigi review <algorithm> splits the work into what a machine already settled and what only a person can:

Settled by machine -- you do not need to check these
  spec validates                       pass
  family resolves                      pass  centrality -> Which nodes matter...
  every credited person resolves       pass
  invariants hold on every run         pass  108 assertions
  declared divergences still reproduce pass  2 declared
  ...

Gaps -- not failures, usually the next contribution choice point 'convergence_criterion' has no fixture

By eye -- nothing checks these but you
  1. Does the reference implementation compute what the definition says?
  2. Could someone learn the algorithm from the reference implementation?
  3. Do maths.md and the `maths:` block say the same thing?
  ...

That first by-eye item is the one that matters: the reference implementation is the oracle every backend is compared against, so if it is wrong, every green check above it is meaningless. tests/expected.yaml is the partial defence -- known answers derived by hand, from the definition, which the reference must reproduce -- and the command prints the definition so you can check the rest.

Maturity is priced. gigi/requirements.py says what frontier, emerging and stable each owe, gigi review shows exactly what promotion would take, and the test suite refuses an entry claiming a tier it has not earned.

Readability is itself a checked property. tests/test_readability.py enforces no module over 400 code lines, docstrings on modules and non-obvious public names, functions under 120 lines, CLI help text, and a budget on library growth. That keeps "the code is reviewable" from quietly stopping being true.

Installing a release

pip install gigi-algo            # library, CLI, reference backend
pip install "gigi-algo[all]"     # plus NetworkX, igraph and rustworkx

Releases are tags. uv version --bump minor, a changelog section, git tag, and the workflow builds, tests on two backend matrices, publishes to PyPI by trusted publishing, and writes the GitHub Release from the changelog -- only after PyPI confirms the version is installable. See docs/RELEASING.md and CHANGELOG.md.

Does it read your data the way you mean it?

The same column can be the right input to two methods and mean opposite things. PageRank reads an edge weight as strength: higher means stronger. Dijkstra reads it as cost: higher is worse. Run both on a column called distance and you have asked two contradictory questions and been told nothing.

$ gigi why pagerank --graph road-distances-small

Answers
  Which nodes are important because other important nodes point at them?

Does not answer
  Which nodes have the most connections?   -> degree_centrality
  What is the cheapest way to get from here to there?   -> nothing here yet

How it reads edge weight
  as strength: higher means stronger

Your data  (road-distances-small)
  ! Column `distance` looks like distance, and this method reads it as
    strength, where higher means stronger. Did you intend to invert it?

Without --graph that is documentation. With it, it is advice: it reads the columns actually in front of you. gigi alternatives and gigi related come from the same structure.

Ask it something

$ gigi ask "how do I find communities in my graph"

Nothing here answers this.
  the question is known: Which nodes belong together, in the sense of being
  more densely connected to each other than to the rest?
  problems/community_grouping.yaml -- no method claims it

Explicitly not for this
  connected_components declares community_grouping out of scope

Connected components is what people reach for when they mean communities. Gigi declines, and names the thing it declined to be.

A model may find, but not speak

Word matching cannot read paraphrase. "Which nodes matter most" shares no word with "important", so it used to return degree centrality and silently drop PageRank. That was a worse answer than the registry contains, while still looking complete.

So a model gets one job: choosing which entries a question is about. It picks ids from a catalogue, checks every id against the registry, and drops anything invented. It cannot add a method to Gigi by mentioning it, and it writes no word you read. Every output sentence is registry content that CI verifies. It can still choose an unhelpful real entry, which is why Gigi shows the match path and keeps recommendations reviewable.

# PowerShell
$env:ANTHROPIC_API_KEY = "..."       # or OPENAI_API_KEY, or run Ollama
gigi ask "who are the influencers in my network"
gigi providers                        # see what is configured
gigi ask "..." --model none           # force word matching
# macOS and Linux
export ANTHROPIC_API_KEY=...           # or OPENAI_API_KEY, or run ollama
gigi ask "who are the influencers in my network"
gigi providers                         # see what is configured
gigi ask "..." --model none            # force word matching

GIGI_MODEL sets the default provider (anthropic, openai, ollama, or none). No key, no network, a timeout, or a model response Gigi cannot use falls back to word matching. gigi ask works offline. Every answer says how it was matched, so a model's involvement is never invisible.

Start from your question

If you are… Try this What you get
a student learning the vocabulary gigi ask "what is PageRank useful for?" A verified method, its maturity, and the next command to understand it.
a researcher comparing implementations gigi compare pagerank -d weighted-small --defaults The same method across backends, including the recorded default that changes the answer.
a developer with a transaction network gigi ask "who are the influencers in my network" Model-assisted matching when configured, then registry-backed candidates to inspect with gigi why.
curious about communities gigi ask "how do I find communities in my graph" An honest gap: Gigi names community grouping and explains why connected components is not a substitute.

The third example is deliberately phrased as a normal question rather than a registry keyword. With no configured model it may return no match; use gigi providers to see whether a provider is available, or start with gigi ask "what is PageRank useful for?" while offline.

See ADR 0014.

For agents

Gigi supports both directions. gigi ask can use a configured model to select registry entries; an external model can call Gigi through MCP to inspect, run, compare, and verify those entries.

$ gigi mcp        # eight tools over MCP on stdio
$ gigi tools -f anthropic   # or openai, or mcp -- the same tools as JSON schemas

Add to Claude Code or Claude Desktop:

{"mcpServers": {"gigi": {"command": "gigi", "args": ["mcp"]}}}

Agents get gigi_ask, gigi_describe_method, gigi_why, gigi_list_methods, gigi_list_datasets, and, most importantly, gigi_run, gigi_compare, and gigi_verify. A model can check a claim before making it. frontier methods still refuse to run without opt-in; the harness gate applies to every caller.

Run it in a container

The published image includes the registry and every supported backend:

docker run --rm ghcr.io/graphgeeks-lab/gigi-algo:latest ask "which nodes matter most"
docker run --rm ghcr.io/graphgeeks-lab/gigi-algo:latest verify

# The default command starts Gigi's MCP server.
docker run -i --rm ghcr.io/graphgeeks-lab/gigi-algo:latest

# Pass a provider key only when you want model-assisted matching.
docker run --rm -e ANTHROPIC_API_KEY \
  ghcr.io/graphgeeks-lab/gigi-algo:latest ask "who are the influencers in my network"

The entrypoint is gigi, so subcommands work as arguments. The default command is the MCP server, which lets an agent runtime use Gigi without Python, uv, or graph libraries installed on the host:

{"mcpServers": {"gigi": {"command": "docker", "args": ["run", "-i", "--rm",
"ghcr.io/graphgeeks-lab/gigi-algo:latest", "mcp"]}}}

To build the image from this checkout instead:

docker build -t gigi .
docker run --rm gigi verify

The image ships every backend, so gigi verify inside it means what it means outside it. That is most of its size, and an image that cannot run igraph and rustworkx cannot verify the registry.

Your own registry instead of the bundled one:

docker run --rm \
  -v "$PWD/methods:/registry/methods:ro" \
  -e GIGI_METHODS_DIR=/registry/methods \
  gigi verify

.github/workflows/docker.yml builds and smoke-tests on every push. A v* tag publishes version, major-minor, and latest tags to ghcr.io/graphgeeks-lab/gigi-algo.

Maturity

Every algorithm declares a tier, and the tier has teeth. frontier entries refuse to run without an explicit --allow-frontier, so an agent asking for the best available algorithm can never be handed unverified work silently. emerging and stable each have a stated price in gigi/requirements.py; gigi review shows where an entry stands and gigi promote refuses a tier it has not earned.

docs/MATURITY.md covers all four tiers, how to move up, and how to use frontier.

Vocabulary

Divergence, invariant, choice point, known answer, reference, maturity -- the words this project leans on are defined once, in plain language, in docs/GLOSSARY.md.

Built for readers who are not people

An agent choosing an algorithm cannot read maths.md, and should not be asked to infer facts from prose. So every fact lives in structured form, and prose supplements it rather than carrying it alone:

  • maths:: the definition in plain text and LaTeX, the invariants, and the places the definition leaves a choice open.
  • invariants are executed. "Scores sum to one" is two lines of YAML, and it is then asserted on every backend, on every fixture, forever. A property whose id names no check in gigi/invariants.py fails the build.
  • under_determined names the choice points, where backends could differ, as opposed to divergences, which records where they did. That is what lets a new backend be assessed before it is ever run.
  • relationships are typed and conditioned. "See also" tells a machine nothing; "generalises eigenvector centrality, and coincides with it as damping approaches 1 on a strongly connected graph" tells it when a substitution is legitimate.
  • family resolves to families/families.yaml, where a family is a question ("Which nodes matter, and in what sense of matter?") rather than a label.

gigi export gives all of it as one JSON document, serialised from the same models the library uses, so what a machine reads is exactly what gigi verify checks. The one place a language model belongs is turning a person's sentence into a typed intent; everything after that reads structure.

See ADR 0008.

What Gigi is not

Not a graph backend. Not a graph database. Not a query language. Gigi does not implement fast kernels or reimplement NetworkX, igraph, or rustworkx inside an adapter. It calls them, records exactly what they did, and tells you where they disagree.

Questions Gigi should help answer

  • What problem am I actually trying to solve, and what question does this method answer?
  • What mathematical ideas and data structures should I understand before I use it?
  • Does my data have the meaning, representation, and constraints that this method assumes?
  • Which methods, implementations, and computational engines are sensible alternatives, and what evidence supports that choice?
  • How do methods, structures, datasets, and real applications relate to one another?
  • Can a candidate implementation improve speed, memory use, or usability without changing the method's meaning?

Gigi and frontier AI

Frontier AI will increasingly explain algorithms, search research, write implementations, and propose optimizations. Gigi does not try to compete with a general-purpose model at those tasks. Its role is the computational evidence layer beneath the model.

Frontier AI Gigi
Generates a plausible answer Supplies versioned, reviewable claims
Proposes code Verifies semantics, invariants, and known answers
Summarizes research Links provenance, datasets, measurements, and failures
Selects a method conversationally Shows backend defaults, divergences, exclusions, and limits
Can improve quickly Preserves public, reproducible evidence over time

A model can say that a PageRank implementation should be faster. Gigi can determine whether it still means PageRank, which workloads it helps, what it costs in memory, where it fails, and who reviewed the evidence. The model can become an excellent Gigi contributor and user, but it should not be the authority that declares its own proposal correct.

Licence

Apache-2.0. Fixtures are CC0.

About

Everything you need to know about graph theory and it's applications

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages