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
preferenceentries 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.
| Operation | How to Estimate Usage | Pricing Source |
|---|---|---|
| Read | Memory lookups per agent, workflow, or app session | Runtime pricing matrix |
| Write | New memories, updates, learning captures, and corrections | Runtime pricing matrix |
| Compact | Occasional memory-health optimization runs | Runtime pricing matrix |
| Reindex | Occasional semantic index maintenance runs | Runtime pricing matrix |
| Prune | Occasional cleanup of stale or low-confidence memory | Runtime 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:
-
Via API:
GET /v1/memory/entries/export?format=json&tags=project:acmeReturns paginated JSON entries.
-
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 Type | Latency |
|---|---|
| By ID | <10ms |
| By tag (single tag) | 20-50ms |
| By tag (multiple tags) | 50-150ms |
| Full-text search | 100-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:
- Use tags aggressively: Narrow queries down before running search
- Set low limits: Ask for 10 results, not 1,000
- Cache results: Store recent queries in-memory for 5 min
- 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:
-
Are entries actually created?
GET /v1/memory/entries?limit=100If results are empty, no entries exist yet.
-
Are tags correct?
GET /v1/memory/entries?tags=your_tag&limit=100If successful, query term is wrong. Go to #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:
- Semantic drift: New entries added with different vocabulary
- Embedding decay: Old embeddings became stale (rare)
- 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:
-
Check burn rate:
GET /v1/memory/statsIs
cost.burnRateabnormally high? -
Optimize writes:
- Don't write on every operation (batch writes)
- Delete low-value entries
- Use
PRUNEto remove stale data
-
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:
-
Add small delay before querying:
const entry = await memory.createEntry(...);
await sleep(100); // Wait for indexing
const results = await memory.queryEntries({ query: entry.title }); -
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:
-
Run during low-traffic windows:
- Schedule for 2-4 AM UTC
- Avoid during peak agent execution
-
Use dry-run first:
POST /v1/memory/compact?dryRun=trueShows estimated duration without blocking.
-
Run from low-priority queue:
- Operations have priority levels: CRITICAL, HIGH, NORMAL, LOW
- Schedule
COMPACTas 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 > 90%, can't write new entries.
Solution Sequence:
-
Run COMPACT:
POST /v1/memory/compact?dryRun=falseDeduplicates and consolidates. Saves 10-20% space.
-
Run PRUNE:
POST /v1/memory/prune?confidenceThreshold=0.6&dryRun=falseRemoves low-confidence entries. Saves 15-30% space.
-
If still high, EXPAND:
POST /v1/memory/expand?capacityIncrementMb=250&dryRun=falsePermanently 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 Type | Channel |
|---|---|
| API errors, rate limits | API Reference |
| Integration questions | Developer Guide |
| Operations & monitoring | Operational Playbook |
| Real-world patterns | Use Cases |
| Architecture questions | Architecture & Design |
| Billing & credits | /buy-credits or support@valkyrlabs.com |
Contact Support
- Email: support@valkyrlabs.com
- Slack: #valkyr-support (for customers)
- GitHub Issues: ValkyrLabs/ValkyrAI
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)