Skip to main content

PersonaTrace

License: MIT Python 3.10+ MCP

A memory layer for what is still true, what is due, and why it was stored.

PersonaTrace is not a chatbot or an agent harness. It is a small memory kernel that a host can call. The host owns the model, the prompt, the UI, the tone, and any proactive behavior.

The unit of memory is not a fact. It is a fact that holds for a while: it has a validity window, a retention curve, evidence, and optionally a due date. A separate intention store handles one-off commitments whose lifetime is measured in hours or days, not in months.

What it remembers

Type Examples Lifetime
Belief preferences, identity, constraints, projects, goals, habits retention curve per category; archived, not deleted
Intention "class the day after tomorrow", "submit the report Friday" due + grace; done / cancelled / expired
Episode the raw user/assistant turn SQLite; time-window + lexical retrospective search
Routine "attend class every Tuesday at 19:00" weekly recurrence + term/exceptions + probe state
Evidence the sentence a belief came from kept with the belief; read only after a hit
Mention "someone said he is preparing for an exam" separate from first-hand beliefs; decays faster

The merged path uses 17 belief categories across two tracks:

work:    explicit_facts expertise projects goals likes dislikes constraints
persona: identity traits interests style_notes behavior_patterns
         decision_style interpersonal boundaries habit state

How retrieval works

query
  ├─ pinned: boundaries / constraints / identity
  ├─ semantic: lexical IDF + vector cosine (candidate-set normalized)
  ├─ time facet: due/range rows, only for time-intent questions
  ├─ cross-category: items from categories not already selected
  └─ evidence on demand: reasoning + original sentence for the first hits

score = (0.15 + 0.85 * similarity) * (0.35 + 0.65 * retention)

A time question is not answered by a better embedding. It is answered by putting the date into a typed field and querying that field. The facet is a conditional bypass, not a second retriever.

Install

# Recommended: Chroma + an embedding model for semantic memory
pip install "personatrace[vector]"

# Minimal install: lexical/time fallback only
pip install personatrace

Python 3.10+. The core belief kernel uses the standard library. The merged facade uses Pydantic; the default local store is SQLite. Redis is optional.

SQLite owns the episode rows and all state. Chroma is the recommended candidate index for semantic retrospective search; install it unless you explicitly want the lexical/time-only fallback. The embedding model is what makes semantic recall work; Chroma is the index, not the source of truth.

from personatrace.chroma_index import ChromaEpisodeIndex

index = ChromaEpisodeIndex(
    path="./persona_data/chroma",
    embed_fn=embed_fn,          # list[str] -> list[list[float]]
)
memory = MergedMemory(
    user_id="alice",
    sqlite_path="./personatrace.db",
    vector_index=index,
)

If Chroma is unavailable, EpisodeStore falls back to lexical + time search. That fallback is intentionally usable, but it is weaker for paraphrases and larger episode archives. The v1 persona_items collection is separate; episodes use episode_turns.

Quick start: structured memory

The host extracts beliefs/intentions; PersonaTrace stores, ranks and expires them. This path does not call an LLM.

from personatrace import MergedMemory

memory = MergedMemory(
    user_id="alice",
    sqlite_path="./personatrace.db",   # one file for the whole memory
)
# Omitting sqlite_path is also durable now: beliefs, intentions, episodes and
# routines all follow the default backend path.

memory.write(
    beliefs=[{
        "category": "identity",
        "label": "Name is Mo",
        "confidence": 0.9,
        "reasoning": "user introduced themselves",
    }],
    intentions=[{
        "action": "attend class",
        "due": "2026-10-25",
        "importance": 0.9,
        "confidence": 0.8,
    }],
    source="direct",
)

context = memory.build_context("When is my deadline?")
print(context.agenda_lines)
print(memory.inject("When is my deadline?"))
  • build_context() returns structured data and has no side effects.
  • inject() is an optional prompt adapter; the host can render the pack itself.
  • source must be one of direct / bridge / console; proactive turns must not write user facts.

Turn and routine

turn = memory.record_turn("I have class every Tuesday at 19:00", session_id="s1")
memory.label_routine(
    "attend class",
    time="19:00",
    weekdays=[2],              # Monday=1 ... Sunday=7
    anchor_date="2026-10-20",
    valid_until="2026-12-20",  # semester end
    status="candidate",
)
pack = memory.build_context("Are we talking?")
print(pack.routine_probes)     # hints only; the host decides whether to ask

A routine is weekly, not daily. One observation creates a candidate; explicit label_routine or a confirmation makes it weekly. skipped_today, denied and silence are separate states.

MCP

The server exposes thirteen tools. The merged tools use SQLite by default, so no Redis or server setup is required.

{
  "mcpServers": {
    "personatrace": { "command": "personatrace-mcp" }
  }
}
Tool Purpose
memory_state current or historical beliefs
memory_recall pinned + ranked beliefs, with hidden count
memory_observe write one evidence-backed belief
memory_correct invalidate a belief without deleting history
memory_predict next action from the behavior sequence
memory_remember write already-extracted beliefs / intentions
memory_context structured pinned/dated/related/cross/agenda pack
memory_agenda read-only short-term agenda
memory_turn store one raw turn; returns an episode id
memory_episodes retrospective raw-turn search by topic and time
memory_routine_probe read-only in-turn routine probe candidates
memory_routine_feedback confirmed / denied / skipped_today / unanswered / ended
memory_routine_label explicit weekly routine with term/exceptions

