Skip to main content

PersonOS

Any in, memory out — the multimodal long-term memory layer for agents, robots, and smart hardware.

Whatever your agent perceives — conversations, images, live video — goes in; layered, traceable memory comes out. And it is the only open memory framework that watches video and remembers who was in it.

PyPI Python License

English · 简体中文

Quickstart

pip install personos
export PERSONOS_LLM_API_KEY=sk-...
export PERSONOS_LLM_BASE_URL=https://api.openai.com/v1   # any OpenAI-compatible gateway
from personos import Memory

m = Memory()                      # SQLite under ~/.personos; the two env vars above are all it needs

m.add("I moved from Hangzhou to Shanghai in June", user_id="alice", session_id="s1")
m.end_session(user_id="alice", session_id="s1", sync=True)   # wait for the queue to drain

print(m.search("where do I live?", user_id="alice").ans.answer)

Not sure what your configuration can do? personos doctor reads it and tells you what works, what is off, and what to set.

Why another memory library

Agents are leaving the chat box. A robot arm, a pair of smart glasses, a desktop copilot — they all perceive continuously, in more modalities than text, and they need memory that keeps up. Most frameworks store a flat list of facts and search it. PersonOS keeps a layered, append-only record and refuses to resolve contradictions at write time:

evidence  ──►  memcell (episode)  ──►  atom  ──►  atom_chain
raw turns      the narrative unit     the          the same fact over time,
never edited   used for answering     retrieval    grouped, never collapsed
                                      anchor

Two consequences that show up in practice:

Contradictions survive. "15 fish" → "actually 13" → "actually 11" stay as three linked atoms with their timestamps. The answer layer decides what is current; the memory layer never silently overwrites the past. Ask "how many fish do I have" and you get the current count; ask "did that change" and the history is still there.

Answers cite episodes, not fragments. Atoms are the retrieval index — short, self-contained propositions that embed well. The narrative episode is what the model actually reads. Retrieval granularity and answering granularity are deliberately different, because what makes a good search key makes a poor answer.

Recall shows its work

search() returns the answer and how it got there: the rewritten query, the atoms retrieved, the materials ranked, the adjudication verdicts, whether it escalated to the deep agent. When an answer is wrong, you can see which stage went wrong instead of guessing.

out = m.search("how many fish?", user_id="alice")
out.ans.answer        # the answer
out.rw.subject        # who the question was resolved to be about
out.hits              # atoms retrieved, in fusion order
out.reviews           # what the adjudicator said about the draft
out.to_public()       # ...or a plain dict, if you just want the answer

Video and person identity

Every other open memory framework is text-only, or turns an image into a caption at ingest. PersonOS takes video clips — the stream a robot or a pair of glasses actually lives in — and builds stable character entities from faces, body shots and voiceprints: someone recognised in clip 12 is the same person three sessions later, without anyone enrolling them first.

Optional (pip install personos[identity], ~2 GB of model dependencies). The text core imports none of it.

Installation

The core is deliberately small — 8 dependencies, no torch, no langchain, no web framework. Everything heavier is an extra you opt into:

Install Unlocks
pip install personos Text memory: layered write path, fast + deep recall, profiles
personos[deep] The multi-step deep-recall agent (recommended)
personos[image] Image ingest + recall over photos
personos[identity] Video: face / body / voiceprint character identity (~2 GB)
personos[mysql] personos[redis] Multi-process deployments
personos[oss] Object storage instead of local media files
personos[all] Everything

Model weights

Optional capabilities download their own weights on first use — no manual setup:

Capability Weights Where they land
Face recognition InsightFace buffalo_l (~300 MB) ~/.insightface (auto-download)
Voiceprints SpeechBrain spkrec-ecapa-voxceleb HuggingFace cache; override with PERSONOS_ECAPA_MODEL / PERSONOS_ECAPA_DIR
Multimodal understanding none — remote API PERSONOS_MLLM_* points at any MLLM endpoint

Behind the GFW or on an offline box: set HF_ENDPOINT=https://hf-mirror.com for HuggingFace, or pre-download and point the *_DIR variables at local copies.

Configuration

