Skip to main content

Vity Python SDK

Vity gives your AI application long-term memory. This package is the Python client for the Maximem memory API, by Maximem AI. It has a synchronous client and an asyncio client.

from maximem_vity import VityClient

client = VityClient(channel="my-app")  # reads MAXIMEM_API_KEY

client.store("User prefers dark mode", memory_type="preference")
print(client.recall("What theme does the user like?"))

Using JavaScript or TypeScript? Use @maximem/vity-sdk on npm. It has the same methods. See Python and JS differences.

Contents

Install

pip install maximem-vity-sdk

Quick start

Create an API key at app.maximem.ai/api-keys and set it in your environment:

export MAXIMEM_API_KEY="mx_..."

A typical agent turn recalls memory before it answers, then captures the conversation afterwards:

from maximem_vity import VityClient

with VityClient(channel="support-bot") as client:
    # Before answering: get memory for the prompt.
    context = client.recall("Can you book my usual table?")

    # After answering: send the exchange so Vity can learn from it.
    client.capture(
        [
            {"role": "user", "content": "Can you book my usual table?"},
            {"role": "assistant", "content": "Booked the window table for 7pm."},
        ],
        session_key="conversation-8f2c",
    )

    # Store one fact directly.
    client.store("Prefers window seats", memory_type="preference", importance="high")

    # Search memory.
    for hit in client.search("seating preferences", top_k=5):
        print(f"{hit['score']:.2f}  {hit['content']}")

Key ideas:

  • Memory belongs to your Maximem account. Every API key on the account reads and writes the same memory. Adding or rotating keys loses nothing.
  • channel names your integration (for example "slack" or "support-bot"). Together with session_key it identifies a conversation.
  • session_key identifies one conversation. Use the same value for every turn of a conversation and a new value for each new conversation.

Configuration

client = VityClient(
    api_key="mx_...",       # default: MAXIMEM_API_KEY
    channel="my-app",
    agent_id="assistant",
    timeout=30.0,
    max_retries=2,
)
Argument Environment variable Default Description
api_key MAXIMEM_API_KEY none (required) Your Maximem API key. Raises VityAuthError if missing.
endpoint MAXIMEM_ENDPOINT Maximem production API Only for a non-default environment. Must start with https:// or http://.
timeout 30.0 Request timeout in seconds. Must be greater than 0.
channel "unspecified" The name of your integration. Set it.
agent_id "unspecified" A tag sent with stores and captures. capture() can override it per call.
max_retries 2 Retries for temporary failures. 0 turns retries off. Keyword-only.
http_client created for you Your own httpx.Client (httpx.AsyncClient for AsyncVityClient). Keyword-only.
user_id Deprecated. Ignored, and emits a FutureWarning. Removed in 0.4.0.

Arguments take precedence over environment variables. The default endpoint is available as maximem_vity.DEFAULT_ENDPOINT.

If you pass http_client, set the timeout on that client. The timeout argument only configures the client the SDK creates itself. The SDK does not close a client you pass in.

Use the client as a context manager (with), or call close() when you are done.

Core methods

Every method exists on VityClient and, as a coroutine, on AsyncVityClient. Arguments are checked before any request is sent. A bad argument raises VityInvalidInputError.

Method Purpose
recall Get memory as text to put in a prompt.
live_context Get only the user's live work from other apps.
capture Send a conversation for background extraction.
store Save one fact now.
search Find memories by meaning, with scores.
forget Preview or delete memories.
knowledge, knowledge_diff Read consolidated knowledge about the user.
get_profile_card Read the user's Profile Card.
meetings_enabled, meeting_ask, get_meeting, list_meeting_reviews Work with meeting memory.
store_many, get, update, delete, list, stats Preview. Not yet generally available.

Recall

client.recall(
    current_prompt,
    recent_messages=None,
    max_tokens=1000,
    strategy="hybrid",
    live_context=False,
    session_key="",
) -> str

