rushdb
FeaturesUse casesCompareGuidesDocsBlogPricingGitHub
Sign InGet API Key

Workflows

Agent memory

Keep state, decisions, and tool output available across sessions.

RAG and knowledge bases

Retrieve related context, not only the nearest similar chunks.

AI-powered apps

Add search and connected-data features without another sync pipeline.

Popular blueprints

Customer support memory

Give support agents instant recall of tickets, resolutions, and account context across channels.

Sales & CRM memory

Persist deal history and account context so every rep and agent recalls the right details.

Transaction monitoring

Trace suspicious transfers across accounts, devices, merchants, counterparties, alerts, and KYC context.

Multi-agent incident response

Coordinate a live SaaS incident through shared, durable graph memory.

Browse by industry

Healthcare & Life SciencesFinance & ComplianceLegalReal EstateSecuritySales & SupportEducation & TrainingCommerce & Retail
View all use cases →

RushDB vs Neo4j

Compare RushDB and Neo4j for graph-backed AI agent memory: query interface, schema, vector search, and deployment.

RushDB vs Mem0

Compare RushDB and Mem0 for AI agent memory: storage model, retrieval, graph memory, and deployment.

RushDB vs SurrealDB

Compare RushDB and SurrealDB for AI agent memory: data model, graph traversal, vector search, schema, and deployment.

RushDB vs Weaviate

Compare RushDB and Weaviate for AI agent memory: data model, hybrid search, managed embeddings, schema, and deployment.

RushDB vs Pinecone

Compare RushDB and Pinecone for AI agent memory: data model, relationships, managed embeddings, deployment, and pricing.

View all comparisons →

AI Agent Memory

Learn how persistent AI agent memory stores decisions, tool output, entities, and relationships outside a single model session.

Graph Analytics

What graph analytics is, when it beats row-based analytics, and how it applies to fraud detection, customer 360, supply chains, and AI agent reasoning.

GraphRAG vs Traditional RAG

Compare GraphRAG and traditional vector-only RAG: retrieval quality, explainability, multi-hop reasoning, and operational complexity.

JSON to Graph Database

Turn nested JSON into a queryable graph without designing a schema first. See how property types, parent-child links, and live schema are inferred on write.

MCP Memory Backend

Use RushDB as a persistent memory backend behind the Model Context Protocol. Give Claude Desktop, Cursor, and other MCP clients durable, queryable agent memory.

View all guides →
rushdb

Open-source structured memory infrastructure for AI-native apps, with graph memory, semantic search, and live schema.

GitHubDiscord

Product

FeaturesUse casesPricingSecurityChangelog

Resources

DocsGuidesCompareBlogQuick startAPI reference

Deploy

CloudSelf-hostingMCP serverOpen sourceGitHub

Company

ContactPrivacy policyTerms of serviceCookie policy

© 2026 Collect Software Inc.

PrivacyTermsCookies
15th July 202624 min readRushDB
ai-agentsschema-discoverygraph-databaserushdbagent-memorydynamic-schemallm

RushDB

Give your agent a memory.

Push any JSON. Get graph relationships and vector search instantly — no schema, no pipeline, no setup.

Start building free →

FAQ

More Posts

data-pipelinesai-architecturegraph-database

Why Every AI Stack Grows Into Five Data Pipelines

LLM applications naturally fragment into ETL, embedding, graph sync, search indexing, and metadata pipelines. Learn why this happens and how a single ingestion layer can replace.

19th July 2026—20 min read

RushDB 2.0: Memory Infrastructure for the Agentic Era

RushDB 2.0 is a major release built for the agentic era: native semantic search, ontology-aware querying, MCP with OAuth, bring-your-own Neo4j, and prebuilt agent skills. It turns memory infrastructure into one unified layer, so developers can store structured context, traverse relationships, and search by meaning without stitching together multiple systems.

21st May 2026
—11 min read
rushdbgraphsgraph theory

