> “Which query optimization candidates came out of last week’s investigation?”

When an agent gets that question, search finds the discovery. The answer lives in a connected insight, grounded by a [memory](/blog/2026-05-18-memory-is-not-a-database) that preserves the workload baseline. Search can return all three as separate matches. The agent still needs the routes between them.

In a codebase, search finds a matching token. Navigation follows the symbol through its definition, implementations, and callers. An agent needs the same path through the dashboards, insights, discoveries, and memories available through a [shared context store](/blog/2026-07-15-lakehouse-as-context-store).

Product code publishes an ontology: a type map that defines which entity types can connect. Each organization and environment supplies the live entities and connections that form its entity graph. A search hit stops being the answer.

The opening animation shows the full handoff before we unpack each piece: Map, Search, Traverse, Read.

<BlogWideSection>
  <AnimatedBlogDiagram
    desktopSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-intro.svg"
    mobileSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-intro-mobile.svg"
    alt="The agent receives the current ontology, searches live entities, traverses from a discovery to its neighbors, and reads INS-208 and MEM-72"
    animationLabel="introduction animation"
    className="aspect-[560/820] md:aspect-[1000/620]"
  />
</BlogWideSection>

## Product code publishes the ontology

Each entity type publishes a compact ontology contract: its handle prefix, description, and whether its instances are indexed. The route list is declared in product code. One entry defines the routes out of a discovery:

```ruby
{
  from: NodeType::Kind::Discovery,
  to: [
    NodeType::Kind::Insight,
    NodeType::Kind::Dashboard,
    NodeType::Kind::Segment,
    NodeType::Kind::SemanticModel,
    NodeType::Kind::Memory
  ]
}
```

Those declarations produce the ontology. The entity graph is materialized separately: when an entity changes, Altertable stores a compact searchable copy of it and its outgoing edges. Traversal reads that copy instead of joining product tables on every request.

Relationships come from explicit product references, including references supplied when agents create objects. We do not infer them from text.

Deploy an ontology node type or update the declared route list, and the next call to the Model Context Protocol (MCP) tool [`initialize`](/docs/api-references/mcp-tools) returns the updated ontology. Product code remains the source, so agents receive the current ontology instead of a static inventory embedded in the prompt. They can later re-read it from the read-only [MCP resource](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) `altertable://ontology` without another `initialize` call.

## Routes become live edges

The ontology declares routes between entity types. Search and traversal read the indexed entities and edges available to the current MCP session.

The graph below comes from a real search followed by one-hop traversal. We shortened titles, removed dates, and replaced object slugs, while preserving their prefixes, entity types, edge directions, and relationships. Inside the platform, the UI and the agent point at the same original objects.

<BlogWideSection>
  <AnimatedBlogDiagram
    desktopSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-routes.svg"
    mobileSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-routes-mobile.svg"
    alt="Ontology routes connect discovery to five entity types and memory back to discovery, while live edges connect DSC-104 to two insights and MEM-72 back to DSC-104"
    animationLabel="ontology diagram animation"
    className="aspect-[560/1140] md:aspect-[1000/720]"
  />
</BlogWideSection>

## Search finds the handle

With the ontology, organization, and environment already supplied by the Altertable MCP tool `initialize`, the agent needs a starting point.

The MCP tool [`search_entities`](/docs/api-references/mcp-tools) combines a [PostgreSQL full-text](https://www.postgresql.org/docs/current/functions-textsearch.html) lexical branch and a [pgvector](https://github.com/pgvector/pgvector) semantic branch into one ranked candidate list with [reciprocal rank fusion](https://cormack.uwaterloo.ca/cormacksigir09-rrf.pdf).

The response gives the agent each candidate's type, title, snippet, and rank. The agent compares those fields with the user's question. In the top result, `DSC-104` is the handle and `DSC` is its type-carrying prefix.

**Ranked candidates**

1. **`DSC-104`, discovery.** Weekly query workload: 6 optimization candidates.
2. **`DSC-87`, discovery.** Query latency follow-up.
3. **`MEM-41`, memory.** Optimization review notes.

**Selected starting point:** discovery `DSC-104`. It ranks first, its title and snippet match the optimization question, and its type can connect to insights and memories.

## One hop to the evidence

We could have exposed an open-ended graph query. We did not. The MCP tool [`get_neighbors`](/docs/api-references/mcp-tools) expands one hop, so the agent chooses each next edge explicitly. The agent gets the analyses and baseline directly connected to the finding instead of running another broad search.

That boundary makes each expansion explicit in the tool trace, prevents a high-degree node from flooding the context window, and requires an explicit resource read before full entity content enters context. `get_neighbors` returns 25 neighbors by default, allows at most 100, and sets `truncated` when more exist.

**One-hop neighbors of `DSC-104`**

**Outgoing**

- **`INS-208`, insight.** Optimization candidates by performance impact.
- **`INS-209`, insight.** Slowest production queries.

**Incoming**

- **`MEM-72`, memory.** Query workload baseline.

Direction is part of the result. Expanding `DSC-104` finds `MEM-72` as an incoming neighbor (`direction: "in"`) because the memory points to the discovery. The agent reaches the workload baseline behind the finding without running another text search. The two outgoing edges lead to its analyses.

This animation focuses on the handoff from search to traversal. The ranked discovery supplies the `slug` to `get_neighbors`, which returns compact references to its neighbors.

<BlogWideSection>
  <AnimatedBlogDiagram
    desktopSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-walk.svg"
    mobileSrc="/assets/blog/2026-07-22-hand-the-agent-a-map-walk-mobile.svg"
    alt="Search returns ranked candidates, an AI agent selects a discovery, and graph traversal reveals its connected evidence"
    animationLabel="search and traversal animation"
    className="aspect-[560/1240] md:aspect-[1000/620]"
  />
</BlogWideSection>

The animation stops after `get_neighbors` returns compact references. It does not read the entity bodies. To ground the answer, the agent selects `INS-208` and `MEM-72`. The client reads their full entity content from the MCP resources `altertable://ontology/entities/INS-208` and `altertable://ontology/entities/MEM-72`.

That division is intentional: [MCP tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) let the model search and traverse; [MCP resources](https://modelcontextprotocol.io/specification/2025-11-25/server/resources) give the client URI-addressed content to read after the model chooses which entities to inspect.

With both entities in context, the agent can report the 6 optimization candidates from `INS-208` and evaluate them against the workload baseline in `MEM-72`.

## A map is not access

Every session receives the same ontology: type-level routes with no live entities or edges. The map grants no access by itself.

Live entities remain scoped to the caller's organization and environment at every step. `search_entities`, `get_neighbors`, and entity resources each apply entity policy before returning candidates, neighbors, or content. A denied entity appears in none of those responses.

Entity policy is not a separate rulebook for agents. It delegates to each type's product policy, the same rules that gate dashboards and memories in the UI, so a session reads exactly what its caller could open. A denied slug also behaves like a missing one: traversal and entity reads return the same not-found result, with no hint that something was withheld.

## Put the map in the protocol

The model does not need every related entity loaded into its context window. It needs a reliable path from the search result to the evidence required for the answer.

The protocol should expose choices, not make them. Search finds a starting point. The ontology shows valid routes. The entity graph connects those routes to live evidence. The agent decides where to go next.

The map is not more context.

**It is how the agent chooses what context to read.**