recall() returns text you can put in a system prompt, or "" when nothing relevant is stored.

  • recent_messages: recent turns as {"role", "content"} dicts. Only the last 20 are sent.
  • max_tokens: the size budget for the returned context. Must be 100 to 10,000.
  • strategy: "semantic", "recency" or "hybrid".
  • live_context and session_key: see Live context.

Each memory line in the result starts with the day it was said, when known, for example - [2026-03-14] Prefers window seats.

Capture

client.capture(messages, agent_id="", session_key="", channel=None) -> CaptureResult

capture() sends a conversation for Vity to learn from. Use it after each turn.

  • messages: up to 50 {"role", "content"} dicts. timestamp is optional.
  • session_key: the conversation's key. Use the same value on every turn of one conversation.
  • agent_id and channel: override the client's values for this call.

Capture is asynchronous:

  • The call returns when the conversation is accepted. Vity extracts durable facts in the background, usually within seconds to minutes. Small talk is dropped.
  • A captured turn is not searchable straight away. "Captured" does not mean "stored".
  • The counts in the result (captured, deduplicated) are None for an accepted capture.
  • capture([]) makes no request and returns zeros.
  • To send more than 50 messages, split them across calls with the same session_key.
  • A capture is never retried once the request may have reached the server. This prevents the same conversation from being stored twice.

timestamp is the time the message was said, in epoch milliseconds. You can also pass epoch seconds, an ISO 8601 string or a datetime, and the SDK converts it. A value that is missing or unreadable becomes the current time. An ISO string or datetime without a time zone is read as UTC. The same rule applies to recent_messages in recall(). Your list is never modified.

capture_status(request_id) polls the processing status of an earlier capture by its request id and returns a dict.

client.store(content, memory_type="fact", category="", importance="medium") -> StoreResult
client.search(query, top_k=10, limit=0, category="", min_score=0.0) -> list[SearchResult]

store() saves one fact immediately and returns {"id", "stored"}. It keeps exactly what you send. To extract facts from a whole conversation, use capture().

  • content: up to 10,000 characters.
  • memory_type: fact, preference, emotion, episode, knowledge or profile. This is a shortcut that maps to a category.
  • category: overrides memory_type. One of preference, fact, task, relationship, context.
  • importance: "low", "medium" or "high".

search() finds memories by meaning. top_k is the maximum number of results (1 to 20). limit is an alias for top_k and wins when set. min_score is 0 to 1, and 0.0 means no minimum.

Each result is a dict:

Field Meaning
content The memory text.
type The memory category.
score Relevance, 0 to 1.
id The memory id.
created_at When the memory was stored.
chat_at When it was said, or None if unknown.
happened_at chat_at when known, otherwise created_at.

All dates are epoch milliseconds. An old chat imported today has a recent created_at and an old chat_at. Use happened_at to order memories by when things happened.

If search finds nothing, the SDK checks recall before it reports no results. Results from that fallback can have a created_at of 0, a chat_at of None and an empty id.

Forget

client.forget(query="", dry_run=True, delete_all=False) -> ForgetResult

forget() returns {"count", "ids"}. It only previews unless you pass dry_run=False.

client.forget("old employer")                    # preview what would be deleted
client.forget("old employer", dry_run=False)     # delete matching memories
client.forget(dry_run=False, delete_all=True)    # delete every memory on the account

Matching is by meaning, not exact text. One query can match several memories, including related but different facts. Check count and ids in the preview before you delete.

Deleting without a query removes every memory on the account, so it needs delete_all=True. Passing both a query and delete_all=True is an error.

Knowledge

client.knowledge(
    query="", scope="chat", limit=10, min_score=0.3, include_candidates=False,
    filters=None, offset=0, include=None, echo_slug=None,
) -> KnowledgeResult

client.knowledge_diff(
    input_text, purpose, scope="chat", include_candidates=True, include_trace=False,
) -> KnowledgeDiffResult

