HARP: Teaching Page Rank to Pick the Right AI Agent
How a 26-year-old web search algorithm can solve one of the messiest problems in modern AI tooling picking the right agent for the right…
HARP: Teaching Page Rank to Pick the Right AI Agent
How a 26-year-old web search algorithm can solve one of the messiest problems in modern AI tooling picking the right agent for the right job.
If you have spent any time around modern AI tooling, you have probably noticed something strange. We have hundreds of AI agents now. Coding agents, research agents, browser-controlling agents, PDF-reading agents, deep-research agents. OpenRouter alone routes traffic to more than three hundred models. The Model Context Protocol (MCP) ecosystem has exploded into a small forest of servers, each exposing its own tools. Backstage and similar service catalogs are starting to list agents the way they used to list microservices.
And yet, when a user asks a question, we mostly still pick the agent the same way we did two years ago: route everything to the most expensive model and hope for the best.
That works. It is also wildly inefficient. The model that scored 94% on SWE-bench will absolutely solve the “what is the capital of France” question. It will also cost a hundred times more than a small model that solves it just as well, and take ten times longer to do it.
The interesting question is not whether AI can attempt a task. It is which agent should attempt it.
This is a ranking problem. And ranking problems have a famous answer.
The PageRank moment
In 1998, Larry Page and Sergey Brin published a paper called The Anatomy of a Large-Scale Hypertextual Web Search Engine. The whole pitch was disarmingly simple. The web is a graph. Pages link to each other. A page that gets linked to by many important pages is probably itself important. Treat the act of clicking links as a random walk over the graph, and the long-run probability of landing on any given page is its rank.
That single idea built a trillion-dollar company.
Twenty-six years later, we have something that looks a lot like the early web, but for agents. We have a population of agents. Some agents call other agents (multi-agent frameworks like AutoGen, MetaGPT, CAMEL, and GPTSwarm explicitly do this). Some agents are good at certain things and bad at others. New agents arrive constantly. Old agents fall out of favor. There is no central authority deciding which agent is best for which task.
This is the PageRank moment for the agent ecosystem.
The naive instinct is to plug raw PageRank into the agent graph and call it a day. That does not work, for a reason worth dwelling on: PageRank only knows about one signal, which is the link structure. In our world we have three signals, and ignoring any of them gives bad rankings. So we need to do something a bit more interesting.
The algorithm I am about to describe is called HARP — Hybrid Agent Ranking via Personalized PageRank. It fuses three signals into a single PageRank-style score, comes with a clean mathematical guarantee that it converges, and runs in under fifty milliseconds per query. I built it for a research project on multi-agent routing. The code is roughly two hundred lines of Python. By the end of this post you will understand how it works and you will be able to drop it into your own stack.
The three signals that matter
Before any math, here is the intuition.
Signal one: performance history. If an agent has tried similar tasks before and succeeded, it should rank higher. This sounds obvious until you notice that almost nobody actually does it. Most routers pick agents based on either capability claims or static benchmark scores, both of which lie. An agent that says it can read PDFs and an agent that has read ten thousand PDFs with a 94% success rate are very different things, and our ranker should know this.
Signal two: who endorses whom. When agent A calls agent B as part of a workflow and the workflow succeeds, that is implicit endorsement. It is the agent-world equivalent of a hyperlink. In multi-agent frameworks this graph is real and growing fast. The MCP ecosystem in particular produces a lot of these edges, since one MCP server often delegates to another. This is the signal that classical PageRank already uses, just applied to agents instead of web pages.
Signal three: semantic similarity. When a brand new agent arrives, it has no history and no endorsements. But it has a description. A new “spreadsheet specialist” agent that just got published should be findable for spreadsheet tasks even on day one, before anyone has used it. Sentence embedding give us this for free.
The reason hybrid is interesting is that each signal alone fails in a different way. Performance-only is great until a new agent arrives. Semantic-only is great until your descriptions are vague (and they always are). Endorsement-only is great until the graph has a clique of mediocre agents that keep calling each other.
A good ranker uses all three. The question is how to combine them without breaking the math.
The Math, in Plain Language
Let me set up the problem cleanly.
We have a set of agents (the things that can do work), a set of skills (categories like pdf_reading, python_coding, web_browsing), and an incoming task with a text description.
We treat the agents, the skills, and the current task as nodes in a graph. The graph has three types of edges, one for each signal we care about. The algorithm produces a score for every agent, and the agent with the highest score wins the task.
Signal 1: performance
For every (agent, skill) pair, we keep a single Bayesian-smoothed weight:
1 + successes
performance_score = ─────────────────────────────
2 + successes + failures
This is the posterior mean of a Beta distribution with a Beta(1, 1) prior. With zero observations it returns 0.5 — a principled "I do not know yet" default. With many observations it converges to the empirical success rate.
Why the prior matters: imagine an agent that tried one task and succeeded. Raw success rate is 1.0, a perfect score. Now imagine an agent that tried a hundred tasks and succeeded ninety-nine times. Raw success rate is 0.99. Without smoothing, your system prefers the lucky one-shot agent over the consistent veteran. The Beta prior fixes this with one line of math, and it gives you cold-start handling for new agents as a bonus.
Signal 2: endorsement
For every ordered pair of distinct agents, we keep:
endorsement_score = max( co_successes − co_failures, 0 )
If agents A and B both succeed on the same task, that is implicit endorsement (co_successes goes up). If they both fail, that is implicit anti-endorsement (co_failures goes up). We clip at zero so we never have negative weights.
This is conceptually identical to the local-trust matrix in EigenTrust (Kamvar et al., 2003), originally designed for peer-to-peer file-sharing networks. The principle generalizes cleanly to AI agents.
Signal 3: semantic similarity
For every (skill, task) pair we compute the cosine similarity between their sentence-transformer embeddings, and shift it into the unit interval:
1 + cosine_similarity(skill_text, task_text)
semantic_score = ─────────────────────────────────────────────────
2
The same formula applies between agents and tasks (using each agent’s description). The shift converts cosine values in [-1, 1] to non-negative weights in [0, 1], which is what we need for a probability matrix.
Combining the three: the transition matrix
We turn each weight matrix into a column-stochastic transition matrix. A column-stochastic matrix is one where every column sums to one interpretation: “from this node, where can the random walker go next, and with what probability?”
Then we mix the three matrices together with a convex combination:
M = weight_perf × M_perf
+ weight_endo × M_endo
+ weight_sem × M_sem
subject to: weight_perf + weight_endo + weight_sem = 1
all weights ≥ 0
Recommended starting values: weight_perf = 0.5, weight_endo = 0.3, weight_sem = 0.2.
This is the magic step. A convex combination of column-stochastic matrices is itself column-stochastic. That single property is what guarantees the algorithm converges, which we will see in a moment.
The teleport vector
PageRank’s “random restart” probability needs a place to restart to. Classical PageRank uses a uniform restart. Topic-Sensitive PageRank (Haveliwala, 2002) used one of sixteen pre-computed topic vectors. HARP uses a softmax over semantic similarity to the current task:
teleport(node) ∝ exp( temperature × cosine_similarity(task_text, node_text) )
The temperature controls how sharply the teleport concentrates on the most similar nodes. Higher temperature = sharper focus on the closest matches. Lower temperature = more spread-out exploration. Default value: temperature = 10.
The result is a probability distribution over the graph that puts more weight on nodes whose descriptions match the incoming task. This is what gives brand-new agents (with no history at all) a meaningful starting score.
The update rule
The whole algorithm fits in one line:
new_score = damping × M × current_score + (1 − damping) × teleport
With damping = 0.85 (the same value Brin and Page used in the original PageRank paper). Start from score = teleport and iterate until consecutive scores differ by less than some small tolerance.
Why it converges
Here is the part that matters mathematically. The update rule is a contraction with rate equal to the damping factor. For any two starting score vectors s and s':
distance( update(s), update(s') ) ≤ damping × distance( s, s' )
By the Banach fixed-point theorem, the iteration has a unique fixed point and converges to it geometrically. With damping = 0.85 and tolerance 10⁻⁷, convergence takes about a hundred iterations.
Each iteration is a sparse matrix-vector multiply, so the total cost is:
total_cost = O( edges × log(1 / tolerance) / log(1 / damping) )
In practice, with twelve agents and eight skills, that comes out to about fifteen milliseconds per query on a laptop.
The code
Here is the algorithm itself, stripped to its essentials. I have left out the setup and the data loading; the full version is roughly two hundred lines of Python and is available as a self-contained script.
python
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
def beta_smoothed(successes, attempts):
"""Bayesian posterior mean with Beta(1,1) prior — 0.5 for zero data."""
failures = attempts - successes
return (1 + successes) / (2 + successes + failures)
def col_normalize(W, eps=1e-12):
"""Make every column of W sum to 1 (column-stochastic)."""
col_sums = W.sum(axis=0, keepdims=True)
col_sums = np.where(col_sums < eps, 1.0, col_sums)
return W / col_sums
def harp_rank(task_idx, W_P, W_C, agent_emb, skill_emb, task_emb,
alpha=0.85, kappa=10.0, beta=(0.5, 0.3, 0.2),
max_iter=200, tol=1e-7):
"""
Run HARP for one task and return per-agent scores.
"""
n_agents, n_skills = W_P.shape
V = n_agents + n_skills + 1
A_idx = np.arange(0, n_agents)
S_idx = np.arange(n_agents, n_agents + n_skills)
T_idx = n_agents + n_skills
# --- Build the three column-stochastic transition matrices ---
M_P = np.zeros((V, V))
P_skill_to_agent = col_normalize(W_P.T)
P_agent_to_skill = col_normalize(W_P)
for ai in range(n_agents):
for si in range(n_skills):
M_P[A_idx[ai], S_idx[si]] = P_skill_to_agent[si, ai]
M_P[S_idx[si], A_idx[ai]] = P_agent_to_skill[ai, si]
M_P[T_idx, T_idx] = 1.0
zero = M_P.sum(axis=0) < 1e-12
M_P[:, zero] = 1.0 / V # dangling-node fix
M_C = np.eye(V)
C_norm = col_normalize(W_C)
for i in range(n_agents):
M_C[A_idx[i], A_idx] = 0.0
M_C[A_idx, A_idx[i]] = C_norm[:, i]
zero = M_C.sum(axis=0) < 1e-12
M_C[:, zero] = 1.0 / V
M_phi = np.zeros((V, V))
sim_st = (1 + cosine_similarity(skill_emb, task_emb[task_idx:task_idx+1]).ravel()) / 2
sim_at = (1 + cosine_similarity(agent_emb, task_emb[task_idx:task_idx+1]).ravel()) / 2
M_phi[S_idx, T_idx] = 0.5 * sim_st / sim_st.sum()
M_phi[A_idx, T_idx] = 0.5 * sim_at / sim_at.sum()
M_phi[T_idx, S_idx] = sim_st / sim_st.sum()
zero = M_phi.sum(axis=0) < 1e-12
M_phi[:, zero] = 1.0 / V
# --- Combine into one column-stochastic operator ---
M = beta[0] * M_P + beta[1] * M_C + beta[2] * M_phi
# --- Build the semantic teleport vector ---
sims = np.concatenate([
cosine_similarity(agent_emb, task_emb[task_idx:task_idx+1]).ravel(),
cosine_similarity(skill_emb, task_emb[task_idx:task_idx+1]).ravel(),
np.array([0.0]),
])
p = np.exp(kappa * sims); p = p / p.sum()
# --- Power iteration ---
r = p.copy()
for _ in range(max_iter):
r_new = alpha * (M @ r) + (1 - alpha) * p
if np.linalg.norm(r_new - r, ord=1) < tol:
break
r = r_new
return r[A_idx] # agent scores only
That is the whole algorithm. The intuition: build three transition matrices, mix them, run a hundred iterations of matrix-vector multiplication, return the slice corresponding to agents.
A worked example. Say a user submits the task “Open this PDF and find the date of the third figure caption.” The semantic teleport concentrates on PDF-reading agents and on the PDF-reading skill node. The performance matrix pulls probability toward agents with a strong empirical track record on PDF tasks. The endorsement matrix adds mass to agents that other PDF-capable agents have successfully collaborated with. After convergence, the top score belongs to whichever agent best balances all three signals. In testing, this is typically a deep-research agent or a specialist PDF reader — not the most expensive generalist, which is what a benchmark-only router would pick.
How we evaluated it
I tested HARP on a synthetic dataset calibrated to the GAIA benchmark 466 real-world tasks across three difficulty levels, with explicit per-task tool annotations that map cleanly onto skills. The HAL Holistic Agent Leaderboard, hosted at Princeton, publishes per-task success traces for around two dozen agent configurations on GAIA’s public validation split, which gives us a real performance signal.
The test setup was twelve agents (mix of generalists, specialist coders, deep-research bots, and browser scaffolds), eight skills, and thirty tasks split fifty-fifty into training and test. I built six rankers and measured each on five metrics.
┌────────────────────┬───────────────────────┬─────────┬──────┬────────┐
│ Method │ What it uses │ NDCG@5 │ MRR │ Regret │
├────────────────────┼───────────────────────┼─────────┼──────┼────────┤
│ Random │ nothing │ 0.42 │ 0.31 │ 0.58 │
│ Performance only │ history │ 0.67 │ 0.62 │ 0.31 │
│ Similarity only │ embeddings │ 0.71 │ 0.65 │ 0.27 │
│ Vanilla PageRank │ endorsements │ 0.58 │ 0.51 │ 0.39 │
│ Weighted average │ all three (linearly) │ 0.78 │ 0.72 │ 0.19 │
│ HARP │ all three (graph) │ 0.86 │ 0.81 │ 0.11 │
└────────────────────┴───────────────────────┴─────────┴──────┴────────┘
Higher is better for NDCG@5 and MRR. Lower is better for Regret.
Two things to notice. First, every single-signal baseline performs worse than HARP. That part is unsurprising. The interesting result is the second one: the naive weighted average which uses the same three signals as HARP, just combined linearly without the graph structure is meaningfully worse than HARP. The gap (0.78 vs 0.86 on NDCG@5) is the value of doing the fusion at the operator level rather than the score level. The random walk lets each signal reinforce or counteract the others through the graph structure, which a flat weighted average cannot do.
An ablation study confirmed that all three signals are non-redundant — removing any one of them drops NDCG@5 by at least 0.05 points.
Where this actually matters: MCP servers and agent repositories
This is where the research becomes useful.
The Model Context Protocol launched in late 2024 and has since produced a fast-growing ecosystem of servers. The official MCP registry now lists hundreds of public servers, ranging from filesystem access to Notion integration to Postgres query helpers to specialized vertical tools. Cloud providers have launched their own marketplaces. Oracle’s Fusion AI Agent Marketplace landed in October 2025. AWS Bedrock added an agent catalog. Backstage plugins for agent registration are increasingly common in enterprise platform teams.
This is a discoverability problem. When you have three MCP servers that can edit code and they all claim to be “powerful coding assistants,” which one do you actually invoke? When you have eight retrieval tools and they all claim to be “fast and accurate,” which one do you call for a long-document QA task?
The current answer is “try them all” or “hardcode your favorite.” Neither scales.
HARP gives you a third option. You maintain three things: a small log of past invocations and their outcomes, a graph of which tools call which other tools (free, you already have it in your traces), and a description of each tool (also free, every MCP server publishes one). Plug those into HARP and the next invocation gets the right tool ranked first.
A few concrete use cases that have come up in conversations with people building on this:
Tool selection inside MCP-aware agents. When an agent has access to a hundred MCP tools, the model itself struggles to pick the right one from the list. HARP precomputes a top-k shortlist per task. The model only sees the three or five most relevant tools, which dramatically improves selection accuracy.
Routing in production agent fleets. Companies running their own internal agent platforms now have dozens of agents from different teams. HARP sits between the request entry point and the agent runtime, returning a ranked list. The platform can apply additional policy (cost caps, compliance rules) on top of the ranking.
Marketplace search. Agent marketplaces and MCP registries need a search experience. Today most use keyword match or basic embedding retrieval. HARP gives them performance-aware search: agents that actually work for the kind of task you described show up first.
Multi-agent orchestration. Frameworks like AutoGen and CrewAI need to decide which sub-agent to dispatch a sub-task to. HARP is a drop-in solution. The orchestration framework already produces the endorsement graph as a side effect of normal operation.
In every case the win is the same: you stop wasting calls on the wrong tool, you stop paying for expensive generalists when a specialist works fine, and you get better answers because the right agent is actually good at the job.
What is genuinely new here
Most of the pieces of HARP are not new. PageRank is from 1998. Personalized PageRank is from 2003. EigenTrust is from 2003. Topic-Sensitive PageRank is from 2002. Beta-Bernoulli smoothing is older than all of them. Sentence embeddings have been in cheap reach for several years.
What is new is the synthesis, and it is new in three specific ways.
The first is the formal proof that you can fuse three heterogeneous signals (Bayesian-smoothed performance, EigenTrust-style endorsement, embedding similarity) at the operator level — by taking a convex combination of column-stochastic matrices — without breaking the PageRank convergence guarantee. The proof is a one-liner once you see it, but seeing it requires the careful construction of the three matrices. Earlier work either fused at the score level (which loses the graph structure) or fused at the tensor level (which loses uniqueness; see Multilinear PageRank, Gleich, Lim, and Yu, 2015).
The second is the task-conditional softmax teleport. Topic-Sensitive PageRank used one of sixteen pre-computed topic vectors. HARP uses a continuous embedding-induced teleport, computed fresh per query. This is what gives cold-start agents a meaningful initial score and what makes the algorithm sensitive to fine-grained task semantics.
The third is the explicit cold-start theorem. We can prove that when an agent has no history and no endorsements, its HARP score reduces to a similarity-only ranking — gracefully degrading to the right baseline behavior instead of throwing a divide-by-zero or returning a meaningless number. That is unusually clean for a ranking system and matters a lot in practice, where new agents arrive constantly.
Together these three points are enough for a real research contribution. Individually each is a small refinement. But the agent ecosystem needs this kind of small, careful, well-justified machinery a lot more than it needs another transformer.
What I would build next
The reference implementation is batch. It recomputes the matrix every fifteen minutes from accumulated outcomes. The next obvious step is online — wrap HARP in a Thompson sampling layer so the system actively explores. Right now if an agent gets unlucky early it can struggle to climb back; with active exploration, every agent gets enough invocations to find its level.
The endorsement graph is a target for gaming. Self-endorsements are already filtered. But two colluding agents could inflate each other. A TrustRank-style seed set (Gyöngyi, Garcia-Molina, Pedersen, VLDB 2004) — propagate trust only from a small vetted set of known-good agents — would harden this. I have not built it yet because the threat is theoretical at the scales I have tested.
Time decay is worth adding before any of that. An agent that was good six months ago and is good today should rank higher than an agent that was good six months ago and has not been used since. A simple exponential decay on both performance and endorsement weights handles this with one extra hyper parameter.
The bigger research direction is federated ranking. Different organizations run different agent fleets. Each has private performance data they cannot share. EigenTrust has a beautiful secure-aggregation construction for exactly this case. The same machinery should work for HARP, and it would enable cross-organization agent quality signals without leaking individual outcomes.
Try it
The full reference implementation is roughly two hundred lines of Python. It runs in Google Colab on the free tier in under a minute, including the embedding model download. It generates synthetic GAIA-style tasks, simulates outcomes, builds the matrices, runs the benchmark, and prints the ablation. If you want to plug your own agents in, replace the agent and skill dictionaries at the top of the file with yours, and it will work.
If you are building on MCP, the integration is even simpler. Your MCP traces already contain everything HARP needs which tool was invoked, what task, whether the call succeeded. Pipe that into the outcome history and HARP gives you back a ranking function you can call before every tool invocation.
We have spent two years scaling models. We have spent only the last few months scaling agents. The next few years are going to be about the infrastructure between them — the discovery, the routing, the trust, the orchestration. Most of that infrastructure does not exist yet. PageRank shows us that a small amount of clever math at the right layer can be worth more than a hundred times its weight in product engineering.
It is probably worth a try.
Built as part of a research project on multi-agent routing. The full algorithm, math, code, and tests are open source. If you build on this, I would love to hear what you find.
References: Brin and Page (1998), Haveliwala (2002), Kamvar et al. (2003), Gyöngyi et al. (2004), Gleich et al. (2015), Mialon et al. (2023), Kapoor et al. (2025).
메타데이터
- post_id
- 2ca8d077e0f8
- slug
- harp-teaching-page-rank-to-pick-the-right-ai-agent-2ca8d077e0f8
- url
- https://medium.com/@deveshruttala/harp-teaching-page-rank-to-pick-the-right-ai-agent-2ca8d077e0f8
- canonical_url
- https://medium.com/@deveshruttala/harp-teaching-page-rank-to-pick-the-right-ai-agent-2ca8d077e0f8
- author_url
- https://medium.com/@deveshruttala
- status
- ok
- fetched_at
- 2026-06-09 15:37:30