memcity

Getting Started

Getting Started

Install Memcity and start adding AI memory to your Convex application in under 5 minutes.

What is Memcity?

Imagine your AI application has amnesia. Every time a user asks a question, it starts from scratch — no memory of past conversations, no knowledge of your company's documents, no understanding of how concepts in your data connect to each other.

Memcity fixes that. It's an AI memory component for Convex that gives your application three kinds of memory:

  • Knowledge memory — Ingest your documents (PDFs, markdown, web pages, images, audio, video) and make them instantly searchable with a production-grade RAG pipeline. When a user asks "what's our refund policy?", Memcity finds the answer in your docs.
  • Graph memory — Automatically extract entities (people, companies, products, concepts) and their relationships from your documents. When a user asks about "Acme Corp's CEO", Memcity connects the dots across multiple documents.
  • Episodic memory — Remember things about individual users. Their preferences, past questions, and conversation history. A returning user doesn't have to re-explain themselves.

Think of it like giving your app a brain — one that reads your documents, understands how things connect, and remembers each user.

What Problems Does It Solve?

Without MemcityWith Memcity
Your chatbot can't answer questions about your own docsYour docs are searchable via natural language
Users have to re-explain context every sessionThe app remembers past conversations and preferences
Search only works with exact keyword matchesSemantic search understands meaning ("car" matches "automobile")
Related concepts across documents are invisibleKnowledge graph connects entities across all your content
No audit trail of what your AI accessedEvery search and ingestion is logged for compliance

Prerequisites

Before you start, make sure you have:

  1. A Convex project — If you don't have one yet, run npm create convex@latest to scaffold one. Memcity runs entirely on Convex's backend.
  2. Node.js 18+ — Required for the Convex CLI and development tools.
  3. A Jina API key — Memcity uses Jina AI for generating embeddings (turning text into searchable vectors) and reranking results. Get a free key at jina.ai.
  4. An OpenRouter API key (Pro+ tiers) — Memcity uses OpenRouter as a gateway to access language models for query routing, entity extraction, and other AI features. Get a key at openrouter.ai.

Installation

If you use shadcn/ui, you can install Memcity components directly into your project. This gives you the source code — you own it and can customize it.

Step 1: Add the Memcity registry to your components.json:

json
{
  "registries": {
    "@memcity": {
      "url": "https://memcity.dev/r/{name}.json",
      "headers": {
        "X-License-Key": "${MEMCITY_LICENSE_KEY}"
      }
    }
  }
}

Step 2: Install the component you need:

bash
# Community (free) — basic vector search
npx shadcn@latest add @memcity/memory-search
 
# Pro ($79) — full 16-step RAG pipeline
npx shadcn@latest add @memcity/rag-pipeline
 
# Pro — multi-file upload with processing
npx shadcn@latest add @memcity/file-uploader

