This release is a pre-release and may not be stable for production use.
Unique User Memory
Persistent per-user memory for Unique AI agents.
unique_user_memory stores a compact Markdown profile for each user and updates it after every agent turn. The profile is loaded before the next turn so the assistant can remember stable user context such as communication preferences, work context, expertise, recent topics, and concrete future tasks.
What It Does
The package provides:
UserMemoryConfig- Pydantic configuration for the consolidation model, profile token budget, and memory folder.load_user_memory(...)- resolves the user's private memory folder, downloadsmemory.md, and enforces the configured token budget. Thelanguage_modelargument is used to tokenizememory.mdwhen capping it, so it must be the same effective model the postprocessor uses for consolidation (see Integration below). Returns aUserMemoryStatewith the profile text and scope id.profile_body(...)- strips the YAML frontmatter and returns only the Markdown body. Use it whenever the profile is shown to a model; the frontmatter is bookkeeping for consolidation.UserMemoryMessageLogger- emits chat Steps (MessageLogs) for load and update, including typedUserMemorydetail entries the chat frontend renders as a badge that opens Settings → Context Memory. Frontends that do not know the entry type render nothing, so the entries are safe to emit in any deploy order.UserMemoryPostprocessor- runs after the assistant response, consolidates the latest turn into the profile, and uploads the updatedmemory.md.
The memory file is intentionally small and structured. It is rewritten as a full Markdown profile rather than appended to as an event log.
Lifecycle
- The orchestrator enables memory when
space.allow_user_memoryis true. - The orchestrator emits a Loading context memory Step, then
load_user_memory(...)resolves the pre-provisioned root folder, ensures a private child folder for the current user, and downloads/user-memory/<user_id>/memory.mdif it exists. - When load returns a
UserMemoryState, that Step is completed with a Context memory detail entry (type: UserMemory) that the chat frontend renders as a badge opening Settings → Context Memory. A successfulNonereturn (soft skip) completes the Step without the entry; a raised exception marks the Step failed. - If memory was loaded,
profile_body(...)of its text is passed into the agent context for the current turn — the prompt only gets the Markdown body, while the postprocessor keeps the full file because it needs the frontmatter to carryturn_countforward. UserMemoryPostprocessorruns after the assistant response.- The package asks the configured language model to either return
NOOPor a complete rewritten profile. - If a rewrite runs, an Updating your memory Step is shown while consolidating (no settings entry yet).
- If the profile changed and
memory.mduploads successfully (ingestion skipped, content hidden from chat), that Step is completed with a Review your context memory detail entry (same settings badge). On NOOP or failed upload the Step completes without the entry.
Storage Model
Memory is stored in Unique content as Markdown:
/<root_folder>/<user_id>/memory.md
By default, root_folder is user-memory. The root folder must already exist. The package creates the per-user child folder when needed.
Profile Format
Profiles contain YAML frontmatter followed by fixed Markdown sections:
---
user_id: user_123
schema_version: 1
last_updated: 2026-06-17T12:00:00+00:00
turn_count: 1
---
# User Memory
## Identity
_(empty)_
## Communication Preferences
- Prefers concise answers with concrete examples.
## Work Context
_(empty)_
## Skills & Expertise
_(empty)_
## Recent Topics
_(empty)_
## Follow-ups
_(empty)_
The consolidation prompt preserves the schema, keeps bullets short, and returns NOOP when a turn has no durable user facts.
Configuration
Memory is activated by the orchestrator when space.allow_user_memory is true. UserMemoryConfig only configures how active memory is consolidated and stored.
from unique_user_memory import UserMemoryConfig
config = UserMemoryConfig(
max_tokens=2000,
root_folder="user-memory",
)
| Field | Default | Description |
|---|---|---|
use_orchestrator_language_model |
True |
When true, consolidation and load-time token capping use the model the orchestrator passes in and language_model is ignored. Set to False to use the configured language_model for both. |
language_model |
DEFAULT_GPT_4o |
Model used to consolidate the latest turn and to tokenize memory.md at load time when use_orchestrator_language_model is False. |
max_tokens |
2000 |
Maximum profile size. Must be between 500 and 8000 tokens. |
root_folder |
user-memory |
Root KB folder that contains per-user memory folders. |
Integration
Typical orchestration code loads memory before the agent loop and registers the postprocessor for the same turn.
load_user_memory and UserMemoryPostprocessor must be given the same effective language model: the postprocessor consolidates memory with either the orchestrator model or the configured one depending on use_orchestrator_language_model, and load-time token capping must use that same model so the loaded baseline is tokenized the way consolidation expects. Resolve the effective model once and pass it to both:
from unique_toolkit.agentic.message_log_manager.service import MessageStepLogger
from unique_user_memory.user_memory import load_user_memory, profile_body
from unique_user_memory.user_memory_message_log import UserMemoryMessageLogger
from unique_user_memory.user_memory_postprocessor import UserMemoryPostprocessor
user_memory_config = config.agent.services.user_memory_config
# Resolve the effective model once and reuse it for load-time capping and
# consolidation so both use the same tokenizer.
memory_language_model = (
config.space.language_model
if user_memory_config.use_orchestrator_language_model
else user_memory_config.language_model
)
message_step_logger = MessageStepLogger(chat_service)
memory_message_step_logger = UserMemoryMessageLogger(
message_step_logger,
logger=logger,
)
await memory_message_step_logger.log_loading_start()
user_memory_state = None
load_succeeded = False
try:
user_memory_state = await load_user_memory(
event=event,
config=user_memory_config,
language_model=memory_language_model,
logger=logger,
)
load_succeeded = True
except Exception as exc:
logger.warning(
"[user-memory] load raised - running without memory: [%s] %s",
type(exc).__name__,
exc,
)
finally:
# Always close the RUNNING step — otherwise the chat Steps UI stays stuck
# on "Loading context memory" for that turn when load raises.
if not load_succeeded:
await memory_message_step_logger.log_loading_failed()
if load_succeeded and user_memory_state is not None:
await memory_message_step_logger.log_loading_complete(with_settings_entry=True)
# The postprocessor keeps the full file (it needs the frontmatter to
# carry turn_count forward); the prompt only gets the Markdown body.
user_memory_text = profile_body(user_memory_state.text)
postprocessor_manager.add_postprocessor(
UserMemoryPostprocessor(
config=user_memory_config,
language_model=memory_language_model,
event=event,
state=user_memory_state,
logger=logger,
message_step_logger=memory_message_step_logger,
)
)
elif load_succeeded:
await memory_message_step_logger.log_loading_complete(with_settings_entry=False)
Note that UserMemoryPostprocessor re-derives the effective model internally from use_orchestrator_language_model, so passing memory_language_model (rather than the raw orchestrator model) keeps its behavior identical while ensuring load_user_memory caps with the matching tokenizer.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unique_user_memory-2026.34.0.dev7.tar.gz.
File metadata
- Download URL: unique_user_memory-2026.34.0.dev7.tar.gz
- Upload date:
- Size: 23.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c9e6b820f15d7483d8bbdc6ae8518a3619c6ae72e2873b1c7823790a4cb9a4f
|
|
| MD5 |
9713cae9eae7f23336932ebb6ea152ad
|
|
| BLAKE2b-256 |
ac244edc128314bfe3b8334df2d798e0abe482505cd506e806c564471a058861
|
File details
Details for the file unique_user_memory-2026.34.0.dev7-py3-none-any.whl.
File metadata
- Download URL: unique_user_memory-2026.34.0.dev7-py3-none-any.whl
- Upload date:
- Size: 27.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3db7315ad8bc096759b4f830410c90a29c35ea513418be66b257bc8a2ae7a680
|
|
| MD5 |
b19d54e00bf955baa48260fbfd6e5ac3
|
|
| BLAKE2b-256 |
61994d4d01f989b2cc884eddb2f036e8dd9395cc4ee3b7eae62e8e56b2cc90dd
|