Labeled Meta Property Graphs (LMPG): A Property-Centric Approach to Graph Database Architecture

Discover how LMPG transforms graph databases by treating properties as first-class citizens rather than simple node attributes. This comprehensive technical guide explores RushDB's groundbreaking architecture that enables automatic schema evolution, property-first queries, and cross-domain analytics impossible in traditional property graphs or RDF systems.

7th July 2025—34 min read

Stop Teaching Agents Your Schema

Every time you build a new AI agent on top of a database, you write the same kind of system prompt: a description of your labels, fields, relationships, and whatever values the LLM needs to filter correctly. The prompt works until the schema changes. Then it silently drifts, and the agent starts generating queries against fields that no longer exist or missing fields that were added last sprint.

The root problem is that schema knowledge lives in a document—a prompt, a README, a Confluence page—rather than in the database itself. RushDB takes a different approach. It continuously tracks its own structure and exposes that structure through a dedicated endpoint, so agents can fetch a current snapshot of what exists before they construct a single query.

This article explains how that endpoint works, walks through a verified experiment that shows the exact response shape, and discusses where the approach has real limits.

The Schema Narration Problem

When a developer writes a schema prompt, they are doing something that feels routine but is actually fragile: translating a live database structure into static text and hoping the two stay synchronized. Three things make that synchronization hard.

Schema drift. Databases evolve. A new ownerTeam field gets added to issues, a relationship gets renamed, an embedding index gets created on a property. None of those changes automatically propagate to the system prompt. The agent keeps generating queries against the old mental model.

Token cost. A thorough schema description for a moderately complex graph can run to several hundred tokens per agent invocation. Multiply that by session volume and the cost compounds quickly—especially when most of those tokens describe parts of the schema the agent never touches in a given session.

Opaque values. Independent research on agent-facing schema design argues that schemas should expose human-readable values rather than opaque identifiers, so agents can filter correctly without extra mapping logic Your Database Schema Is Your Agent's Mental Model. A static prompt rarely includes the actual values a field contains in practice. An agent told that priority is a string field has to guess whether the values in use are high/medium/low, P0/P1/P2, or something else entirely.

Research on agentic schema refinement proposes giving agents minimal seed instructions and direct database access so they can discover structure, refine a semantic layer, and validate results—rather than relying on static documentation Towards Agentic Schema Refinement. RushDB's runtime schema discovery is a production implementation of that principle.

The Fixture: A Project Graph with Issues and Contributors

To make the mechanism concrete, start with a realistic graph. The fixture below represents a software project called "Atlas Support" with two nested record types: ISSUE records that carry status, priority, ownerTeam, and title fields, and CONTRIBUTOR records that carry name and team fields. Importing this single JSON payload creates five records and the parent-child relationships between them automatically.

{
  "PROJECT": {
    "name": "Atlas Support",
    "repository": "atlas-api",
    "ISSUE": [
      {
        "title": "Checkout timeout",
        "status": "open",
        "priority": "high",
        "ownerTeam": "backend"
      },
      {
        "title": "Update empty state",
        "status": "open",
        "priority": "low",
        "ownerTeam": "frontend"
      }
    ],
    "CONTRIBUTOR": [
      {
        "name": "Mina",
        "team": "backend"
      },
      {
        "name": "Noah",
        "team": "frontend"
      }
    ]
  }
}

RushDB's import_json / importJson method accepts nested JSON and infers the graph structure from the key hierarchy. The top-level label is PROJECT; nested arrays whose keys are uppercase become child labels (ISSUE, CONTRIBUTOR). The suggestTypes option tells RushDB to infer property types from the values rather than storing everything as strings.

Once this data is in the graph, no schema documentation has been written anywhere. The structure exists only inside RushDB—built on Neo4j as the underlying storage and transaction engine, with RushDB's Labeled Meta Property Graph (LMPG) model managing how properties and relationships are represented as first-class structures above the storage layer.