knowledge() returns consolidated knowledge about the user: topics, summaries and confidence.

  • scope: "chat", "echo" or "all". Use echo_slug to choose the echo when the scope is "echo".
  • limit is 1 to 100 and offset is 0 to 10,000. Use them to page through results.
  • min_score is 0 to 1.
  • include: any of "items", "facets", "profile".
  • filters: for example {"topic_category": ["work"], "confidence_min": 0.5, "seen_after": "2026-01-01"}. Other keys include topic_subcategory, knowledge_type, expertise_level, seen_before and topic_id. The SDK converts snake_case keys to the API's camelCase names.

knowledge_diff() returns the knowledge most relevant to a piece of text. purpose says what knowledge you need and why. It is a selection criterion, not a formatting instruction. A status of "degraded" is a valid partial answer.

client.knowledge_diff(
    input_text="Draft: proposal to move our analytics to ClickHouse",
    purpose="the user's earlier decisions and opinions about analytics databases",
)

Profile card

client.get_profile_card() -> ProfileCard

get_profile_card() returns the user's Profile Card: who they are, built from their memories and kept up to date. It is a fast read and makes no LLM call.

card = client.get_profile_card()
name = card["identity"]["name"]              # {"value": "Ada", "confidence": 0.95, ...} or None
skills = [s["value"] for s in card["knowledge"]["skills"]]

Sections:

Section Fields
identity name, email
professional current_company, current_role, company_history, role_history
personal location, location_history, education, interests
knowledge expertise, top_domains, skills
goals active, completed
preferences a list of values
candidates speculative, intended or ended values. Never current state.

Each value has value, confidence (0 to 1) and source_ids (the memories behind it). A field with no value is None, or [] for lists. A user whose profile is not ready yet gets an empty card. Keys are snake_case.

get_profile() is deprecated and will be removed in 0.4.0. It returns a broad memory search, not the Profile Card.

Live context

Live context lets Vity see work the user is doing right now in other apps, such as ChatGPT or Claude. When the current message continues one of those chats, the context comes from that chat.

# With recall: also checks live chats.
context = client.recall("Continue the plan", live_context=True, session_key="conversation-8f2c")

# On its own: only the live chat, or "" if the prompt continues none.
live = client.live_context("Continue the plan", session_key="conversation-8f2c")

live_context(current_prompt, session_key="") is cheap. It does no memory retrieval when nothing matches. Use it on the first turn of a session if you do your own retrieval.

Pass the same session_key and agent_id that you use in capture(). Vity uses them to tell this session's own chat apart from the user's other chats.

Meetings

Method Returns
meetings_enabled() bool. False when meeting memory is off or the call fails.
meeting_ask(question, max_memories=12, meeting_type="", status="", seed_person_id="") dict
get_meeting(external_id) dict
list_meeting_reviews() list[dict]

meeting_ask() answers a question from meeting memory. It is metered per call, so call it on demand. question is up to 4,000 characters. max_memories is 1 to 50. meeting_type (for example "1:1" or "standup") and status (for example "open") filter the meetings. seed_person_id boosts meetings with that person.

Preview methods

These methods are not yet generally available: store_many, get, update, delete, list and stats. When the service does not offer one, you get a VityNotFoundError whose message ends with "this is a preview feature that is not available yet". Do not depend on them yet.

client.store_many(items)
client.get(memory_id)
client.update(memory_id, content=None, category=None, importance=None)
client.delete(memory_id)
client.list(limit=50, offset=0, category="", since=None)
client.stats()

Async usage

AsyncVityClient takes the same arguments and has the same methods, as coroutines. Pass an httpx.AsyncClient as http_client if you need your own.

import asyncio
from maximem_vity import AsyncVityClient