Registry installs scaffold Convex files directly:

  • convex/memcity/* for installed Memcity blocks
  • convex/memory.ts for a default Memory client
  • convex/memcity/convex.config.snippet.ts as the registration snippet

Option B: Direct Download

Download the package zip for your tier from memcity.dev:

bash
# Community (free) — no license key needed
curl -O https://memcity.dev/download?tier=community
 
# Pro/Team — requires your license key
curl -O "https://memcity.dev/download?key=MEMCITY_PRO_xxxx-xxxx-xxxx"

Then extract and add to your project:

bash
unzip memcity-community.zip -d packages/memcity

Setup

Step 1: Register the Component

Convex uses a component system — think of components as self-contained backend modules with their own database tables, functions, and logic. Memcity is a Convex component that you "install" by registering it in your app's configuration.

Open (or create) convex/convex.config.ts:

ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import memcity from "memcity/convex.config.js";
 
const app = defineApp();
 
// This line installs Memcity's tables, indexes, and functions
// into your Convex deployment. It runs alongside your own code.
app.use(memcity);
 
export default app;

Step 2: Set Environment Variables

Memcity needs API keys to access external AI services. Set them in your Convex deployment:

bash
# Required for ALL tiers — powers embeddings and reranking
# Jina v4 turns your text into 1024-dimensional vectors for search
# Jina Reranker v3 re-scores results for higher precision
npx convex env set JINA_API_KEY your-jina-api-key-here
 
# Required for Pro and Team tiers — powers LLM reasoning
# Used for query routing, entity extraction, HyDE generation,
# and other AI features that need language understanding
npx convex env set OPENROUTER_API_KEY your-openrouter-key-here

What do these keys actually do?

  • Jina API key: When you ingest a document, Memcity breaks it into chunks and sends each chunk to Jina to generate an "embedding" — a list of 1,024 numbers that capture the meaning of the text. When you search, your query gets the same treatment, and Memcity finds chunks whose numbers are similar to your query's numbers. The reranker is a second-pass model that re-scores the top results for better precision.

  • OpenRouter API key: This gives Memcity access to language models (like Gemini, GPT, Claude) through a single gateway. The LLM is used for smart features like understanding whether a query is simple or complex, extracting entities from text, and generating hypothetical answers to improve search quality.

Step 3: Initialize the Memory Class

In your Convex backend code, create a Memory instance. This is the main entry point for all Memcity operations:

ts
// convex/myFunctions.ts
import { Memory } from "memcity";
import { components } from "./_generated/api";
 
// Create a Memory instance with your desired configuration
const memory = new Memory(components.memcity, {
  // Which tier you're on — controls which features are available
  tier: "pro", // "community" | "pro" | "team"
 
  // AI model configuration
  ai: {
    // "openrouter" routes through OpenRouter (recommended)
    // "vercel" routes through Vercel AI Gateway
    gateway: "openrouter",
 
    // The model used for reasoning tasks
    // (query routing, entity extraction, etc.)
    // This is NOT the embedding model — embeddings always use Jina v4
    model: "google/gemini-2.0-flash-001",
  },
 
  // Search behavior — sensible defaults, tune later
  search: {
    maxResults: 10,        // How many results to return
    minScore: 0.1,         // Minimum relevance score (0-1)
    weights: {
      semantic: 0.7,       // Weight for meaning-based search
      bm25: 0.3,           // Weight for keyword-based search
    },
  },
});

Your First Search: A Complete Example

Let's walk through a complete example — ingest a document and search it:

ts
// convex/example.ts
import { action } from "./_generated/server";
import { v } from "convex/values";
import { Memory } from "memcity";
import { components } from "./_generated/api";
 
const memory = new Memory(components.memcity, {
  tier: "pro",
  ai: {
    gateway: "openrouter",
    model: "google/gemini-2.0-flash-001",
  },
});
 
// Ingest a document into a knowledge base
export const ingestDocument = action({
  args: {},
  handler: async (ctx) => {
    // Create an organization (top-level container)
    const orgId = await memory.createOrg(ctx, {
      name: "My Company",
    });
 
    // Create a knowledge base (a collection of related documents)
    const kbId = await memory.createKnowledgeBase(ctx, {
      orgId,
      name: "Company Policies",
      description: "HR policies, handbooks, and procedures",
    });
 
    // Ingest a document — Memcity will automatically:
    // 1. Split it into chunks (~512 tokens each)
    // 2. Generate vector embeddings for each chunk
    // 3. Index chunks for semantic and keyword search
    // 4. Extract entities and relationships for the knowledge graph
    await memory.ingestText(ctx, {
      orgId,
      knowledgeBaseId: kbId,
      text: `
        # Vacation Policy
 
        All full-time employees are entitled to 20 days of paid
        vacation per year. Vacation days do not roll over.
 
        ## Requesting Time Off
 
        Submit vacation requests at least 2 weeks in advance
        through the HR portal. Your manager must approve the
        request within 3 business days.
 
        ## Sick Leave
 
        Sick leave is separate from vacation. You get 10 sick
        days per year. A doctor's note is required for absences
        longer than 3 consecutive days.
      `,
      source: "vacation-policy.md",
    });
 
    return { orgId, kbId };
  },
});
 
// Search the knowledge base
export const searchDocs = action({
  args: {
    query: v.string(),
    orgId: v.string(),
    knowledgeBaseId: v.string(),
  },
  handler: async (ctx, args) => {
    const results = await memory.getContext(ctx, {
      orgId: args.orgId,
      knowledgeBaseId: args.knowledgeBaseId,
      query: args.query,
    });
 
    // results.results is an array of matching chunks:
    // - text: the matching text content
    // - score: relevance score (0-1)
    // - confidence: how confident Memcity is in this result
    // - citations: page/line/heading breadcrumbs
    // - source: which document this came from
    return results;
  },
});

Now from your frontend:

ts
// In your React component
const { orgId, kbId } = await ingestDocument();
 
// Ask a natural language question
const results = await searchDocs({
  query: "How many vacation days do I get?",
  orgId,
  knowledgeBaseId: kbId,
});
 
// results.results[0].text →
//   "All full-time employees are entitled to 20 days..."
// results.results[0].score → 0.92
// results.results[0].confidence → 0.87

What Just Happened Behind the Scenes?

When you called ingestText, Memcity ran a multi-step ingestion pipeline:

  1. Chunking — The vacation policy was split into overlapping chunks of ~512 tokens. Each chunk is small enough to be meaningful but has enough context to be useful.

  2. Embedding — Each chunk was sent to Jina v4, which converted the text into a 1,024-dimensional vector — a list of 1,024 numbers that capture the meaning of the text. Similar concepts get similar numbers.

  3. Indexing — The vectors were stored in Convex's vector index for fast similarity search. The raw text was also indexed for BM25 keyword search.

  4. Entity Extraction (Pro+) — The LLM identified entities ("vacation", "sick leave", "HR portal") and relationships ("vacation policy" → "requires" → "manager approval").

When you called getContext, Memcity ran the full 16-step search pipeline:

  1. Your query was embedded into a vector
  2. That vector was compared against all chunk vectors (semantic search)
  3. The same query was matched using keywords (BM25 search)
  4. Results from both were merged using Reciprocal Rank Fusion
  5. A reranker re-scored the top results for precision
  6. The best results were returned with scores and citations

The whole process typically takes 200-800ms depending on your tier and which pipeline steps are enabled.

Troubleshooting

ProblemCauseFix
JINA_API_KEY not setMissing environment variablenpx convex env set JINA_API_KEY your-key
OPENROUTER_API_KEY not setMissing env var (Pro+)npx convex env set OPENROUTER_API_KEY your-key
Tier cannot use this featureUsing Pro/Team features on CommunityUpgrade your tier or disable gated options
Knowledge base limit reachedCommunity allows only 1 KBUpgrade to Pro for unlimited KBs
Empty search resultsDocuments still indexingWait a few seconds after ingestion
Low relevance scoresDefault config not tunedSee Configuration

Next Steps