Back to blog
Embedded Graph Database for AI Agents: In-Process Setup

This blog is written by AI for SEO

Embedded Graph Database for AI Agents: In-Process Setup

HelixDB9 min read

A 30-millisecond HTTP round trip to fetch memory wrecks an autonomous coding agent's execution loop. The agent evaluates tool outputs, tracks conversational state, and checks historical context, which means dozens of memory queries per decision cycle. At that frequency, network overhead dominates your execution time.

Developers building desktop assistants, CLI tools, and local-first agent loops often pick SQLite or Kuzu to stay in-process. SQLite handles relational tables and basic key-value data, but multi-hop graph traversal is awkward. Traditional embedded graph stores handle relationships but have no native vector search alongside graph topology. HelixDB combines graph traversal, approximate vector search, and BM25 full-text search in a single engine built in Rust.

This guide shows how to run HelixDB as an embedded graph database for AI agents directly inside your TypeScript process. You will set up an in-memory instance, configure persistent disk storage, write graph-vector memory nodes, manage the single-writer constraint, and plan for scale.

Step 1: Decide whether embedding fits your agent (no network hop, local-first, tests, CLI tools)

Running your database in-process removes network serialization and socket latency. Say your agent traverses a four-step dependency graph and pre-filters vectors across related decisions. An embedded engine runs that whole sequence inside the host process memory space in sub-millisecond time.

Local-first software needs zero background daemons. If you ship a developer tool, an on-device personal assistant, or a command-line agent, you can't make users run Docker or maintain a separate database server. Embedding the database keeps your agent packaged as a self-contained executable or a clean npm dependency. For testing, an in-memory embedded engine lets your suite spin up isolated state in parallel and tear it down immediately. No port conflicts, no leftover test artifacts.

Embedding doesn't fit every architecture. If you run a distributed swarm where twenty stateless workers write agent memories to the same store at once, an embedded database will fail. Embedded storage engines enforce a single-writer model per storage directory. If your agent runs on a single host, operates as a CLI tool, or processes tasks inside one worker process, embedding gives you the fastest retrieval with the least operational overhead. Devs who want to give AI agents persistent memory find that starting in-process cuts architectural complexity a lot.

Step 2: Install the SDK and open an in-memory embedded client

HelixDB ships native TypeScript bindings through the @helix-db/helix-db package. The core engine is written in Rust and compiled for your platform, so Node.js and Bun runtimes talk directly to the embedded storage engine.

Install the SDK with your package manager:

npm install @helix-db/helix-db

For test suites and transient agent loops, an in-memory instance is the fastest way to start. Data lives only as long as the process, so nothing is left on the host filesystem when your script exits.

Here is how to initialize an in-memory embedded client in TypeScript:

import { Client } from "@helix-db/helix-db";

async function runEphemeralAgent() {
  // Open an embedded client in pure memory mode
  const db = await Client.embedded({
    mode: "memory",
  });

  try {
    console.log("Embedded in-memory HelixDB instance ready.");
    // Agent execution logic goes here
  } finally {
    await db.close();
  }
}

runEphemeralAgent();

Calling Client.embedded({ mode: "memory" }) allocates internal buffers directly in the process heap and never touches the file system. In CI runs, you can spin up dozens of isolated database instances per second without mocking your memory layer.

Step 3: Persist to disk with a root directory and tune the vector cache profile

Production agents need durable state across process restarts. HelixDB uses SlateDB as its underlying storage engine and organizes records, property graphs, and vector indexes inside an on-disk root directory.

To persist state, configure the embedded client with a valid path. HelixDB creates the directory structure if it doesn't exist and mounts the storage engine to that folder.

import { Client } from "@helix-db/helix-db";
import * as path from "node:path";

async function initializePersistentStore() {
  const storagePath = path.resolve(process.cwd(), ".agent_memory");

  const db = await Client.embedded({
    mode: "disk",
    path: storagePath,
    vectorCache: {
      maxCachedVectors: 50_000,
      preloadIndex: true,
    },
  });

  return db;
}

Vectors in HelixDB are first-class properties stored directly on nodes and edges. Vector indexes support cosine, Euclidean, or Manhattan distance, and example vectors use float32 values.

Step 4: Write and query agent memory with the TypeScript query builder, closing the client in try/finally

An agent needs more than flat vector recall. Real memory means understanding relationships between tasks, tools, and execution artifacts. HelixDB uses JSON queries under the hood, and the TypeScript SDK has a native query builder, so you can write and query graphs and vectors without learning a separate query language.

Always wrap database operations in a try/finally block. If an unhandled exception interrupts execution, the finally block guarantees that db.close() runs, which releases file locks and flushes unwritten buffers.

import {
  Client,
  writeBatch,
  readBatch,
  g,
  VectorDistanceMetric,
} from "@helix-db/helix-db";

async function manageAgentMemory(db: Client) {
  try {
    const embeddingVector = [0.12, -0.45, 0.88, /* ...1536 float values */];

    // Write entities and relationships in an atomic transaction
    const insertTx = writeBatch([
      {
        action: "create_node",
        label: "Task",
        properties: {
          id: "task_101",
          title: "Scrape competitor pricing",
          status: "completed",
          embedding: embeddingVector,
        },
      },
      {
        action: "create_node",
        label: "Artifact",
        properties: {
          id: "art_505",
          filename: "pricing_summary.csv",
          created_at: 1718002000,
        },
      },
      {
        action: "create_edge",
        label: "PRODUCED",
        from: "task_101",
        to: "art_505",
        properties: { step: 3 },
      },
    ]);
    await db.query(insertTx);

    // Query: Traverse graph and run vector pre-filtering
    const query = readBatch([
      g.nodes("Task")
        .has("status", "completed")
        .outE("PRODUCED")
        .inV()
        .hasVectorSimilarity({
          property: "embedding",
          vector: embeddingVector,
          metric: VectorDistanceMetric.Cosine,
          limit: 10,
        })
        .select(),
    ]);

    const results = await db.query(query);
    return results;
  } finally {
    await db.close();
  }
}