async def main() -> None:
    async with AsyncVityClient(channel="support-bot") as client:
        context = await client.recall("What does the user like to eat?")
        await client.capture(
            [{"role": "user", "content": "I like Thai food."}],
            session_key="conversation-8f2c",
        )

asyncio.run(main())

Close the client with async with, or await client.close() (also available as aclose()).

Use as an agent tool

as_tool() lets a model search and save memories itself. It gives you two tools, search_memory and save_memory, in OpenAI or Anthropic format.

from openai import OpenAI
from maximem_vity import VityClient

llm, vity = OpenAI(), VityClient(channel="assistant")
memory = vity.as_tool(style="openai")       # or style="anthropic"

messages = [{"role": "user", "content": "What am I allergic to?"}]
response = llm.chat.completions.create(model="gpt-4o", messages=messages, tools=memory.definitions)

message = response.choices[0].message
messages.append(message)
for call in message.tool_calls or []:
    if memory.handles(call.function.name):
        result = memory.call(call.function.name, call.function.arguments)
        messages.append({"role": "tool", "tool_call_id": call.id, "content": result})
  • as_tool(style="openai", read_only=False). style is "openai" or "anthropic".
  • definitions is plain JSON. Pass it as tools.
  • call(name, arguments) takes arguments as a JSON string or a dict, and returns a JSON string. search_memory takes query and an optional limit (default 5). save_memory takes content and optional category and importance.
  • If the model sends invalid arguments, call() returns {"error": ...} so the model can try again. Other problems, such as an invalid API key, raise an exception. An unknown tool name raises ValueError.
  • read_only=True offers only search_memory.
  • handles(name) tells you whether a tool call is for these tools.
  • The model chooses the query or the fact. It cannot change whose memory is used.

With AsyncVityClient, use await memory.call(...).

Errors

Every exception inherits from VityError.

Exception HTTP status Retryable Raised when
VityInvalidInputError 400, 422 No An argument is invalid. Usually raised before any request is sent.
VityAuthError 401, 403 No The API key is missing, invalid or revoked.
VityInsufficientCreditsError 402 No The account has no credits left.
VityNotFoundError 404 No The item does not exist. For a preview method, the feature is not available.
VityRateLimitError 429 Yes Too many requests. Check retry_after.
VityUnavailableError 5xx except 501, or no response Yes The service is temporarily unavailable, the network failed, or the request timed out.
VityError any other No Any other failure, including a success response that is not JSON.

Fields on every exception:

Field Meaning
message A description of the problem. Also str(err).
status_code The HTTP status, or None for errors raised on your side or by the network.
code A machine-readable code from the service, if any.
request_id The id the SDK sent with the request. Include it when you contact support.
retry_after Seconds the service asked you to wait, if any.
retryable True if trying again later may work.
import time
from maximem_vity import VityError, VityInsufficientCreditsError, VityRateLimitError

try:
    client.store("User is vegetarian", memory_type="preference")
except VityInsufficientCreditsError:
    ...  # ask the account owner to add credits
except VityRateLimitError as err:
    time.sleep(err.retry_after or 60)
except VityError as err:
    print(f"Vity request failed: {err} (request id: {err.request_id})")

Invalid client settings raise ValueError: a timeout that is not above 0, a negative max_retries, or an endpoint that does not start with http:// or https://.

Retries and timeouts

The client retries temporary failures (HTTP 429, 5xx, network errors and timeouts) up to max_retries times.

  • The wait starts at about 0.5 seconds, doubles each time, includes random jitter and never exceeds 8 seconds.
  • If the service sends a retry_after of more than 8 seconds, the client does not wait. It raises VityRateLimitError with retry_after set so you can decide what to do.
  • capture, store, store_many and forget(dry_run=False) are not retried after the request may have reached the server. They are retried only when the connection failed before sending, or when the service answered 429, which means it did no work.
  • All other calls, including a forget dry run, are retried.

timeout applies to each stage of a request (connect, send, receive). Connecting never waits longer than 10 seconds.

