What's new in Atulya 0.4.15
Atulya 0.4.15 adds PydanticAI integration, observation scopes, richer entity labels, and several performance improvements—alongside a set of reliability and stability fixes.
- PydanticAI Integration: Add persistent memory to PydanticAI agents.
- Observation Scopes: Control how facts consolidate into observations across tag dimensions.
- Richer Entity Labels: Optional labels, free-form values, and multi-value fields for entity extraction.
- Timestamp Unset: Retain timeless content without a date.
- Performance: Recall and retain are faster and more scalable for large memory banks.
PydanticAI Integration
Atulya now integrates with PydanticAI, letting you attach persistent memory to any PydanticAI agent with a few lines of code.
from atulya_client import Atulya
from atulya_pydantic_ai import create_atulya_tools, memory_instructions
from pydantic_ai import Agent
client = Atulya(base_url="http://localhost:8888")
agent = Agent(
"openai:gpt-4o",
tools=create_atulya_tools(client=client, bank_id="user-123"),
instructions=[memory_instructions(client=client, bank_id="user-123")],
)
result = await agent.run("What do you remember about my preferences?")
print(result.output)
create_atulya_tools registers three async tools on the agent:
atulya_retain— stores information to long-term memoryatulya_recall— searches memory for relevant factsatulya_reflect— synthesizes a reasoned answer from stored memories
memory_instructions injects recalled context as instructions at the start of every run, so the agent always has relevant history in its context window without needing to call a tool explicitly.
Both accept the full set of Atulya retrieval parameters—budget, max_tokens, tags, recall_tags, and recall_tags_match—for fine-grained control over what gets stored and retrieved.
You can also configure defaults globally:
from atulya_pydantic_ai import configure, create_atulya_tools
configure(
atulya_api_url="http://localhost:8888",
api_key="your-api-key", # Or set ATULYA_API_KEY env var
budget="mid", # Recall budget: low/mid/high
max_tokens=4096, # Max tokens for recall results
tags=["env:prod"], # Tags for stored memories
recall_tags=["scope:global"], # Tags to filter recall
recall_tags_match="any", # Tag match mode: any/all/any_strict/all_strict
)
# Now create tools without passing client — uses global config
tools = create_atulya_tools(bank_id="user-123")
See the PydanticAI integration documentation for a full quickstart.
Observation Scopes
Observations are Atulya's higher-level summaries derived from raw facts. Previously, observations were always consolidated across all tags at once. Now you can control the granularity of each consolidation pass with observation_scopes.
This matters when memories are tagged across multiple dimensions—say, student:alice, teacher:bob, and session-id:s1. You might want observations per-student, per-teacher, and per-session independently, not just one combined observation for all three.
observation_scopes is a per-item parameter passed in the retain request. The examples below use a lesson transcript retained with tags: ["student:alice", "teacher:bob", "session-id:s1"].
per_tag — one consolidation pass per individual tag. The most common choice for multi-party content.
- Observations created:
["student:alice"]·["teacher:bob"]·["session-id:s1"] - Queries like "What does Alice struggle with?" and "How does Bob teach?" match.
- Queries like "How does Alice perform specifically with Bob?" do not — no observation was built for that combination.
combined (default) — one pass using all tags together.
- Observations created:
["student:alice", "teacher:bob", "session-id:s1"] - Only the exact combination matches. Individual tags like
["student:alice"]do not.
all_combinations — one pass per subset. For 3 tags that is 7 passes.
- Observations created: all
per_tagscopes plus every pair and the full set.
custom — explicit list of tag sets:
[["student:alice"], ["teacher:bob"], ["teacher:bob", "session-id:s1"]]
Only those three scopes are built — nothing more.
Scopes are fully isolated during consolidation—a memory consolidated under ["student:alice"] will never bleed into an observation tagged ["student:alice", "teacher:bob"].
Richer Entity Labels
entity_labels is a new bank configuration option that defines a controlled vocabulary of key:value classification labels. During retain, the LLM extracts these labels from each piece of content and stores them as entities. Because labels become entities, they automatically link memories in the knowledge graph and improve both semantic and BM25 retrieval.
Three field types are supported:
| Type | Behavior |
|---|---|
"value" | Single value from a fixed list |
"multi-values" | One or more values from a fixed list |
"text" | Free-form string (no fixed values) |
Fields can also be marked optional: true so the LLM skips them when the content doesn't have enough information.
{
"entity_labels": [
{
"key": "engagement",
"description": "Student engagement level during the session",
"type": "value",
"optional": true,
"values": [
{ "value": "active", "description": "Student is actively participating" },
{ "value": "passive", "description": "Student is listening but not participating" }
]
},
{
"key": "pedagogy",
"description": "Teaching strategies used",
"type": "multi-values",
"values": [
{ "value": "scaffolding", "description": "Breaking complex tasks into smaller steps" },
{ "value": "direct_instruction", "description": "Explicit explanation by the teacher" },
{ "value": "socratic_questioning", "description": "Guiding through questions rather than answers" }
]
},
{
"key": "topic",
"description": "Specific subject being discussed. Examples: algebra, quadratic equations, geometry.",
"type": "text",
"optional": true
}
]
}
For enum types ("value", "multi-values"), anything outside the values list is silently dropped—vocabulary stays stable and graph links stay tight. For "text" types, the LLM writes any string; use description to provide examples and guidance.
Set entity_labels via the bank config API. The control plane UI has been updated to display multi-value and free-form labels cleanly.
Timestamp Unset
When retaining reference material—documentation, books, or any content without a meaningful event date—you can now pass timestamp="unset" to tell Atulya there is no real date to associate with the content.
# Timeless reference content
client.retain(
bank_id="my-bank",
content="The quick sort algorithm has O(n log n) average-case time complexity.",
timestamp="unset"
)
# Mix timeless and timestamped content in a single batch
client.retain_batch(
bank_id="my-bank",
items=[
{"content": "Meeting notes...", "timestamp": "2026-03-01T14:00:00Z"},
{"content": "Company handbook...", "timestamp": "unset"},
]
)
When "unset" is passed, the fact-extraction prompt shows Event Date: Unknown, allowing the model to correctly return N/A for the when field of every extracted fact rather than anchoring facts to an arbitrary date.
Performance
This release includes significant database-level work targeting large-scale memory banks—better indices, improved query planning, reduced lock contention, deadlock fixes, and connection warmup improvements. We benchmarked against banks with up to 100,000 memories and 100 million entity links:
- Retain: Up to 10x faster at large scale.
- Recall: Up to ~20x better at very large scale, with the largest gains in the graph and temporal retrievers.
No configuration is required.
Other Updates
Features
- OpenClaw auto-retain: OpenClaw now retains the last
N*2 + 4messages every N turns (default N=10) instead of every turn. The sliding window ensures conversation context is never lost at boundaries while significantly reducing LLM calls per session. Configure withretainEveryNTurnsin the plugin config. - Gemini/Vertex AI safety settings: Safety settings for Gemini and Vertex AI LLM calls are now configurable. Useful for deployments that need to relax or tighten content filtering on LLM calls.
- Document tag filtering: The list documents API now supports filtering by tags, making it easier to query which documents were retained under a given label.
- Extension hooks: New hooks to customize root routing behavior and add custom error headers, for deployments that need to intercept requests or decorate responses at the transport layer.
Bug Fixes
- Fixed reflect failing with
context_length_exceededon large memory banks by truncating the context window correctly. - Fixed a consolidation deadlock caused by retrying after zombie processing tasks.
- Fixed the observations count in the control plane that always showed 0.
- Fixed JSON serialization issues and logging-related exception propagation with the
claude_codeLLM provider. - Fixed the ZeroEntropy rerank endpoint URL.
- Fixed MCP
async_processingparameter handling. - Added bank-scoped request validation to prevent cross-bank operations.
- Fixed the TypeScript SDK to send
null(notundefined) whenincludeEntitiesisfalse.
Feedback and Community
Atulya 0.4.15 is a drop-in replacement for 0.4.x with no breaking changes.
Share your feedback:
For detailed changes, see the full changelog.