What's new in Hindsight 0.9.2
Hindsight 0.9.2 opens the knowledge base to MCP clients, lets a caller hand recall the time window instead of writing it in English, adds a GitHub Copilot provider and asymmetric embedding prefixes, and gives operators two ceilings they didn't have before — one on what a retain may hold in memory, one on which banks get their own vector index.
- The knowledge base is now an MCP surface: Seven agent-facing operations registered as native tools, plus refresh triggers on the tree.
- Recall: supply the time window yourself: A
temporal_windowyou set directly, which also skips the parse it replaces. - Mental models: pace the refreshes and audit the results: A minimum interval between automatic refreshes, and a typed outcome on every one.
- A GitHub Copilot provider, and asymmetric embeddings: Run the LLM lanes on a Copilot plan, and give queries and passages different prefixes.
- Retain: a memory ceiling in megabytes: A budget the pipeline throttles against, and peak memory flat from 4 MB to 90 MB.
- Vector indexes earned by bank size: A row threshold before a bank gets its own index, for deployments with thousands of them.
The knowledge base is now an MCP surface
The knowledge base was reachable only over HTTP, so an MCP client had to fall back to a second integration path to browse or maintain it. Seven agent-facing operations are now native MCP tools, with the same bank scoping, tenant auth and operation-validator behaviour as the existing ones:
get_knowledge_base_tree, search_knowledge_base, get_knowledge_page, create_knowledge_folder, create_knowledge_page, update_knowledge_node, delete_knowledge_node
An agent can now read, search and maintain its own wiki over the same connection it already uses to retain and recall. export_knowledge_base stays HTTP and CLI only — it returns the whole bank as one markdown bundle, which has no business in an agent's context window.
Alongside it, each page's refresh trigger is visible on the tree, so you can see what causes a page to rebuild without opening the page to find out.
Recall: supply the time window yourself
RecallRequest takes a temporal_window with a start and an end. Set it and that range is used verbatim.
Until now the only way to constrain time was to phrase it in English inside the query text and hope the date parser agreed with you — which a date picker, or an agent that had already resolved "last quarter" against a real calendar, had no reason to do. Now the caller that already knows the range can just say it.
Setting it also skips the extraction it replaces, and that parse is not free: it's pure CPU work, serialised through a single worker, costing up to ~1.3s on document-sized query text. That's exactly the shape of query consolidation and reflect issue internally, so the saving shows up in more than the calls you make by hand.
Mental models: pace the refreshes and audit the results
Two controls for anyone running models that refresh on their own.
A floor on automatic refreshes. HINDSIGHT_API_MENTAL_MODEL_MIN_REFRESH_INTERVAL_SECONDS sets the minimum gap between two automatic refreshes of the same model — both the after-consolidation trigger and the cron schedule. A trigger that fires inside the window isn't dropped: its refresh is parked until the window closes, and every further trigger in the meantime folds into that one parked refresh, which still sees everything that accumulated while it waited. So a burst of small retains costs one refresh instead of one per retain. Explicit refreshes from the API, MCP or the control plane ignore the floor and run immediately. It's hierarchical — set it server-wide, per bank, or per model with trigger.min_refresh_interval_seconds, where a 0 exempts one hot model from a bank-wide floor. The default is 0, so nothing changes until you choose a value.
Every refresh reports what it did. Refresh operations now carry a typed outcome — and a failure_reason when they fail — on the operations list and the single read, not buried in debug metadata that the next refresh overwrites. The outcome is decided by observable effect rather than by which code path produced it, so a candidate identical to the stored document reports content_unchanged rather than claiming a write. Measuring how your models actually behave over a window no longer needs an external sampler polling each one's latest state. Refresh operations also name their mental_model_id, which the list previously couldn't tell you.
The control plane shows staleness per model, described the same way everywhere it appears.
A GitHub Copilot provider, and asymmetric embeddings
- Run the LLM lanes on a Copilot subscription. Set
HINDSIGHT_API_LLM_PROVIDER=github-copilot— no API key required. - Different prefixes for queries and passages. Many embedding models expect a different instruction prefix for a query than for a stored passage, and collapsing the two costs retrieval quality.
HINDSIGHT_API_EMBEDDINGS_QUERY_PREFIXandHINDSIGHT_API_EMBEDDINGS_PASSAGE_PREFIXare now set independently for text-in providers, which previously had no way to distinguish the two sides. - Exact entity resolution when you're the one naming.
update_memoryand retain take aresolve_entitiesflag. Retain keeps fuzzy resolution, which is right for names that came out of extraction; a curated edit can now ask for exact matching instead, so an entity name you typed is an instruction rather than a guess to be reconciled against the graph. - Batch retain runs through a batch-capable model member, so a multi-LLM setup routes batch work to a backend that actually supports it.
Retain: a memory ceiling in megabytes
HINDSIGHT_API_RETAIN_MEMORY_BUDGET_MB (default 128) caps how much extracted-but-unwritten state one retain operation may hold.
HINDSIGHT_API_RETAIN_CHUNK_BATCH_SIZE already bounded how many chunks are in flight, but a count is only a memory bound if chunks cost a predictable amount — and they don't, since a chunk carries however many facts the extractor found in it. The budget is the figure to size a worker against: peak per retain is roughly this number whatever the document, multiplied by your concurrent retain slots. Over budget, extraction waits for the write path to catch up rather than growing. 0 restores the previous count-only bound.
The front half of the pipeline was rebuilt to make that ceiling meaningful. Sizing a 45 MB body used to allocate 385 MB, and chunking it another 200 MB — both before a single fact existed. Both are now flat across 4, 16, 45 and 90 MB documents, at 9.6 MB and effectively zero, with sizing and chunking a touch faster than before. Splitting an oversized document into sub-batches streams as well, so the slices are no longer all held at once: peak across the split-and-screen stage went from 204 MB to 26 MB on a 90 MB body, and is now the same 26 MB at 16 MB. What's left scaling with the document is the submitted body itself, which has to survive to be stored. Chunking also no longer depends on langchain at runtime, which drops langchain-core and langsmith from the install; boundaries are pinned against langchain's own output by a differential test across every separator tier, because a shifted boundary would silently re-chunk every stored document.
Vector indexes earned by bank size
HINDSIGHT_API_VECTOR_INDEX_MIN_ROWS sets how many memories a bank needs in one fact type before that fact type gets its own vector index.
This matters at scale: every per-bank index lives on the shared memories table and is planned against by every other bank's queries, so creating three per bank unconditionally puts a ceiling on how many banks one deployment can hold. Set it to something like 10000 and small banks fall back to exact search — which is both faster and exact at that size — while large ones get the index they actually need, built and dropped by background maintenance as they cross the line. HINDSIGHT_API_VECTOR_INDEX_MAINTENANCE_MIN_INTERVAL_SECONDS keeps a bank hovering at the threshold from building and dropping the same index repeatedly.
The default stays 0, meaning the threshold is off and every bank is indexed at creation, exactly as before.
Two smaller things for the same audience: the bank list is paginated, and worker runs are traced and join the caller's trace, so an async retain is visible end to end instead of disappearing after the API returns.
0.9.2 also carries a long list of fixes across mental-model refreshes, knowledge pages, recall, retain, consolidation and the supported providers. See the changelog for the full list.