Logs go to the maximem_vity logger: retries at WARNING, each request at DEBUG. The API key is never logged.

Testing your code

Pass an httpx.MockTransport to test without network access:

import httpx
from maximem_vity import VityClient

def handler(request: httpx.Request) -> httpx.Response:
    return httpx.Response(200, json={"context": "User prefers dark mode", "sources": []})

http = httpx.Client(transport=httpx.MockTransport(handler))
client = VityClient(api_key="mx_...", http_client=http)
assert client.recall("theme?") == "User prefers dark mode"

Python and JS differences

Both packages share one API. The names follow each language's style.

Python (maximem-vity-sdk) JavaScript (@maximem/vity-sdk)
Method names snake_case: get_profile_card camelCase: getProfileCard
Arguments Keyword arguments: search(q, top_k=5) Options object: search(q, { topK: 5 })
Async AsyncVityClient Every method returns a promise
Cancellation Cancel the asyncio task signal option on every method (AbortSignal)
Error fields status_code, request_id, retry_after statusCode, requestId, retryAfter, cause
Timeout Applies to each stage of a request Applies to the whole attempt
Your own HTTP layer http_client fetch
Search dates created_at, chat_at, happened_at createdAt, chatAt, happenedAt
Knowledge filter keys snake_case or camelCase camelCase
Closing close() or with close() does nothing
Unknown tool name ValueError RangeError
Logging maximem_vity logger None

Requirements

  • Python 3.9 or later.
  • httpx 0.24 or later, below 1.0. Pip installs it for you.
  • The package ships type information (py.typed).

This is version 0.3.2. The Python and JS packages share one version number. Version 0.3.2 changed only the documentation. What changed in 0.3.1:

  • Added get_profile_card().
  • Search results now include chat_at and happened_at, so dates say when a memory was said and not only when it was stored.
  • Message timestamp is converted to epoch milliseconds. Before, a value in seconds was read as a date in 1970.
  • get_profile() is deprecated.

Coming from 0.2.x:

  • user_id is ignored and will be removed in 0.4.0.
  • channel and agent_id default to "unspecified". Set channel.
  • forget(dry_run=False) with no query needs delete_all=True.
  • search(min_score=0.0) applies no minimum, so you may see more results.
  • Invalid arguments raise VityInvalidInputError before any request is sent.

Links: documentation and API keys.

License

MIT.

Metadata

Release files for maximem-vity-sdk 0.3.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for maximem-vity-sdk 0.3.2
File Size Uploaded Provenance Actions
maximem_vity_sdk-0.3.2.tar.gz 50.4 kB Without provenance Details for maximem_vity_sdk-0.3.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for maximem-vity-sdk 0.3.2
File Interpreter ABI Platform Actions
maximem_vity_sdk-0.3.2-py3-none-any.whl Python 3 none any Details for maximem_vity_sdk-0.3.2-py3-none-any.whl

Total release size: 85.4 kB

Release files / maximem_vity_sdk-0.3.2.tar.gz

Download URL maximem_vity_sdk-0.3.2.tar.gz
Size 50.4 kB
Tags Source
SHA-256 checksum
How to use checksums
56e7afe7b3fa156d5cde02cf3f21d3e41b631e9f5bd59b82cc18b219420fede0
BLAKE2b-256 checksum
How to use checksums
d344e4a287ab804bf47f10896fc1389e4ca14878505d5560e26363619e6d24ef
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Release files / maximem_vity_sdk-0.3.2-py3-none-any.whl

Download URL maximem_vity_sdk-0.3.2-py3-none-any.whl
Size 35.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e33168b195a54418f09964014c7b5a0427c88491b14b02e2b3562ae42740080a
BLAKE2b-256 checksum
How to use checksums
14bbbcbcc208a5e053d62656de028aa79ac540a3820170edc18a03f8185fadba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Security for maximem-vity-sdk 0.3.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page