- Update version to 0.4.13 in all components - Regenerate OpenAPI spec and client SDKs - Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm, hindsight-embed - Python client: hindsight-clients/python - TypeScript client: hindsight-clients/typescript - Rust CLI: hindsight-cli - Control Plane: hindsight-control-plane - OpenClaw integration: hindsight-integrations/openclaw - AI SDK integration: hindsight-integrations/ai-sdk - Helm chart - Sync documentation to version-0.4
12 KiB
Ingest Data
Store documents, conversations, and raw content into Hindsight to automatically extract and create memories.
When you retain content, Hindsight doesn't just store the raw text—it intelligently analyzes the content to extract meaningful facts, identify entities, and build a connected knowledge graph. This process transforms unstructured information into structured, queryable memories.
{/* Import raw source files */}
:::info How Retain Works Learn about fact extraction, entity resolution, and graph construction in the Retain Architecture guide.
💡 Prerequisites
Make sure you've completed the Quick Start to install the client and start the server.
Store a Single Memory
Python
client.retain(
bank_id="my-bank",
content="Alice works at Google as a software engineer"
)
Node.js
await client.retain('my-bank', 'Alice works at Google as a software engineer');
CLI
hindsight memory retain my-bank "Alice works at Google as a software engineer"
The Importance of Context
The context parameter is crucial for guiding how Hindsight extracts memories from your content. Think of it as providing a lens through which the system interprets the information.
Why context matters:
- Steers memory extraction: Context tells the memory bank what type of information to focus on and how to interpret ambiguous content
- Improves relevance: Memories extracted with proper context are more accurately categorized and easier to retrieve
- Disambiguates meaning: The same sentence can have different implications depending on context (e.g., "the project was terminated" means different things in a career vs. product context)
Store with Context and Date
Always provide context and event dates for optimal memory extraction:
Python
client.retain(
bank_id="my-bank",
content="Alice got promoted to senior engineer",
context="career update",
timestamp="2024-03-15T10:00:00Z"
)
Node.js
await client.retain('my-bank', 'Alice got promoted to senior engineer', {
context: 'career update',
timestamp: '2024-03-15T10:00:00Z'
});
CLI
hindsight memory retain my-bank "Alice got promoted" \
--context "career update"
The timestamp defaults to the current time if not specified. Providing explicit timestamps enables temporal queries like "What happened last spring?"
Response Fields
The retain response includes:
| Field | Type | Description |
|---|---|---|
success |
bool | Whether the operation succeeded |
bank_id |
string | The memory bank ID |
items_count |
int | Number of items processed |
async |
bool | Whether processed asynchronously |
usage |
TokenUsage | Token usage metrics for LLM calls (synchronous only) |
The usage field contains token metrics for cost tracking:
input_tokens: Tokens consumed by promptsoutput_tokens: Tokens generated by the LLMtotal_tokens: Sum of input and output tokens
Note: usage is only present for synchronous operations. Async operations (async: true) do not return usage metrics.
Batch Ingestion
Store multiple items in a single request. Batch ingestion is the recommended approach as it significantly improves performance by reducing network overhead and allowing Hindsight to optimize the memory extraction process across related content.
Python
client.retain_batch(
bank_id="my-bank",
items=[
{"content": "Alice works at Google", "context": "career", "document_id": "conversation_001_msg_1"},
{"content": "Bob is a data scientist at Meta", "context": "career", "document_id": "conversation_001_msg_2"},
{"content": "Alice and Bob are friends", "context": "relationship", "document_id": "conversation_001_msg_3"}
]
)
Node.js
await client.retainBatch('my-bank', [
{ content: 'Alice works at Google', context: 'career', document_id: 'conversation_001_msg_1' },
{ content: 'Bob is a data scientist at Meta', context: 'career', document_id: 'conversation_001_msg_2' },
{ content: 'Alice and Bob are friends', context: 'relationship', document_id: 'conversation_001_msg_3' }
]);
The document_id groups related memories for later management.
Store from Files
Upload files directly — Hindsight automatically converts them to text and extracts memories. File processing always runs asynchronously and returns operation IDs for tracking.
Supported formats: PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, images (JPG, PNG, GIF, etc. — OCR), audio (MP3, WAV, FLAC, etc. — transcription), HTML, and plain text formats (TXT, MD, CSV, JSON, YAML, etc.)
CLI
# Upload a single file (PDF, DOCX, PPTX, XLSX, images, audio, and more)
hindsight memory retain-files my-bank report.pdf
# Upload a directory of files
hindsight memory retain-files my-bank ./documents/
# Upload and wait for processing to complete (polls until done)
hindsight memory retain-files my-bank report.pdf
# Queue files for background processing (returns immediately)
hindsight memory retain-files my-bank ./documents/ --async
HTTP
# Via HTTP API (multipart/form-data)
curl -X POST "${HINDSIGHT_URL}/v1/default/banks/my-bank/files/retain" \
-F "files=@report.pdf;type=application/octet-stream" \
-F "request={\"files_metadata\": [{\"context\": \"quarterly report\"}]}"
Python
# Upload files and retain their contents as memories.
# Supports: PDF, DOCX, PPTX, XLSX, images (OCR), audio (transcription), and text formats.
# Pass file paths — the client reads and uploads them automatically.
result = client.retain_files(
bank_id="my-bank",
files=[EXAMPLES_DIR / "sample.pdf"],
context="quarterly report",
)
print(result.operation_ids) # Track processing via the operations endpoint
Node.js
// Upload files and retain their contents as memories.
// Supports: PDF, DOCX, PPTX, XLSX, images (OCR), audio (transcription), and text formats.
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const pdfBytes = readFileSync(join(__dirname, 'sample.pdf'));
const result = await client.retainFiles('my-bank', [
new File([pdfBytes], 'sample.pdf'),
], { context: 'quarterly report' });
console.log(result.operation_ids); // Track processing via the operations endpoint
File Retain Response
The file retain endpoint always returns asynchronously:
| Field | Type | Description |
|---|---|---|
operation_ids |
string[] | One operation ID per uploaded file. Use GET /v1/default/banks/{bank_id}/operations to track progress. |
Batch File Uploads
Upload up to 10 files per request (max 100 MB total). Each file becomes a separate document with optional per-file metadata:
Python
# Upload multiple files with per-file metadata (up to 10 files per batch).
# Each file gets its own context, document_id, and tags.
result = client.retain_files(
bank_id="my-bank",
files=[
EXAMPLES_DIR / "sample.pdf",
EXAMPLES_DIR / "sample.pdf", # Replace with a second file path
],
files_metadata=[
{"context": "quarterly report", "document_id": "q1-report", "tags": ["project:alpha"]},
{"context": "meeting notes", "document_id": "q1-notes", "tags": ["project:alpha"]},
],
)
print(result.operation_ids) # One operation ID per file
:::info File Storage
Uploaded files are stored server-side (PostgreSQL by default, or S3/GCS/Azure for production). Configure storage via HINDSIGHT_API_FILE_STORAGE_TYPE. See Configuration for details.
Async Ingestion
For large batches, use async ingestion to avoid blocking:
Python
# Start async ingestion (returns immediately)
result = client.retain_batch(
bank_id="my-bank",
items=[
{"content": "Large batch item 1", "document_id": "large-doc-1"},
{"content": "Large batch item 2", "document_id": "large-doc-2"},
],
retain_async=True
)
# Check if it was processed asynchronously
print(result.var_async) # True
Node.js
// Start async ingestion (returns immediately)
await client.retainBatch('my-bank', [
{ content: 'Large batch item 1', document_id: 'large-doc-1' },
{ content: 'Large batch item 2', document_id: 'large-doc-2' },
], {
async: true
});
Cut Costs 50% with Provider Batch APIs
When using async retain, enable the provider Batch API to reduce LLM fact-extraction costs by 50%. OpenAI and Groq both offer this discount in exchange for a processing window of up to 24 hours — a trade-off that's typically invisible when retain already runs in the background.
export HINDSIGHT_API_RETAIN_BATCH_ENABLED=true
Hindsight submits fact extraction calls as a batch job to the provider, polls for completion, and processes results automatically. No changes to your API calls are needed.
:::note
Batch API cost savings require async=true in your retain request and a compatible provider (OpenAI or Groq).
Tagging Memories
Tags enable visibility scoping—useful when one memory bank serves multiple users but each should only see relevant memories. For example, an agent that chats with multiple users can tag memories by user ID and filter during recall.
Tag Individual Items
Python
# Tag individual items for visibility scoping
client.retain_batch(
bank_id="my-bank",
items=[
{
"content": "User Alice said she loves the new dashboard",
"tags": ["user:alice", "feedback"],
"document_id": "user_feedback_001"
},
{
"content": "User Bob reported a bug in the search feature",
"tags": ["user:bob", "bug-report"],
"document_id": "user_feedback_002"
}
]
)
Apply Tags to All Items in a Batch
Use document_tags to apply the same tags to all items in a request:
Python
# Apply tags to all items in a batch
client.retain_batch(
bank_id="my-bank",
items=[
{"content": "Alice mentioned she prefers dark mode", "document_id": "support_session_123_msg_1"},
{"content": "Bob asked about keyboard shortcuts", "document_id": "support_session_123_msg_2"}
],
document_tags=["session:123", "support"] # Applied to all items
)
When both document_tags and item-level tags are provided, they are merged together.
Tag Naming Conventions
Use consistent naming patterns for tags:
| Pattern | Example | Use Case |
|---|---|---|
user:<id> |
user:alice |
Multi-user agent filtering |
session:<id> |
session:123 |
Session-based scoping |
room:<id> |
room:general |
Chat room isolation |
topic:<name> |
topic:feedback |
Topic categorization |
Listing Tags
Use the list tags API to discover existing tags, useful for UI autocomplete or wildcard expansion:
Python
# List all tags in a bank
response = requests.get(f"{HINDSIGHT_URL}/v1/default/banks/my-bank/tags")
tags = response.json()
for tag in tags["items"]:
print(f"{tag['tag']}: {tag['count']} memories")
# Search with wildcards (* matches any characters)
response = requests.get(f"{HINDSIGHT_URL}/v1/default/banks/my-bank/tags", params={"q": "user:*"})
user_tags = response.json()
response = requests.get(f"{HINDSIGHT_URL}/v1/default/banks/my-bank/tags", params={"q": "*-admin"})
admin_tags = response.json()
See Recall API for filtering memories by tags during retrieval.