Retrieve-time query appraisal¶
By default the affect-proximity signal (s3) in retrieval scores each memory against the engine's current runtime affective state. When the query itself carries affect that the runtime state does not reflect, you can score against the query's own affect instead — without mutating the runtime state.
This is the production-reachable mechanism studied in Addendum T: appraising the query
at retrieve-time recovers most of the oracle-affect advantage on the curated
realistic_recall_v2 benchmark, with no oracle. See the scope caveat at the end.
This tutorial covers four usage patterns:
- Pass an explicit
query_affect - Auto-appraise the query (
retrieve_with_query_appraisal) - Pair with
DIRECT_VAD_SCHEMA(recommended) - Gate neutral queries to cosine (
retrieve_query_gated)
1. Pass an explicit query_affect¶
retrieve() and retrieve_with_explanations() accept a keyword-only
query_affect: CoreAffect | None. When provided, it feeds the affect-proximity
signal instead of the runtime state — the runtime state is left untouched.
from emotional_memory import EmotionalMemory, InMemoryStore
from emotional_memory.affect import CoreAffect
em = EmotionalMemory(store=InMemoryStore(), embedder=my_embedder)
# ... encode some memories ...
# Score against the query's affect (e.g. an anxious, negative-valence query),
# regardless of the engine's current mood.
results = em.retrieve(
"I'm worried I forgot something important before the trip",
top_k=5,
query_affect=CoreAffect(valence=-0.6, arousal=0.7),
)
query_affect=None (the default) preserves the previous behaviour (uses the runtime
state), so this is fully backward-compatible.
2. Auto-appraise the query¶
If you have an appraisal engine configured, retrieve_with_query_appraisal()
appraises the query text for you and uses the result as query_affect — no oracle,
no state mutation:
results = em.retrieve_with_query_appraisal(
"I'm worried I forgot something important before the trip",
top_k=5,
)
It raises RuntimeError if no appraisal engine is configured. Both methods exist on
AsyncEmotionalMemory as well (awaitable).
3. Pair with DIRECT_VAD_SCHEMA (recommended)¶
Addendum V found that having the LLM rate valence/arousal/dominance directly
estimates query affect more faithfully than the default Scherer SEC→projection. Pair
the convenience method with an LLM appraisal engine using DIRECT_VAD_SCHEMA:
from emotional_memory import DIRECT_VAD_SCHEMA
from emotional_memory.appraisal_llm import LLMAppraisalConfig, LLMAppraisalEngine
from emotional_memory.llm_http import OpenAICompatibleLLMConfig, make_httpx_llm
cfg = OpenAICompatibleLLMConfig.from_env()
appraisal_engine = LLMAppraisalEngine(
llm=make_httpx_llm(cfg),
config=LLMAppraisalConfig(appraisal_schema=DIRECT_VAD_SCHEMA, cache_size=4096),
)
em = EmotionalMemory(
store=InMemoryStore(),
embedder=my_embedder,
appraisal_engine=appraisal_engine,
)
results = em.retrieve_with_query_appraisal("...", top_k=5)
The cache means each distinct query text is appraised once.
4. Gate neutral queries (retrieve_query_gated)¶
Addendum Y is a validated safe wrapper around the same retrieve-time path:
appraise the query, then route |valence| < tau (default 0.2) to
semantic-only weights and everything else to retrieve_with_query_appraisal.
Runtime affective state is not mutated. Requires an appraisal engine.
# Factual / affect-free query → cosine arm
results = em.retrieve_query_gated("What is the capital of France?", top_k=5)
# Distressed query → affect-conditioned arm (same as retrieve_with_query_appraisal)
results = em.retrieve_query_gated(
"I'm worried I forgot something important before the trip",
top_k=5,
)
Tune the threshold with EmotionalMemoryConfig.query_affect_gate_tau or the
keyword-only tau= argument. The gate recovers the neutral-query slice of
the off-regime penalty; it does not fix counter-congruent or content-determined
gold (Addenda X/X2). See the configuration guide.
Scope and honest caveat¶
Retrieve-time query appraisal helps in the affect-discriminative regime — where
the correct memory is distinguished by affect, not just lexical/semantic overlap. On
the curated realistic_recall_v2 benchmark it beats cosine (Addendum T, Ht1 PASS).
A pre-registered naturalistic re-test (Addendum T2A) found it does not beat cosine on naturalistic, affect-sparse dialogue (DailyDialog): the query appraisal there is faithful (valence r=0.69, arousal r=0.74) yet the affect signal does not discriminate when semantic overlap already carries it. Treat query appraisal as a targeted booster for affect-conditioned retrieval, not a universal win.