Vector pre-filtering in HelixDB restricts similarity search to a candidate set determined by graph traversal. Unrestricted vector searches are hard-capped at 800 effective results, and candidate streams over 1,000,000 entities produce a query error. Atomicity means graph structure and vector indexes commit together, so you never get orphaned vector references.

Step 5: Handle the one-writer rule with a read-only embedded reader for a second process

HelixDB relies on SlateDB for local storage, and the on-disk format enforces a strict one-writer rule. Only one process can hold the write lock on the root directory at a time. If a second process opens the same path in read-write mode, the storage engine rejects the lock and throws a file lock error.

A common agent architecture splits the work: one primary process plans and executes tasks, while a background observer reads agent state to update a user dashboard. If you pick HelixDB over other embedded engines, you have to design around this lock pattern.

To let a secondary process read the database without colliding with the primary agent, open the client in read-only mode:

import { Client } from "@helix-db/helix-db";

async function openMonitoringReader(storagePath: string) {
  // Open the store without acquiring an exclusive write lock
  const reader = await Client.embedded({
    mode: "disk",
    path: storagePath,
    readOnly: true,
  });

  return reader;
}

A read-only client maps the underlying storage tables directly and skips lock contention. The reader sees committed state from the writer process. If your architecture needs multiple concurrent writers on the same machine, don't share an on-disk root directory between Node.js runtimes. Route mutations through an IPC channel to your primary writer process, or switch to a client-server setup. Devs comparing embedded stores often read HelixDB vs LadybugDB to see how different engines handle process isolation and concurrency.

Step 6: Plan for the tradeoffs: single-node capacity and backups of the on-disk root

Embedding your database buys big speed gains, but it comes with operational constraints. The first is vertical capacity. An embedded database can't scale out beyond the local host, so your capacity is bounded by the disk space and RAM on the machine running your agent.

Backups need care. You can't run a naive recursive file copy (cp -r) on an active on-disk root directory while the writer process executes transactions. You risk capturing torn pages or inconsistent SST files, and the backup will be corrupted on recovery.

Follow this procedure for safe backups:

  1. Quiesce the writer process. Make sure no writeBatch transactions are running in your agent loop.

  2. Ensure all in-flight transactions are committed before taking a snapshot:

await db.flush();
  1. Snapshot the directory. On Linux or macOS, use a filesystem-level snapshot (such as ZFS or APFS snapshots), or archive the directory into a compressed tarball while writes stay paused.

  2. Resume agent operations.

If your agent handles continuous high-throughput mutations and pausing is impossible, the embedded setup will eventually box you in. At that point, move your memory store to an external server process.

Step 7: Move to a local server or Cloud when you outgrow one process (helix init, helix start dev, Client.server)

Once your architecture grows from a single CLI tool to a cluster of worker processes, running in-process stops working. The good news: leaving embedded mode doesn't mean rewriting your agent memory queries or rebuilding your schemas. HelixDB uses the same data model, JSON query format, and TypeScript SDK whether the engine is embedded or running over the network.

You can move to a local server container with the official Helix CLI:

# Initialize a project configuration
helix init

# Start a local development server backed by disk or memory
helix start dev

This starts the database engine on your machine. To point your existing agent application at the server, update the client initialization code:

import { Client } from "@helix-db/helix-db";

// Connect to a local or remote HelixDB server instance
const db = Client.server({
  endpoint: "http://localhost:8080",
  apiKey: process.env.HELIX_API_KEY,
});

Your existing readBatch and writeBatch queries run the same way against the server. If your storage needs exceed local NVMe drives, self-hosted deployments can run against S3-compatible object storage. For managed production systems, HelixDB Cloud offers an object-storage-backed service where a single writer serializes mutations and reader nodes scale horizontally with serializable snapshot-isolation ACID transactions. If you need to migrate from older setups, see our Kuzu migration guide.

What to do next and troubleshooting

When you develop with an embedded graph database for AI agents, watch for these common issues:

  1. File lock collisions: If you see a file lock error, check that a previous run didn't crash without calling db.close(). Verify that background processes pass readOnly: true.

  2. Vector normalization: HelixDB normalizes vectors to float32. Passing float64 values straight from JavaScript arrays without proper dimensional alignment can trigger validation errors. Always check that your embedding dimension matches your index configuration.

  3. Candidate stream limits: Queries that traverse large unbounded graphs can hit the 1,000,000 unique entity limit. Apply property predicates early in your traversal to keep candidate sets small.

Now that you have an embedded instance running, clone the open-source repository on GitHub (github.com/HelixDB/helix-db), star the project, and test your agent workflows. Try modeling complex agent execution traces, with BM25 text indexes on tool documentation and vector similarity on user goals.

Conclusion

Embedding your graph-vector database inside your agent process removes network latency and external server dependencies for local-first systems. HelixDB is a single engine written in Rust that gives your agent relationship awareness, vector search, and BM25 full-text retrieval in one package.

Stop stitching together separate SQLite files and vector stores with fragile glue code. Check out the open-source HelixDB repository on GitHub, star the project, and test the embedded TypeScript client inside your agent today.

Build with HelixDB

Give your coding agent the setup prompt, or sign up and deploy a database.

Sign up