Skip to main content

GrayMatter FAQ & Troubleshooting

Frequently Asked Questions

General Questions

Q: Is GrayMatter a vector database?

A: No. GrayMatter is a relational database with semantic search capabilities.

  • Vector databases store only embeddings and rely on approximate similarity matching.
  • GrayMatter stores structured objects (type, title, text, tags, metadata) in PostgreSQL, with optional embeddings for semantic search.

This distinction matters:

  • Structured queries work (e.g., "all preference entries with tag=payment")
  • Updates are transactional and auditable
  • Access control is granular
  • You don't lose the original text when storing vectors

Q: How much does GrayMatter cost?

A: GrayMatter uses a dynamic pay-per-operation model via prepaid credits. Current plan details, credit bundles, add-ons, and the credit pricing matrix live on the main site Pricing page. Treat the table below as planning guidance only, not a price sheet.

OperationHow to Estimate UsagePricing Source
ReadMemory lookups per agent, workflow, or app sessionRuntime pricing matrix
WriteNew memories, updates, learning captures, and correctionsRuntime pricing matrix
CompactOccasional memory-health optimization runsRuntime pricing matrix
ReindexOccasional semantic index maintenance runsRuntime pricing matrix
PruneOccasional cleanup of stale or low-confidence memoryRuntime pricing matrix

Example estimate: A customer with 1 agent doing 30 memory queries/day and 10 memory writes/day should plan for roughly 1,200 read operations and 300 write operations per month, before maintenance operations such as compact, reindex, or prune. Actual credits and dollar conversion depend on the current pricing matrix and subscription terms shown on Pricing.

Important: Credits are indivisible units. Credit purchase exchange rates and operation pricing are runtime-configured; all credit displays use whole numbers only, never decimals. A balance of "2,700 credits" should never be shown as "$27.00 credits" or "2,700.50 credits".


Q: Can I export memory entries?

A: Yes. Two options:

  1. Via API:

    GET /v1/memory/entries/export?format=json&tags=project:acme

    Returns paginated JSON entries.

  2. Via dashboard: Click "Export" on /graymatter-memory → downloads all visible entries as CSV or JSON.


Q: What happens if I delete an entry?

A: Soft delete (marked as retired, not physically removed):

  • Entry is no longer returned by queries
  • Audit trail preserved (shows who deleted it, when)
  • Can be restored by engineering if needed within 30 days
  • Physical deletion after 30 days

Q: Can multiple agents share memory?

A: Yes. Use tags to control access:

GET /v1/memory/entries?tags=shared,project:acme

Or use ACLs for fine-grained permissions (read/write/delete by principal).


Integration Questions

Q: How do I integrate GrayMatter with my ValkyrAI workflow?

A: Use the memory_read and memory_write modules:

tasks:
- id: "fetch_context"
module: "memory_read"
config:
query: "{{input.topic}}"
tags: ["instruction"]
limit: 5
outputs:
- context

- id: "store_result"
module: "memory_write"
config:
type: "context"
title: "Execution result"
text: "{{output}}"
tags: ["result", "{{date}}"]

See Developer Guide for more examples.


Q: Can I use GrayMatter from Python?

A: Yes. Use the authenticated HTTP API directly:

import os
import requests

base_url = os.environ.get("VALKYRAI_API_URL", "http://localhost:8080/v1")
headers = {"Authorization": f"Bearer {os.environ['JWT_TOKEN']}"}

# Query
results = requests.get(
f"{base_url}/memory/entries",
headers=headers,
params={"query": "payment", "tags": "stripe", "limit": 10},
).json()

# Create
entry = requests.post(
f"{base_url}/memory/entries",
headers=headers,
json={
"type": "instruction",
"text": "Always use idempotency keys",
"tags": ["payment", "best-practice"],
},
).json()

Q: Does GrayMatter support batch operations?

A: Partially:

  • Batch reads: No (use single query with loose criteria)
  • Batch writes: No (write one at a time; use tags to group)
  • Batch delete: No (soft-delete entries individually)

Why? Prevents accidental data loss. We recommend batching at the application level (write N entries in a loop).


Performance & Scaling

Q: How fast are memory queries?

A: Latency depends on query complexity:

Query TypeLatency
By ID<10ms
By tag (single tag)20-50ms
By tag (multiple tags)50-150ms
Full-text search100-500ms
Semantic search (embedding)200-800ms

Caching on client reduces subsequent queries to <5ms.


Q: What's the maximum number of entries I can store?

A: PostgreSQL supports billions of rows. In practice:

  • Small account (1-2 agents): 10K-100K entries
  • Medium account (5-10 agents): 100K-1M entries
  • Large account (50+ agents): 1M-10M entries

Run COMPACT and PRUNE regularly to keep growth manageable.


Q: How do I optimize memory operations for speed?

A:

  1. Use tags aggressively: Narrow queries down before running search
  2. Set low limits: Ask for 10 results, not 1,000
  3. Cache results: Store recent queries in-memory for 5 min
  4. Batch reads: One query for 10 entries beats 10 queries for 1 entry

Example:

// ❌ Slow: 5 separate API calls
for (let i = 0; i < 5; i++) {
await memory.query({ query: topic[i], limit: 1 });
}

// ✅ Fast: 1 API call with OR logic
await memory.query({
query: "topic1 OR topic2 OR topic3 OR topic4 OR topic5",
limit: 10,
});

Troubleshooting

Problem: Queries Returning Empty Results

Symptoms: GET /v1/memory/entries?query=X returns 0 results, but entries exist.

Diagnosis:

  1. Are entries actually created?

    GET /v1/memory/entries?limit=100

    If results are empty, no entries exist yet.

  2. Are tags correct?

    GET /v1/memory/entries?tags=your_tag&limit=100

    If successful, query term is wrong. Go to #3.

  3. Is query term matching?

    • Queries use substring matching (case-insensitive).
    • "payment" matches "Payment Handler", "stripe payments", etc.
    • But "paymentX" doesn't match "payment".

Solution:

# Broad query to debug
GET /v1/memory/entries?query=a&limit=100

# Then narrow down step by step
GET /v1/memory/entries?query=pay&limit=100
GET /v1/memory/entries?query=payment&limit=100

Problem: Hit Rate Dropping Over Time

Symptoms: Yesterday's queries returned relevant results. Today, same query returns irrelevant entries.

Root Causes:

  1. Semantic drift: New entries added with different vocabulary
  2. Embedding decay: Old embeddings became stale (rare)
  3. Memory pollution: Too many low-signal entries accumulated

Diagnosis:

GET /v1/memory/stats

Check memory.hitRate. If <0.80:

Solution:

# Option 1: Reindex (improves retrieval quality)
POST /v1/memory/reindex?strategy=FULL&dryRun=false

# Credit charge: runtime configured; see /pricing
# Duration: 15-30 min
# Expected improvement: +3-8% hit rate

Problem: Out of Credits (402 Payment Required)

Symptoms: Operations fail with 402 Payment Required.

Immediate fix:

POST /v1/credits/{accountId}/payment \
-d '{"credits": 1000, "paymentMethod": "card"}'

Long-term fix:

  1. Check burn rate:

    GET /v1/memory/stats

    Is cost.burnRate abnormally high?

  2. Optimize writes:

    • Don't write on every operation (batch writes)
    • Delete low-value entries
    • Use PRUNE to remove stale data
  3. Top up credits:

    GET /buy-credits

Problem: Entries Not Being Found After Update

Symptoms: Created entry, then immediately queried for it. Query returns 0 results.

Root Cause: Indexing latency. Newly created entries take 2-5 seconds to become queryable.

Solution:

  1. Add small delay before querying:

    const entry = await memory.createEntry(...);
    await sleep(100); // Wait for indexing
    const results = await memory.queryEntries({ query: entry.title });
  2. Query by ID instead (no indexing delay):

    const entry = await memory.createEntry(...);
    const result = await memory.getEntry(entry.id); // Instant

Problem: Compact or Reindex Takes Too Long

Symptoms: Operations are hanging or taking >30 min.

Root Cause: Large number of entries (>100K) or heavy write load competing.

