Official Python SDK for the UNDR9 graph-memory database.
Project description
UNDR9 Python SDK
The official Python client for UNDR9, a graph-native memory database for AI agents, provides sync and async APIs for:
- node CRUD
- edge CRUD
- graph query execution
- typed property filtering
- vector search
- temporal search
- ranked retrieval
- typed query streaming
- transaction lifecycle and staged transactional queries
- admin maintenance helpers
- replication and cluster helpers
- observability helpers for health, readiness, and metrics
The async client uses a real async HTTP transport and supports async context management for clean connection shutdown.
Install
pip install undr9
The package is published on PyPI as undr9.
For release steps and Trusted Publishing setup, see docs/releasing.md.
Retrieval Philosophy
UNDR9 is built for memory retrieval, not only record storage. The core idea is that useful memory in applications usually depends on more than one signal at a time:
- semantic similarity tells you what is related
- graph structure tells you what is connected
- recency tells you what is still fresh
- importance tells you what should stay salient
- confidence tells you how much the system should trust what was stored
This is influenced by practical memory systems and by how human memory is often described in applications: recall is shaped by association, recency, salience, and certainty rather than by a single keyword match. UNDR9 does not try to be a neuroscience model. It turns those ideas into a simple, explicit retrieval model that applications can reason about.
Why this matters:
- agents usually need more than vector similarity to recover the right memory
- recent but low-value memories should not always outrank older critical ones
- highly connected memories often matter because they sit near the current context
- uncertain memories should not be treated the same as well-grounded ones
Ranked Retrieval Formula
The default ranked retrieval profile in UNDR9 is v1-default. It computes a weighted score from
five normalized components:
score =
0.30 * structural +
0.30 * semantic +
0.15 * temporal +
0.15 * importance +
0.10 * confidence
Where:
structural: graph-distance score from a reference node when one is providedsemantic: cosine similarity over the selected named vector spacetemporal: recency score derived from the nodetimestampimportance: normalized nodeimportancesignalconfidence: normalized nodeconfidencesignal
In the current implementation:
- semantic similarity is normalized cosine similarity
- temporal recency uses a seven-day half-life style decay
- missing
importanceorconfidencedefault to a neutral midpoint instead of zero - ranked retrieval can union semantic candidates with structural candidates before reranking
This is why the optional built-in node properties timestamp, importance, and confidence are
worth storing whenever you have them. They give the ranking model more useful memory signals than
vector similarity alone.
Quick Start
import os
from undr9 import AsyncUndr9Client, PropertyValue, SyncUndr9Client
base_url = os.environ.get("UNDR9_SDK_BASE_URL", "http://127.0.0.1:8080")
writer = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_WRITER_API_KEY"],
user_agent="undr9-python-sdk-example/writer",
headers={"x-demo-flow": "quick-start"},
timeout=10.0,
max_retries=2,
retry_backoff_seconds=0.25,
max_connections=20,
max_keepalive_connections=10,
)
reader = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_READER_API_KEY"],
user_agent="undr9-python-sdk-example/reader",
headers={"x-demo-flow": "quick-start"},
timeout=10.0,
max_retries=2,
retry_backoff_seconds=0.25,
max_connections=20,
max_keepalive_connections=10,
)
node = writer.create_node(
node_id="node_a",
node_type="memory",
properties={
"unique_key": PropertyValue.string("alpha"),
# Optional built-in retrieval properties. If you add them,
# ranked_retrieval() can use them together with vectors and graph signals.
"timestamp": PropertyValue.integer(1000),
"importance": PropertyValue.float(0.9),
"confidence": PropertyValue.float(0.85),
"score": PropertyValue.integer(98),
},
vectors={
"default": [1.0, 0.0],
"title": [0.8, 0.2],
},
)
filtered = reader.filter_nodes(
label="memory",
where={
"op": "gt",
"field": "score",
"value": {"kind": "Integer", "value": 90},
},
limit=10,
)
print(len(filtered.nodes))
Built-in retrieval properties:
timestamp: optional node property in epoch milliseconds. Ranked retrieval uses it for recency.importance: optional node property asFloatorInteger. Ranked retrieval uses it to boost higher-value memories.confidence: optional node property asFloatorInteger. Ranked retrieval uses it to down-rank uncertain memories.
These properties are optional, but it is better to add them on nodes when you have the data
because ranked_retrieval() combines them with semantic and graph signals.
Vector search:
import time
from undr9 import PropertyValue, SyncUndr9Client
writer = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_WRITER_API_KEY"],
)
reader = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_READER_API_KEY"],
)
# Store embeddings in the vectors map.
writer.create_node(
node_id="memory_alpha",
node_type="memory",
properties={
"unique_key": PropertyValue.string("alpha"),
"title": PropertyValue.string("Graph memory basics"),
# Optional built-in retrieval properties. These are especially useful
# when you later call ranked_retrieval().
"timestamp": PropertyValue.integer(1_717_171_717_000),
"importance": PropertyValue.float(0.9),
"confidence": PropertyValue.float(0.85),
},
vectors={
"default": [1.0, 0.0],
"title_embedding": [0.9, 0.1],
},
)
writer.create_node(
node_id="memory_beta",
node_type="memory",
properties={
"unique_key": PropertyValue.string("beta"),
"title": PropertyValue.string("Cluster replication notes"),
"timestamp": PropertyValue.integer(1_717_171_817_000),
"importance": PropertyValue.float(0.6),
"confidence": PropertyValue.float(0.7),
},
vectors={
"default": [0.2, 0.9],
"title_embedding": [0.1, 0.95],
},
)
# Search one named vector space and inspect the ranked results.
results = reader.vector_search(
[1.0, 0.0],
limit=3,
node_type="memory",
vector_name="default",
top_k=25,
)
best_match = results.ranked_results[0]
print(best_match.node.id)
print(best_match.score)
# Ranked retrieval combines semantic similarity with other retrieval signals.
# If the nodes include timestamp, importance, and confidence, those optional
# built-in properties are also used here.
retrieval = reader.ranked_retrieval(
query_vector=[1.0, 0.0],
vector_name="default",
limit=3,
top_k=25,
now_epoch_ms=int(time.time() * 1000),
)
print(retrieval.ranked_results[0].node.id)
# Clean up sample data if you are running this example repeatedly.
writer.delete_node("memory_alpha")
writer.delete_node("memory_beta")
Use vector_name="default" for your primary embedding space. Use a more specific vector such as
title_embedding when you want retrieval against one slice of a node, for example title-only
similarity instead of whole-document similarity.
Updating vectors on an existing node:
from undr9 import SyncUndr9Client
client = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_WRITER_API_KEY"],
)
node = client.get_node("node_a")
# Add a new named vector.
node.vectors["title_embedding"] = [0.9, 0.1, 0.3]
# Replace the primary vector.
node.vectors["default"] = [1.0, 0.2, 0.4]
# Remove a vector you no longer want to keep.
node.vectors.pop("old_embedding", None)
# Send the full node back to persist the updated vectors map.
updated = client.update_node(node)
print(updated.vectors)
Node CRUD:
from undr9 import PropertyValue, SyncUndr9Client
client = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_WRITER_API_KEY"],
)
# Create a new node.
node = client.create_node(
node_id="node_a",
node_type="memory",
properties={
"unique_key": PropertyValue.string("alpha"),
"title": PropertyValue.string("First memory"),
# Optional built-in node properties. Adding them helps ranked retrieval.
"timestamp": PropertyValue.integer(1_717_171_717_000),
"importance": PropertyValue.float(0.8),
"confidence": PropertyValue.float(0.9),
},
)
# Read the node back by id.
node = client.get_node("node_a")
print(node.properties["title"].value)
# Update properties by sending the full node again.
node.properties["title"] = PropertyValue.string("Updated memory")
node.properties["importance"] = PropertyValue.float(0.95)
node = client.update_node(node)
print(node.properties["title"].value)
# Delete the node when you no longer need it.
client.delete_node("node_a")
Edge CRUD:
from undr9 import Edge, PropertyValue, SyncUndr9Client
client = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_WRITER_API_KEY"],
)
# Create the two nodes first. The built-in retrieval properties are optional,
# but they help ranked_retrieval() if you add them.
client.create_node(
node_id="node_a",
node_type="memory",
properties={
"unique_key": PropertyValue.string("alpha"),
"timestamp": PropertyValue.integer(1_717_171_717_000),
"importance": PropertyValue.float(0.9),
"confidence": PropertyValue.float(0.85),
},
)
client.create_node(
node_id="node_b",
node_type="memory",
properties={
"unique_key": PropertyValue.string("beta"),
"timestamp": PropertyValue.integer(1_717_171_817_000),
"importance": PropertyValue.float(0.7),
"confidence": PropertyValue.float(0.8),
},
)
# Create the edge between the two nodes. Edge properties are normal application
# metadata; ranked retrieval reads the built-in properties from nodes.
edge = client.create_edge(
edge_id="edge_a",
source="node_a",
target="node_b",
edge_type="related_to",
properties={
"timestamp": PropertyValue.integer(1_717_171_900_000),
"weight": PropertyValue.float(0.75),
},
)
# Read the edge by id.
edge = client.get_edge("edge_a")
print(edge.edge_type)
# Update properties by resubmitting the edge.
edge.properties["weight"] = PropertyValue.float(0.9)
edge = client.update_edge(edge)
print(edge.properties["weight"].value)
# Delete the edge when the relationship is no longer needed.
client.delete_edge("edge_a")
client.delete_node("node_a")
client.delete_node("node_b")
Async usage:
async with AsyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_READER_API_KEY"],
timeout=10.0,
max_retries=2,
user_agent="undr9-python-sdk-example/async-reader",
headers={"x-demo-flow": "async-quick-start"},
max_connections=20,
max_keepalive_connections=10,
) as client:
response = await client.vector_search(
[1.0, 0.0],
limit=3,
vector_name="default",
top_k=25,
)
print(response.plan_kind)
Stream usage:
for frame in client.query_stream({"GetNodeById": {"node_id": "node_a"}}):
if frame.frame_type == "meta":
print(frame.plan_kind)
elif frame.frame_type == "node":
print(frame.node.id)
elif frame.frame_type == "end":
print(frame.item_count)
Transaction usage:
tx = client.begin_transaction()
tx.upsert_node(
node,
)
snapshot = tx.query({"GetNodeById": {"node_id": "node_a"}})
print(snapshot.plan_kind)
for frame in tx.query_stream({"GetNodeById": {"node_id": "node_a"}}):
print(frame.frame_type)
commit = tx.commit()
print(commit.committed_lsn)
Admin and replication usage:
status = client.admin_maintenance_status()
print(status.last_operation)
integrity = client.admin_integrity()
print(integrity.manifest_present)
replication = client.replication_status()
print(replication.status.mode)
topology = client.cluster_topology()
print(topology.leader_node_id)
Observability usage:
health = client.health()
ready = client.readiness()
metrics = client.metrics()
print(health.status)
print(ready.status)
print("undr9_requests_total" in metrics)
Advanced transport tuning:
client = SyncUndr9Client(
base_url,
api_key=os.environ["UNDR9_SDK_READER_API_KEY"],
user_agent="undr9-python-sdk-prod/1.0",
headers={"x-request-source": "worker-a"},
http2=True,
follow_redirects=False,
verify=True,
max_connections=50,
max_keepalive_connections=20,
keepalive_expiry=30.0,
connect_timeout=2.0,
read_timeout=10.0,
write_timeout=10.0,
pool_timeout=5.0,
max_retries=2,
retry_backoff_seconds=0.25,
)
Examples shipped in the repository:
examples/sync_end_to_end.pyexamples/async_end_to_end.pyexamples/operational_workflows.py
Live contract test entrypoint:
export UNDR9_SDK_LIVE_TESTS=1
export UNDR9_SDK_BASE_URL=http://127.0.0.1:8080
export UNDR9_SDK_ADMIN_API_KEY=dev-admin-key-000000000001
export UNDR9_SDK_WRITER_API_KEY=dev-writer-key-000000000001
export UNDR9_SDK_READER_API_KEY=dev-reader-key-000000000001
python -m unittest -q tests.test_live_contract
Packaging verification:
python -m build
python -m twine check dist/*
python scripts/verify_dist.py
Notes
- Store embeddings only in the node
vectorsmap. Do not sendproperties.embedding. - Use
timeouton the client constructor to bound blocking HTTP calls. - Use
max_retriesandretry_backoff_secondsto enable bounded retry/backoff behavior. - Use
connect_timeout,read_timeout,write_timeout, andpool_timeoutwhen you need per-phase timeout control instead of one shared timeout. - Use
max_connections,max_keepalive_connections, andkeepalive_expiryto tune HTTP connection pooling for long-lived services. - Use
headers,user_agent,http2,follow_redirects, andverifywhen you need proxy, TLS, or observability-oriented transport customization. - Retries are disabled by default and only apply to safe requests plus query POSTs unless
retry_non_idempotent_requests=Trueis explicitly set. AsyncUndr9Clientalso acceptstimeoutand should be used as an async context manager when possible.- Use separate reader, writer, and admin clients when you want your SDK usage to mirror the server's API-key roles.
- Use
query_stream()when you want typed NDJSON stream frames from/v1/query/stream. - Use
begin_transaction()for snapshot transactions, then stage writes withupsert_node(),upsert_edge(),delete_node(), ordelete_edge(). - Use
transaction_query()andtransaction_query_stream()if you prefer direct transaction-id based helpers instead of the wrapper object. - Use
admin_compact(),admin_backup(),admin_restore(),admin_repair(),admin_rebuild_indexes(),admin_integrity(), andadmin_maintenance_status()for maintenance and integrity workflows. - Use
replication_status(),replication_history(),configure_as_leader(),configure_as_follower(),acknowledge_replica(), andapply_replication_records()for replication workflows. - Use
cluster_topology(),register_cluster_node(),mark_cluster_node_health(), andpromote_cluster_node()for cluster-topology operations. - Use
health(),readiness(), andmetrics()for runtime observability checks. SyncUndr9Clientcan now be used as a context manager and closes its reusable HTTP client on exit.- Use
vector_nameto target a named vector space forvector_search()andranked_retrieval(). - Use
top_kto override the semantic candidate budget when needed. - Use
filter_nodes()for database-side property predicates such aseq,gt,gte,lt,lte,and, andor.
Project details
Release history Release notifications | RSS feed
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 undr9-0.1.1.tar.gz.
File metadata
- Download URL: undr9-0.1.1.tar.gz
- Upload date:
- Size: 30.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c4ea8eb192ac5562640292c57b992fd780472776a5c87446d5de41cc0b641d8d
|
|
| MD5 |
273f71c01d974f4f7f2b8de507959544
|
|
| BLAKE2b-256 |
eab63a4632b419dfdd4fc6b825d62f86564516b9c72edd945bb44f31fe28477c
|
Provenance
The following attestation bundles were made for undr9-0.1.1.tar.gz:
Publisher:
python-sdk-release.yml on undr9/undr9-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
undr9-0.1.1.tar.gz -
Subject digest:
c4ea8eb192ac5562640292c57b992fd780472776a5c87446d5de41cc0b641d8d - Sigstore transparency entry: 1952878721
- Sigstore integration time:
-
Permalink:
undr9/undr9-python@16d6070b5143cc5f86f3227c5f4d35c598dbdb6b -
Branch / Tag:
refs/tags/python-sdk-v0.1.1 - Owner: https://github.com/undr9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@16d6070b5143cc5f86f3227c5f4d35c598dbdb6b -
Trigger Event:
push
-
Statement type:
File details
Details for the file undr9-0.1.1-py3-none-any.whl.
File metadata
- Download URL: undr9-0.1.1-py3-none-any.whl
- Upload date:
- Size: 17.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f9ec8e36bb10e78519e0983db87aa1a8d565fcbd7411866a6479ad5545af09f
|
|
| MD5 |
cbb7b8ee445fa4b1c70ed1f4863c03f5
|
|
| BLAKE2b-256 |
dc8eb15622af477c41e1a70fbeb1295010f574f3168f5c0dc831b66d3fba4c72
|
Provenance
The following attestation bundles were made for undr9-0.1.1-py3-none-any.whl:
Publisher:
python-sdk-release.yml on undr9/undr9-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
undr9-0.1.1-py3-none-any.whl -
Subject digest:
6f9ec8e36bb10e78519e0983db87aa1a8d565fcbd7411866a6479ad5545af09f - Sigstore transparency entry: 1952878913
- Sigstore integration time:
-
Permalink:
undr9/undr9-python@16d6070b5143cc5f86f3227c5f4d35c598dbdb6b -
Branch / Tag:
refs/tags/python-sdk-v0.1.1 - Owner: https://github.com/undr9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-sdk-release.yml@16d6070b5143cc5f86f3227c5f4d35c598dbdb6b -
Trigger Event:
push
-
Statement type: