Retain: Storing Memories
Examples on this page use the Hindsight Cloud endpoints (https://api.hindsight.vectorize.io, https://ui.hindsight.vectorize.io). If you use Hindsight Cloud with Enterprise features, they work as written. On a dedicated deployment, use your deployment's API and console URLs instead; see Accessing your deployment.
The Retain operation stores new information in a memory bank. Hindsight processes the content, extracts structured memories, and indexes them for efficient retrieval.
Overview
Retain is the primary way to add information to Hindsight:
- Process natural language content
- Extract and categorize memories automatically
- Store world facts, experience, and observations
- Build a searchable knowledge base indexed for TEMPR retrieval
Basic Usage
- Python
- TypeScript
- cURL
from hindsight_client import Hindsight
client = Hindsight(
base_url="https://api.hindsight.vectorize.io",
api_key="your-api-key"
)
# Simple retain
client.retain(
bank_id="your-bank-id",
content="The user's favorite color is blue and they work as a software engineer."
)
import { HindsightClient } from '@vectorize-io/hindsight-client';
const client = new HindsightClient({
baseUrl: 'https://api.hindsight.vectorize.io',
apiKey: 'your-api-key'
});
// Simple retain
await client.retain(
'your-bank-id',
"The user's favorite color is blue and they work as a software engineer."
);
curl -X POST https://api.hindsight.vectorize.io/v1/default/banks/{bank_id}/memories \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"content": "The user'\''s favorite color is blue and they work as a software engineer."
}
]
}'
How It Works
When you call Retain:
- Content Analysis - Hindsight processes your text using AI
- Memory Extraction - Individual memories are identified
- Categorization - Each memory is classified by type:
- World Facts (objective facts from external sources)
- Experience (events and interactions)
- Observations (automatically synthesized knowledge consolidated from facts)
- Indexing - Memories are indexed for TEMPR retrieval (semantic, keyword, graph, temporal)
- Storage - Data is persisted in the memory bank
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bank_id | string | Yes | Target memory bank ID (in URL path) |
items | array | Yes | Array of memory items to store |
items[].content | string | Yes | Text content to process |
items[].context | string | No | Context about the content |
items[].timestamp | string | No | ISO 8601 timestamp for the memory |
async | boolean | No | Process asynchronously (default: false) |
Response
{
"success": true,
"bank_id": "my-assistant",
"items_count": 1,
"async": false,
"operation_id": null,
"usage": {
"input_tokens": 2684,
"output_tokens": 511,
"total_tokens": 3195
}
}
| Field | Description |
|---|---|
success | Whether the operation succeeded |
bank_id | The memory bank ID |
items_count | Number of items processed |
async | Whether processed asynchronously |
operation_id | ID for tracking async operations |
usage | Token consumption breakdown |
Content Best Practices
Be Specific
- Good
- Less Effective
client.retain(
bank_id=bank_id,
content="Alice prefers Python over JavaScript for backend development. She has 5 years of experience with FastAPI."
)
client.retain(
bank_id=bank_id,
content="User likes coding."
)
Include Context
- Good
- Less Effective
client.retain(
bank_id=bank_id,
content="During our March 15 meeting, the client mentioned they need the report by April 1st and prefer PDF format."
)
client.retain(
bank_id=bank_id,
content="Report needed by April 1st."
)
Use Natural Language
- Good
- Less Effective
client.retain(
bank_id=bank_id,
content="The customer is based in Tokyo, Japan and operates in the financial services sector. They typically respond faster to emails than phone calls."
)
client.retain(
bank_id=bank_id,
content="Location: Tokyo. Industry: Finance. Preference: Email."
)
Batch Operations
contents = [
"User completed onboarding on January 10th.",
"User's team has 5 members.",
"User prefers weekly check-ins over daily standups."
]
for content in contents:
client.retain(bank_id=bank_id, content=content)
With Metadata
client.retain(
bank_id=bank_id,
content="Meeting notes from Q1 review...",
source="meeting_transcript",
metadata={
"meeting_date": "2024-03-15",
"attendees": ["alice", "bob", "charlie"]
}
)
Memory Types Explained
Hindsight automatically categorizes memories into four types:
World Facts
Objective facts received from external sources:
- "Paris is the capital of France"
- "The project uses PostgreSQL for the database"
- "The company was founded in 2020"
- "The user's email is alice@example.com"
Experience
Events and interactions that have occurred:
- "User signed up for the premium plan last week"
- "We had a productive call with the design team"
- "The deployment completed successfully at 2 PM"
- "I sent a welcome email on January 10th"
Observations
Automatically synthesized knowledge — new facts are analyzed and consolidated into observations with evidence tracking. Observations are created and refined automatically in the background after each retain call:
- "User is growing comfortable with async Python"
- "Traffic peaks on Tuesday mornings"
- "Support tickets decrease after documentation updates"
Mental Model Auto-Refresh
When memories are stored via Retain, Hindsight consolidates new facts into observations. Mental models that have trigger.refresh_after_consolidation enabled will be automatically refreshed after this consolidation completes, keeping them up to date with the latest knowledge. See Mental Models for more details.
Each refresh is a billable Refresh Model call ($0.05), one per model rebuilt, so how a model keeps itself fresh is a cost decision as much as a freshness one. Knowledge base pages are mental models, which is what makes this worth reading before you build on it: a bank whose pages are broadly scoped, under steady retains, can spend more on Refresh Model than on every other operation combined.
Choosing how a model stays fresh
A model refreshes in one of three ways. refresh_after_consolidation and
refresh_cron are mutually exclusive — a model follows one or the other, never
both.
| mode | set | when it rebuilds | cost |
|---|---|---|---|
| Manual | neither field | only when you ask | exactly what you ask for |
| On consolidation | trigger.refresh_after_consolidation: true | after consolidations that touched its scope | scales with how much you write |
| Scheduled | trigger.refresh_cron: "0 * * * *" | on the schedule, only if something changed | capped by the schedule |
The default depends on how the model was created: a mental model you create through the API, an MCP tool or the dashboard is manual, while a knowledge base page is created on consolidation, so that a page keeps itself current without being asked.
A schedule is the better choice for most pages. It gives the model a ceiling you pick — an hourly cron is at most 24 rebuilds a day — and a tick whose scope saw no new memories is skipped rather than rebuilt, so a quiet bank costs nothing. Refreshing on consolidation has no such ceiling: it follows your write volume, so the same page costs more in a busy week than a slow one, for reasons that have nothing to do with the page.
Use refresh_after_consolidation when a model genuinely has to reflect the last
retain — a live operational summary, say — and accept that its cost tracks your
ingestion rate.
Cron expressions are standard 5-field UTC, so 0 3 * * * is daily at 03:00 UTC.
Neither automatic mode rebuilds blindly. A model is refreshed only if new memories actually landed in its scope since it last read them; models the consolidation or tick could not have affected are skipped and not billed. The number of calls therefore tracks how broadly your models are scoped, not simply how many you have.
How often an automatic refresh can run
Both automatic modes are rate-limited. On Hindsight Cloud a model rebuilds at most once a minute: a trigger arriving sooner is not dropped but queued, and every further trigger in that window folds into the one queued rebuild. So a burst of retains costs one refresh rather than one per retain.
Two things the limit does not apply to:
- Explicit refreshes. Refreshing from the API, an MCP tool or the dashboard ignores the floor entirely, so you can always bring a page current on demand.
- Models that opt out.
trigger.min_refresh_interval_secondson an individual model overrides the default — including0, which restores rebuilding on every trigger for a model that has to be immediate.
Raise it on a model that does not need to be current to the minute:
trigger.min_refresh_interval_seconds: 3600 rebuilds it at most hourly, however
much you write.
Token Usage
Retain operations consume tokens based on:
- Input content length
- Processing complexity
- Number of memories extracted
Error Handling
- Python
- TypeScript
try:
client.retain(bank_id=bank_id, content=content)
print("Memory stored successfully")
except Exception as e:
print(f"Error: {e}")
try {
await client.retain(bankId, content);
console.log('Memory stored successfully');
} catch (error) {
console.error('Error:', error.message);
}
Common Errors
| Error | Cause | Solution |
|---|---|---|
| 401 Unauthorized | Invalid API key | Check your API key |
| 402 Payment Required | Insufficient credits | Add credits to your account |
| 404 Not Found | Invalid bank_id | Verify the bank exists |
| 400 Bad Request | Empty content | Provide non-empty content |