Solution:

  1. Run during low-traffic windows:

    • Schedule for 2-4 AM UTC
    • Avoid during peak agent execution
  2. Use dry-run first:

    POST /v1/memory/compact?dryRun=true

    Shows estimated duration without blocking.

  3. Run from low-priority queue:

    • Operations have priority levels: CRITICAL, HIGH, NORMAL, LOW
    • Schedule COMPACT as LOW to avoid starving reads

Problem: Agents Repeatedly Making Same Mistakes

Symptoms: Agent fails on Task X, but doesn't learn. Fails again next run.

Root Cause: Agent not storing failure patterns or reading past learnings.

Solution: Implement self-improvement loop (see Use Cases):

// Before task: read past failures
const failurePatterns = await memory.query({
tags: [`task:${taskType}`, "failure"],
limit: 5,
});

// Execute with failure avoidance context
const result = await executeTask(task, { failurePatterns });

// After task: store learnings
if (!result.success) {
await memory.createEntry({
type: "decision",
text: `Failed: ${result.error}. Instead, try: ${result.suggestion}`,
tags: [`task:${taskType}`, "failure"],
});
}

Problem: Memory Taking Up Too Much Storage

Symptoms: Dashboard shows usedPct &gt; 90%, can't write new entries.

Solution Sequence:

  1. Run COMPACT:

    POST /v1/memory/compact?dryRun=false

    Deduplicates and consolidates. Saves 10-20% space.

  2. Run PRUNE:

    POST /v1/memory/prune?confidenceThreshold=0.6&dryRun=false

    Removes low-confidence entries. Saves 15-30% space.

  3. If still high, EXPAND:

    POST /v1/memory/expand?capacityIncrementMb=250&dryRun=false

    Permanently increases capacity. Credit charge is runtime configured.


Problem: Inconsistent Responses from Multi-Agent System

Symptoms: Agent A uses Memory X to make decision D. Agent B reads same Memory X but makes different decision D'.

Root Cause: Agents interpreting memory text differently, or memory is ambiguous.

Solution:

Use structured metadata for precise interpretation:

// ❌ Ambiguous
await memory.createEntry({
text: "Always retry on failure",
});

// ✅ Clear with metadata
await memory.createEntry({
text: "Always retry on failure with exponential backoff",
metadata: {
maxRetries: 3,
backoffMs: 100,
backoffMultiplier: 2,
ignoreCodes: [400, 401, 403], // Don't retry auth errors
},
});

Agents can now parse metadata deterministically:

const rule = await memory.getEntry(ruleId);
const backoff = (attempt) =>
rule.metadata.backoffMs * Math.pow(rule.metadata.backoffMultiplier, attempt);

Getting Help

Where to Get Support

Issue TypeChannel
API errors, rate limitsAPI Reference
Integration questionsDeveloper Guide
Operations & monitoringOperational Playbook
Real-world patternsUse Cases
Architecture questionsArchitecture & Design
Billing & credits/buy-credits or support@valkyrlabs.com

Contact Support


Advanced Topics

Custom Query Syntax

GrayMatter supports enhanced query syntax beyond substring matching:

# OR logic
GET /v1/memory/entries?query=payment%20OR%20stripe

# Phrase matching
GET /v1/memory/entries?query=%22exponential%20backoff%22

# Negation
GET /v1/memory/entries?query=retry%20-payment

# Combination
GET /v1/memory/entries?query=(retry%20OR%20timeout)%20-firebase

Working with Embeddings

If using semantic search:

# Query by embedding distance
POST /v1/memory/search \
-d '{
"embedding": [0.1, 0.2, ..., 0.8], // 1536-dim vector
"topK": 10,
"threshold": 0.8 // Cosine similarity
}'

Embeddings are kept in sync automatically via scheduled jobs.


Handling Sensitive Data

For secrets, API keys, PII:

// Mark field as encrypted
const entry = await memory.createEntry({
type: "preference",
title: "Stripe API Key Storage",
text: "API_KEY: sk_live_xyz", // Will be encrypted
secure: true, // Optional explicit flag
metadata: {
dataClassification: "SENSITIVE",
encryptionKey: "stripe-api-v1",
},
});

// Decryption is transparent in-memory
const decrypted = await memory.getEntry(entry.id);
console.log(decrypted.text); // "API_KEY: sk_live_xyz" (decrypted on load)