How Runtime Schema Discovery Works

RushDB is self-aware about its own structure: as records are written, the LMPG model continuously tracks which labels exist, which properties appear on each label, what types and value ranges those properties hold, and which labels are connected by relationships. That tracked structure is what the discovery endpoints expose.

Two methods surface it:

db.ai.get_schema_markdown / db.ai.getSchemaMarkdown returns the schema as a compact Markdown string. It is designed for direct LLM consumption: labels with record counts, properties with types and sampled values, relationship traversal patterns, and a "Semantic Search" column that shows embedding index metadata for any property that has a vector index configured. When no embedding index exists for a property, the column shows —. The entire graph schema arrives as a single token-efficient string that an agent can place directly in its context window.

db.ai.get_schema / db.ai.getSchema returns the same information as structured JSON. Each item in the response array contains the label name, record count, a properties array with name, type, and sampled values, and a relationships array with direction and connected label. When a property has an embedding index, a vectorIndexes array appears on that property entry, exposing id, sourceType, similarityFunction, dimensions, status, and modelKey. A non-empty vectorIndexes array means the property is queryable with db.records.vector_search / db.records.vectorSearch.

Both methods accept an optional labels filter to scope the response to a subset of the graph, which is useful when an agent only needs to reason about one or two record types.

The RushDB Schema API feature page describes the design intent: the first schema call tells the agent what data exists and what queries are safe to construct. RushDB's agent-safe query planning documentation recommends a schema-first loop—load the schema first, learn the query specification second, and only then build queries from what actually exists Agent-Safe Query Planning with Schema First | RushDB Docs.

DiagramClick to expand

The diagram shows the two-step pattern: one schema call at session start, then one or more record queries grounded in the returned structure. The agent never has to guess whether priority accepts "high" or "HIGH" or "p1"—the schema response includes the actual sampled values observed in the graph.

Fetching and Using the Discovered Schema

With the fixture in place, call get_schema_markdown to retrieve the LLM-ready representation.

# Python
from rushdb import RushDB

db = RushDB("RUSHDB_API_KEY")

schema_markdown = db.ai.get_schema_markdown({})
print(schema_markdown.data)

The response is a single Markdown string. An agent can place schema_markdown.data directly into its system prompt or inject it as a user-turn context message before asking the LLM to generate a query. Because the string is fetched at session start rather than hardcoded, it reflects the state of the graph at the time the cache was last populated—more on that timing in the limitations section.

For programmatic use—validation, query planning logic, graph visualization—call the JSON variant instead:

# Python
import json
from rushdb import RushDB

db = RushDB("RUSHDB_API_KEY")

schema = db.ai.get_schema({})
print(json.dumps(schema.data, indent=2))
schema = db.ai.get_schema({})
print(json.dumps(schema.data, indent=2))

The JSON response gives an agent's orchestration layer everything it needs to validate a proposed query before executing it: the exact label names, the property names and their types, sampled values for string fields, and the relationship directions. A query that references a label or field not present in this response can be rejected before it ever reaches the database.

Once the schema is loaded, building a grounded query is straightforward. The Markdown output shows that ISSUE records have a priority field with observed values "high" and "low", and a status field with observed value "open". An agent can use those exact values:

# Python
import json
from rushdb import RushDB

db = RushDB("RUSHDB_API_KEY")

records = db.records.find({
    "labels": ["ISSUE"],
    "where": {
        "status": "open",
        "priority": "high"
    },
    "limit": 10
})
print(json.dumps({"total": records.total, "data": [r.data for r in records.data]}, indent=2))
records = db.records.find({
    "labels": [
        "ISSUE"
    ],
    "where": {
        "status": "open",
        "priority": "high"
    },
    "limit": 10
})
print(json.dumps({"total": records.total, "data": [record.data for record in records.data]}, indent=2))

