rushdb
ProductSolutionsDevelopersPricingResourcesCompanyGitHub
Sign InStart building

Understand

Context layer

Why shared operational context needs its own infrastructure layer.

Product overview

Create, inspect, retrieve, use, and operate connected context.

Architecture

See the data model, query surfaces, and deployment boundaries.

Build

Ingestion and live schema

Turn evolving payloads into typed, inspectable structure.

Graph and relationships

Preserve known links and review suggested patterns.

Semantic retrieval

Combine similarity, exact filters, and connected records.

Smart Search

Generate inspectable SearchQuery from natural language.

Operate

Query and analytics

Use one query shape across records, schema, and metrics.

Deployment options

Use managed cloud, an External Database, or self-hosted infrastructure.

Security

Review privacy, controls, and deployment posture.

Explore the product →

Primary workflows

Agent context and memory

Durable state, decisions, tool output, and semantic recall.

GraphRAG

Retrieve connected evidence, not only similar chunks.

Applications

Build operational software on connected context.

Operational analytics

Analyze current values, relationships, and change.

Solution patterns

Customer intelligence

Connect customer, product, support, and event data.

Search and discovery

Power semantic, faceted, and connected discovery.

Evidence and compliance

Keep operational evidence connected and inspectable.

Blueprints

Agent systemsConnected applicationsAnalytical systemsAll blueprints
Explore all solutions and blueprints →

Documentation

Concepts, tutorials, deployment, and API guides.

Quickstart

Create a project and run your first query.

TypeScript SDK

Type-safe access for browser and Node.js applications.

Python SDK

Sync and async access for services and data workflows.

MCP server

Expose RushDB operations to MCP-compatible clients.

Agent skills

Install task guidance for memory, querying, and modelling.

Open documentation →

Guides

Evergreen explanations and implementation paths.

Comparisons

Evaluate RushDB against graph, vector, and memory tools.

Blog

Product updates and technical articles.

Architecture

Understand the data path and current boundaries.

Changelog

Follow product and platform releases.

LMPG research

Separate the property-centric implementation from research direction.

Explore resources →

Contact

Discuss product, architecture, or enterprise requirements.

Security

Security, privacy, and responsible disclosure.

Open source

Review the source, open issues, and contribute.

Contact RushDB →
rushdb

Open-source context infrastructure for agents, applications, and analytics, with connected records, live schema, semantic retrieval, and operational queries through one API.

GitHubDiscord

Product

Context layerProduct overviewArchitecturePricingSecurityDeployment

Solutions

Agent contextGraphRAGApplicationsOperational analyticsBlueprint library

Developers

DocsQuick startAPI referenceTypeScript SDKPython SDKMCP serverAgent skills

Resources

GuidesComparisonsBlogChangelogOpen sourceContactSelf-hosting

© 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

vector searchgraph databasehybrid retrieval

Vector Search Doesn't Understand Data Structure

Embeddings rank similarity but ignore joins, cardinality, and constraints. Learn how RushDB combines semantic retrieval with explicit graph relationships and live schema discovery.

21st July 2026—15 min read
data-pipelines
ai-architecture
graph-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

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
}