Every tool call runs in one isolated worker process with an 8s deadline. A timeout kills the worker, so a slow call cannot keep writing behind the error result or exhaust a shared thread pool; the next call starts a fresh worker. In-memory mode (PERSONA_DB=off) runs in-process and is tests-only. JSON-RPC batch arrays are accepted, and malformed messages return an error instead of crashing the stdio loop. Both tool families use one PERSONA_USER_ID and one store, so memory_observe is visible to memory_context.

See MCP.md.

Evaluation

A 200-belief, 50-query fixture is included. It is a directional gate, not a benchmark. The earlier 12/10 fixture scored 90.0%; it was too easy.

method                      recall@3     MRR
v2 native (query, no pinned)    66.0%   0.707
v2 native (pinned)              52.0%   0.290
outer lex                       61.0%   0.637
outer + bge                     74.0%   0.770
outer + bge + time facet        76.0%   0.760

v2 native (query) is the kernel's own lexical channel with no outer layer. pinned is capped at half the budget (max(1, k // 2)), so constraints and boundaries cannot consume the whole query budget. The useful comparison is outer + bge → + time facet: +2.0 recall but -0.010 MRR. The time bucket can promote a due item above an equally correct semantic item. The larger fixture is the number to quote; CI gates the deterministic outer lex channel at 60%.

python scripts/eval_recall.py
python scripts/eval_recall.py --no-embed
# CI gate (deterministic, no embedding model required)
python scripts/eval_recall.py --no-embed --fail-under 0.60 --gate-method "outer lex"

LongMemEval temporal-reasoning subset

First external run, not the full benchmark and not GPT-4o:

133 questions (temporal-reasoning)     101 correct   75.9%
20 questions (first run)                 17 correct   85.0%
20 questions (timed run)                 16 correct   80.0%
retrieval hybrid+rerank (133)           recall@3 86.8%, MRR 0.935
build 32,930 episodes                   1,259.9 s; reuse 2.5 s

Answerer and judge are both deepseek-flash at temperature 0, so self-judge bias applies. The bilingual episode hook opens on 132/133 questions (20/20 on the smoke set). The merged-context gate and the retrieval numbers are explained in LONGMEMEVAL.md.

Design notes

The full failure analysis, measurements, tradeoffs and pitfalls are in DESIGN_NOTES.md. The temporal kernel is described in MEMORY_V2.md. The routine/vector research note is ROUTINE_MEMORY_RESEARCH.md. Release history is in CHANGELOG.md; the v1 → 2.x import/behavior changes are in UPGRADING.md.

What is shipped

Area Status
v2 kernel: retention, bi-temporal state, evidence, CAS guard shipped
SQLite persistence by default shipped
17-category belief contract + Chinese/English typed time shipped
outer retrieval + time facet + cross-category shipped
short-term intentions with importance / TTL / anti-nag shipped
conservative write-time dedup shipped
MergedMemory facade + MCP tools shipped
episodes + retrospective search shipped (1.6.5)
weekly routines + in-turn probes shipped (1.6.5)
v1 → v2 migration script shipped; dry-run by default
runtime v1 pipeline removed in 2.0; migration shim only
CI: tests on Windows/Linux + recall gate shipped

Known limits:

  • single user; no multi-tenant or auth;
  • no UI, persona, or proactive messaging;
  • dedup is lexical and conservative; numbers/months must match, and a low-overlap paraphrase can still create a second record;
  • 50 queries are still a small evaluation set;
  • routines are weekly fields, not a full RFC 5545 RRULE parser yet;
  • episode retrieval falls back to lexical/time when Chroma is unavailable; Chroma requires an embedding function
  • routine probes are hints; the host must ask and must report feedback;
  • there is no background scheduler and no automatic proactive message;
  • old Chroma profiles must be migrated with scripts/migrate_v1.py; the runtime v1 pipeline is no longer shipped.

License

MIT

Metadata

Release files for personatrace 2.0.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 personatrace 2.0.2
File Size Uploaded Provenance
personatrace-2.0.2.tar.gz 99.7 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 201.1 kB

Release files / personatrace-2.0.2.tar.gz

Download URL personatrace-2.0.2.tar.gz
Size 99.7 kB
Tags Source
SHA-256 checksum
How to use checksums
2d00ab63b822e71573fd47c73b0e0c2edf446d63a86e74a16a5fa61e76ea0a83
BLAKE2b-256 checksum
How to use checksums
ac11a8c90b63fa1f74d0801785f63f2235bcfee00ecb987c06755836f9299f4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release files / personatrace-2.0.2-py3-none-any.whl

Download URL personatrace-2.0.2-py3-none-any.whl
Size 101.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e40aba50b69a5136cc5222484e1b2c388f5edb7b4ffdc2bed380350a4625d53a
BLAKE2b-256 checksum
How to use checksums
eda488ed8c520c4c8b5ad69a44939e861f6fc57a5de32734cdc86a4d759c87a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Security for personatrace 2.0.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