The agent did not need to be told that ISSUE is a valid label, that priority accepts "high", or that status accepts "open". It discovered all of that from the schema response.

What the Schema Response Actually Contains

The Markdown output from get_schema_markdown for the Atlas Support fixture looks like this:

# Graph Schema

## Labels

| Label | Count |
|-------|------:|
| `CONTRIBUTOR` | 2 |
| `ISSUE` | 2 |
| `PROJECT` | 1 |

---

## `ISSUE` (2 records)

### Properties

| Property | Type | Values / Range | Semantic Search |
|----------|------|----------------|-----------------|
| `ownerTeam` | string | `backend`, `frontend` | — |
| `priority` | string | `high`, `low` | — |
| `status` | string | `open` | — |
| `title` | string | `Checkout timeout`, `Update empty state` | — |

### Relationships

| Traversal | Count | Edge Properties |
|-----------|------:|-----------------|
| `(ISSUE)<-[:__RUSHDB__RELATION__DEFAULT__]-(PROJECT)` | 2 | — |

---

## `CONTRIBUTOR` (2 records)

Several things are worth noting in this output. First, the three labels (CONTRIBUTOR, ISSUE, PROJECT) appear with their record counts. Second, each label section lists its properties with types and sampled values—priority shows high and low, status shows open, ownerTeam shows backend and frontend. An agent reading this knows what filter values have been observed in the graph without any additional documentation. Third, the Relationships section for each label shows the traversal pattern with direction, so an agent can reason about which labels are connected and in which direction.

The Semantic Search column shows — for every property in this fixture because no embedding indexes have been configured. When a property does have a vector index, that column would show the index metadata (for example, managed cosine 1536d [ready]), signaling to the agent that db.records.vector_search / db.records.vectorSearch is available for that property.

The JSON response carries the same information in a machine-readable form:

[
  {
    "label": "ISSUE",
    "count": 2,
    "properties": [
      {
        "name": "ownerTeam",
        "type": "string",
        "recordsCount": 2,
        "values": [
          "backend",
          "frontend"
        ]
      },
      {
        "name": "priority",
        "type": "string",
        "recordsCount": 2,
        "values": [
          "high",
          "low"
        ]
      },
      {
        "name": "status",
        "type": "string",
        "recordsCount": 2,
        "values": [
          "open"
        ]
      },
      {
        "name": "title",
        "type": "string",
        "recordsCount": 2,
        "values": [
          "Checkout timeout",
          "Update empty state"
        ]
      }
    ],
    "relationships": [
      {
        "label": "PROJECT",
        "direction": "in",
        "count": 2,
        "type": "RUSHDB_DEFAULT_RELATION"
      }
    ]
  }
]

The relationships array on each label entry includes direction ("in" or "out"), the connected label, and a count. This is the same structural information that db.relationships.patterns.list() exposes for relationship pattern analysis—the live schema endpoint surfaces it inline so an agent does not need a separate call to understand graph connectivity.

The downstream query result confirms that schema-grounded filtering works correctly:

{
  "data": [
    {
      "__label": "ISSUE",
      "ownerTeam": "backend",
      "priority": "high",
      "status": "open",
      "title": "Checkout timeout"
    }
  ],
  "total": 1
}

One record is returned: "Checkout timeout", with priority: "high" and status: "open". The searchQuery field in the response echoes back the exact query that was executed, which is useful for debugging and for agents that need to log or audit their query decisions.

Verification details — exact normalized responses

Verified against an isolated RushDB project in Python and TypeScript. Volatile record identifiers are normalized.

Capture the token-efficient schema

