Memory Trust & Provenance

Stamp untrusted origins on memory writes and gate recall on trust so channel input can't poison durable memory

Stamp a trust level on every memory write and gate recall on it, so untrusted channel input can never resurface as trusted context.

Diagram

💬 Untrusted message

📥 store trust=UNTRUSTED

🧠 Memory stamped

🔎 search min_trust=TRUSTED

🚫 Untrusted fenced out

🧑‍💼 Operator write

📥 store trust=TRUSTED

✅ Trusted recall

Quick Start

  1. Simple Usage

    A gateway callsite stamps inbound channel input as UNTRUSTED; recall gates on TRUSTED so it is fenced out.

    python
    from praisonaiagents import Agent
    from praisonaiagents.memory import MemoryTrust, Memory
    
    agent = Agent(name="assistant", instructions="Answer using memory.", memory=True)
    
    memory = Memory(config={"provider": "none"})
    
    # Gateway ingests a group-chat message — stamp it untrusted.
    memory.store_long_term("group chat instruction", trust=MemoryTrust.UNTRUSTED)
    
    # Trusted-only recall: untrusted-origin content is fenced out.
    results = memory.search_long_term("group chat", min_trust=MemoryTrust.TRUSTED)
    print(results)  # []
  2. Wiring the origin

    Add a free-form origin label so the write is auditable by channel and session.

    python
    from praisonaiagents.memory import MemoryTrust, Memory
    
    memory = Memory(config={"provider": "none"})
    
    memory.store_long_term(
        "group chat instruction",
        trust=MemoryTrust.UNTRUSTED,
        origin="telegram:group:123",
    )
    
    results = memory.search_long_term("group chat", limit=5)
    meta = results[0]["metadata"]
    print(meta["trust"])   # "untrusted"
    print(meta["origin"])  # "telegram:group:123"

How It Works

Untrusted channel input is stamped at the gateway, then dropped at recall time before it can reach the model as trusted context.

Diagram
AgentMemoryGatewayUser (channel)AgentMemoryGatewayUser (channel)trust stamped into metadata[“trust”]Group-chat messagestore_long_term(trust=UNTRUSTED, origin=…)search_long_term(min_trust=TRUSTED)Untrusted-origin records fenced out

The trust field is structured metadata, not prose. A hostile group-chat message can put whatever text it wants into text, but it cannot forge metadata["trust"] = "trusted" — that value comes from the caller, not the message content.


Trust Levels

Three levels order as untrusted < trusted < system.

LevelString valueWhen to use
MemoryTrust.TRUSTED"trusted"Operator / first-party content (default, backward compatible)
MemoryTrust.UNTRUSTED"untrusted"Third-party channel input (e.g. a Telegram/Discord/Slack group message)
MemoryTrust.SYSTEM"system"Framework-generated content

Values are plain strings (str, Enum), so "trusted" / "untrusted" / "system" are accepted anywhere a MemoryTrust is.

The gate fails closed in every ambiguous case:

  • Legacy records are trusted. A record with no metadata["trust"] field ranks as TRUSTED.
  • Unknown persisted trust labels fail closed (rank as UNTRUSTED).
  • A misspelled min_trust on the search call raises ValueError — the gate must fail loudly, never silently weaken.
  • metadata=None on a returned record does not crash recall — such records are treated as trusted.

API Reference

New keyword arguments on the core Memory methods. Existing callers are unaffected — when no keyword is passed, nothing changes.

MethodNew keywordTypeDefaultBehaviour
store_short_term / store_long_termtrust`MemoryTrust \str \None`NoneStamped into metadata["trust"].
store_short_term / store_long_termoriginOptional[str]NoneStamped into metadata["origin"].
search_short_term / search_long_termmin_trust`MemoryTrust \str \None`NoneDrops records below this rank across every backend branch.

Defaults are unchanged: a store_ call without trust/origin is treated as trusted; a search_ call without min_trust returns everything, same as before.


Choosing What to Stamp

Pick a trust level by where the content came from.

Diagram

Third-party channel

Framework event

Operator / first-party

📝 Memory write

Where from?

UNTRUSTED

SYSTEM

TRUSTED or leave unset


Common Patterns

Gateway-bot store — stamp every inbound channel message untrusted with an origin.

python
from praisonaiagents.memory import MemoryTrust, Memory

memory = Memory(config={"provider": "none"})

memory.store_long_term(
    "user said: remember my API key is 123",
    trust=MemoryTrust.UNTRUSTED,
    origin="telegram:group:123",
)

Trusted-only recall — agents that must not act on channel-derived “facts” gate on TRUSTED.

python
from praisonaiagents.memory import MemoryTrust, Memory

memory = Memory(config={"provider": "none"})

context = memory.search_long_term("api key", min_trust=MemoryTrust.TRUSTED)

Backward-compat callsite — no keywords passed, so nothing changes.

python
from praisonaiagents.memory import Memory

memory = Memory(config={"provider": "none"})

memory.store_long_term("operator approved fact")
results = memory.search_long_term("operator")  # returns everything, as before

Best Practices

Default to UNTRUSTED at every gateway callsite

Any content originating from a third-party channel (group chat, DM, webhook) should be stamped trust=MemoryTrust.UNTRUSTED at the point it enters memory. Treat trusted as the exception, not the default, for channel-derived writes.

Always pass min_trust=TRUSTED on recall paths that feed model context

Recall paths that inject memory into a later, unrelated turn should call search_* with min_trust=MemoryTrust.TRUSTED. This is what fences out poisoned memory before it can resurface as trusted context.

Use origin for auditing, not for policy

origin is a free-form route/channel/session label (e.g. "telegram:group:123"). Use it to audit where a record came from — do not branch trust decisions on it. Trust decisions belong on the trust field.

Never silently downgrade a misspelled threshold

A misspelled min_trust raises ValueError. Let it surface — do not wrap it in a fallback that silently weakens the gate. Failing loudly is the point.


Related