Everything except the model endpoint is optional. Unset means a capability is off or degraded, never that the text path breaks.

Default Unset means
Chat + embeddings — required
Database SQLite at ~/.personos set PERSONOS_DB_URL for MySQL — needed only for several workers
Multimodal model off images are stored but contribute nothing to retrieval; video is refused with instructions
Media storage local files set PERSONOS_MEDIA_BACKEND=oss for object storage
Reranker off retrieval keeps its fusion order
Deep recall off pip install personos[deep] for the multi-step agent
Redis off single process; session state is in memory
Tracing off every tracing call is a no-op

See .env.example for the full list with explanations.

Errors tell you what to do

Ask for something unconfigured and you get a sentence, not silence:

MissingCapability: video understanding is unavailable: no multimodal model is configured
  To enable it: set PERSONOS_MLLM_API_KEY and PERSONOS_MLLM_MODEL

The rule is: cannot do it at all → raise; did it partially → return and say so in result.warnings. A missing optional capability never fails a write.

Examples

One story in three chapters: a week of conversation (watch the profile build itself between days) · with a photo · with video and person identity. See examples/README.md for what each needs and real output.

API

m.add(messages, user_id=..., session_id=...)   # text, a dict with an image, or video clips
m.end_session(user_id=..., session_id=...)     # close the segment and build memories
m.search(query, user_id=..., mode="auto")      # "auto" | "fast" | "deep"
m.profile(user_id=...)                         # distilled user profile
m.trace(node_id, user_id=...)                  # provenance, both directions
m.capabilities()                               # what this configuration can do
m.reset(user_id=...)                           # delete one user's data

Writes are asynchronous by default. add()/end_session() enqueue onto a per-session ordered queue (the same machinery the server deployment uses — FIFO per session, fair scheduling across sessions, backpressure when a session is overloaded) and return an AddReceipt immediately, so memory writes never block your application's own work:

receipt = m.add(..., user_id=..., session_id=...)   # returns at once
# ... your code keeps running; a background dispatcher builds the memories ...

m.flush(user_id=..., session_id=...)                # or: wait until the queue drains
m.end_session(..., sync=True)                       # or: close and wait in one call

Pass sync=True to add()/end_session(), or call flush(), whenever you need to read your own writes. queue_status() reports a session's depth and cursor. A full queue raises QueueBusy — the same contract the HTTP API expresses as 503 + Retry-After.

Reads (search, profile, trace) are synchronous. There is no AsyncMemory yet, and rather than pretend, the honest workaround is await asyncio.to_thread(m.search, q).

Running it as a service

server/ is a FastAPI deployment — ordered ingestion across processes, backpressure, token-scoped multi-tenancy. It is not part of the pip package; it has its own dependencies and lifecycle.

If you are embedding memory in an application, you do not need it.

Status

0.1.0 — the memory pipeline has been running in a production deployment; the packaging around it is new, and the public API may still move before 1.0. Releases follow RELEASE.md.

Contributing

Issues and PRs are welcome at github.com/2049lab/personos. Run the suite with pytest; it needs no external services.

License

Apache-2.0. See LICENSE.

Metadata

Release files for personos 0.1.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 personos 0.1.2
File Size Uploaded
personos-0.1.2.tar.gz 538.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for personos 0.1.2
File Interpreter ABI Platform
personos-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 855.8 kB

Release files / personos-0.1.2.tar.gz

Download URL personos-0.1.2.tar.gz
Size 538.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2054e19c9b07cf299ccfe02efc50229b2cf0a53f5b8dff3ee93e99d1b37872e8
BLAKE2b-256 checksum
How to use checksums
2c044cb0c2952e988b30fa5de5d146692d8d7e93f542f44158464460986a565c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release files / personos-0.1.2-py3-none-any.whl

Download URL personos-0.1.2-py3-none-any.whl
Size 317.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8d544a0843c30fb0cea0fb08131861312578a5426a117eb253f769abb5b84d61
BLAKE2b-256 checksum
How to use checksums
ebbb4ed36467850dd0c00eef8312daf9b0f13655724dddfd50dd1c76b3b647c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.14

Release history Release notifications | RSS feed

0.1.3

2 release files

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

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