{
  "data": "# Graph Schema\n\n## Labels\n\n| Label | Count |\n|-------|------:|\n| `CONTRIBUTOR` | 2 |\n| `ISSUE` | 2 |\n| `PROJECT` | 1 |\n\n---\n\n## `ISSUE` (2 records)\n\n### Properties\n\n| Property | Type | Values / Range | Semantic Search |\n|----------|------|----------------|-----------------|\n| `ownerTeam` | string | `backend`, `frontend` | — |\n| `priority` | string | `high`, `low` | — |\n| `status` | string | `open` | — |\n| `title` | string | `Checkout timeout`, `Update empty state` | — |\n\n### Relationships\n\n| Traversal | Count | Edge Properties |\n|-----------|------:|-----------------|\n| `(ISSUE)<-[:__RUSHDB__RELATION__DEFAULT__]-(PROJECT)` | 2 | — |\n\n---\n\n## `CONTRIBUTOR` (2 records)\n\n### Properties\n\n| Property | Type | Values / Range | Semantic Search |\n|----------|------|----------------|-----------------|\n| `name` | string | `Mina`, `Noah` | — |\n| `team` | string | `backend`, `frontend` | — |\n\n### Relationships\n\n| Traversal | Count | Edge Properties |\n|-----------|------:|-----------------|\n| `(CONTRIBUTOR)<-[:__RUSHDB__RELATION__DEFAULT__]-(PROJECT)` | 2 | — |\n\n---\n\n## `PROJECT` (1 record)\n\n### Properties\n\n| Property | Type | Values / Range | Semantic Search |\n|----------|------|----------------|-----------------|\n| `name` | string | `Atlas Support` | — |\n| `repository` | string | `atlas-api` | — |\n\n### Relationships\n\n| Traversal | Count | Edge Properties |\n|-----------|------:|-----------------|\n| `(PROJECT)-[:__RUSHDB__RELATION__DEFAULT__]->(CONTRIBUTOR)` | 2 | — |\n| `(PROJECT)-[:__RUSHDB__RELATION__DEFAULT__]->(ISSUE)` | 2 | — |",
  "success": true
}

Capture the structured schema response

{
  "data": [
    {
      "count": 1,
      "label": "PROJECT",
      "properties": [
        {
          "id": "<record-id>",
          "name": "name",
          "recordsCount": 1,
          "type": "string",
          "values": [
            "Atlas Support"
          ]
        },
        {
          "id": "<record-id>",
          "name": "repository",
          "recordsCount": 1,
          "type": "string",
          "values": [
            "atlas-api"
          ]
        }
      ],
      "relationships": [
        {
          "count": 2,
          "direction": "out",
          "label": "CONTRIBUTOR",
          "type": "RUSHDB_DEFAULT_RELATION"
        },
        {
          "count": 2,
          "direction": "out",
          "label": "ISSUE",
          "type": "RUSHDB_DEFAULT_RELATION"
        }
      ]
    },
    {
      "count": 2,
      "label": "CONTRIBUTOR",
      "properties": [
        {
          "id": "<record-id>",
          "name": "name",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "Mina",
            "Noah"
          ]
        },
        {
          "id": "<record-id>",
          "name": "team",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "backend",
            "frontend"
          ]
        }
      ],
      "relationships": [
        {
          "count": 2,
          "direction": "in",
          "label": "PROJECT",
          "type": "RUSHDB_DEFAULT_RELATION"
        }
      ]
    },
    {
      "count": 2,
      "label": "ISSUE",
      "properties": [
        {
          "id": "<record-id>",
          "name": "ownerTeam",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "backend",
            "frontend"
          ]
        },
        {
          "id": "<record-id>",
          "name": "priority",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "high",
            "low"
          ]
        },
        {
          "id": "<record-id>",
          "name": "status",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "open"
          ]
        },
        {
          "id": "<record-id>",
          "name": "title",
          "recordsCount": 2,
          "type": "string",
          "values": [
            "Checkout timeout",
            "Update empty state"
          ]
        }
      ],
      "relationships": [
        {
          "count": 2,
          "direction": "in",
          "label": "PROJECT",
          "type": "RUSHDB_DEFAULT_RELATION"
        }
      ]
    }
  ],
  "success": true
}

Trade-offs and Practical Boundaries

Runtime schema discovery is a useful grounding mechanism, but it has real constraints that matter for production agent design.

Schema snapshot caching. The graph structure tracked by RushDB's LMPG model updates as records are written. However, the snapshot returned by the schema endpoints is cached for approximately one hour. A schema call made shortly after a migration or bulk import may still return the previous cached snapshot until the cache refreshes. For most agent workflows this lag is acceptable, but agents that operate immediately after a schema migration or a bulk import should account for this latency window.

Sampled values, not exhaustive enumerations. The values array in the JSON response and the Values/Range column in the Markdown output contain samples drawn from existing records, not a complete enumeration of every distinct value in the database. For high-cardinality string fields like title or name, the samples are illustrative rather than exhaustive. An agent should treat sampled values as evidence of format and vocabulary—useful for constructing plausible filters—not as a closed set of the only valid inputs.

Schema grounding reduces but does not eliminate query errors. Knowing that priority has been observed with values "high" and "low" prevents the agent from filtering on "P1", but it does not prevent logical errors in query construction—incorrect where clause nesting, wrong relationship traversal direction, or mismatched label scoping. Schema grounding narrows the error surface; it does not make queries risk-free.

Relationship traversal depth is not exposed. The schema shows direct relationships between labels (one hop), but it does not describe multi-hop traversal paths. An agent that needs to reason about paths like PROJECT → ISSUE → CONTRIBUTOR must infer the chain from the per-label relationship entries rather than reading a single traversal descriptor.

Vector index readiness is a prerequisite for semantic search. The Semantic Search column in the Markdown schema shows index metadata, but an index in a non-ready state cannot be queried with db.records.vector_search. The schema tells the agent whether an index exists and what its current status is; the agent is responsible for checking that status before attempting a vector search call.

Schema Discovery as a First-Class Agent Primitive

The core shift RushDB enables is moving schema knowledge from a document you maintain to a runtime call the agent makes. Instead of writing a prompt that says "the ISSUE label has a priority field that accepts high, medium, or low", the agent calls get_schema_markdown at session start and reads that fact directly from the graph.

The verified experiment shows what this looks like in practice: a five-record graph with three labels, imported from a single nested JSON payload, produces a complete Markdown schema that includes label counts, property types, sampled values, relationship directions, and vector index status—all without any manual schema documentation. A downstream query built from that schema returns exactly the expected record.

The mechanism has a real boundary: the schema snapshot is cached for roughly an hour, so graph changes made after the last cache population may not appear immediately in a subsequent schema call. Sampled values are not exhaustive enumerations, and grounding does not eliminate all query errors. Those constraints are worth understanding before designing an agent that depends on schema freshness or treats sampled values as closed sets.

For teams building agents on evolving graphs, the practical next step is to add a schema fetch as the first operation in every agent session. The RushDB agent memory documentation covers how to structure that session initialization, and the Schema API feature page describes the full response contract including vector index metadata.

Sources

  • Schema Self-Awareness | RushDB Docs
  • Schema API For Schema-Aware AI Agents | RushDB
  • Agent Memory Overview | RushDB Docs
  • Agent-Safe Query Planning with Schema First | RushDB Docs
  • RushDB 2.0: Memory Infrastructure for the Agentic Era
  • GitHub - rush-db/rushdb
  • Your Database Schema Is Your Agent's Mental Model
  • Towards Agentic Schema Refinement

Use the discovered fields in a structured query

{
  "data": [
    {
      "__label": "ISSUE",
      "ownerTeam": "backend",
      "priority": "high",
      "status": "open",
      "title": "Checkout timeout"
    }
  ],
  "searchQuery": {
    "labels": [
      "ISSUE"
    ],
    "limit": 10,
    "where": {
      "priority": "high",
      "status": "open"
    }
  },
  "total": 1
}