TL;DR
TEM is a memory scheme for agents. When a fact about the user changes (they moved, switched jobs, changed plans), the old value isn't erased. It's kept alongside the new one, all versions are lined up in the order they appeared, and the outdated ones get a [historical] tag. The latest version stays untagged. The model receives a ready-made “timeline slice” with a hint about what's current and what's already in the past.
The core problem: keeping old values isn't enough. Hand the model every version in a row and it mixes up “now” and “before.” It treats the old address as the current one, answers about the wrong change, or grabs the wrong fact altogether. The authors compared three setups with identical facts and identical order. If you overwrite old with new, the model can barely answer questions about the past. If you keep everything without tags, it does better, but inconsistently. Add nothing but the [historical] tag, and accuracy jumps several-fold on every model. Relying on “the hint is somewhere in the text” doesn't help: you have to walk the model to the answer explicitly.
The method has three parts: store versions, order them by source, and tag the outdated ones. For you, the last part is what matters: an explicit status tag right next to the fact works as a data-presentation rule. You can use it by hand in a chat or in an agent's memory file, no code required.
Want this for your own case? Apply this study to your task: AI adapts its method to what you are working on and writes the prompt.
Method Blueprint
WRITE: new fact → add as a new version (do NOT erase the old one)
each record = subject + relation + value + source (session/date)
versions of one “subject+relation” = one group
STATUS: in a group, the version with the latest source = current (no tag)
all others = [historical]
PRESENT: pick the relevant facts (up to 15)
→ sort by source order (oldest → newest)
→ put [historical] on the outdated ones
→ hand them to the model together with the question
ANSWER: the model picks the right version from the wording of the question
(“now” → untagged, “before the move” → the previous one)
In the original this is a software system: code extracts facts and searches over them. But the final “chunk of context” the model sees is just text, and you can assemble it by hand.
Example
Task: You keep notes in a ChatGPT project on a client who runs a home-goods store. Over the year they switched marketplaces, ad budgets and contractors. You ask: “Why did they leave Amazon, and what are they doing now?” Without markup, the model scrambles the order or treats the old value as current.
Prompt:
<Memory>
Facts about the client “Cozy Home Co.”, in order of appearance (oldest to newest).
Tag [historical] = value was replaced by a later one.
No tag = current value.
- Main sales channel | Amazon | source: call Feb 12 [historical]
- Main sales channel | Etsy | source: call Jun 20 [historical]
- Main sales channel | Walmart Marketplace + own Shopify store | source: call Oct 3
- Ad budget | $3,000/mo | source: call Feb 12 [historical]
- Ad budget | $8,000/mo | source: call Jun 20 [historical]
- Ad budget | $4,500/mo | source: call Oct 3
- Reason for leaving Amazon | rising fees and inbound-shipment penalties | source: call Jun 20
</Memory>
Answer only from <Memory>. If the question is about “now,” use the untagged values.
If it's about the past, pick the version that was in effect at that moment.
Never present a historical value as the current one.
Question: Where did the client sell before Etsy, why did they leave, and what is their ad budget now?
Result: The model should answer all three parts of the question: the previous channel (Amazon), the reason for leaving (from the untagged record about the reason), and the current budget (the untagged record, $4,500). It should not pass off the historical value ($8,000) as the current one.
Why it works
The model's weakness. When the context holds several values of the same fact, they all look “semantically similar” to the question. The model has to decide on its own which one is current and which isn't. Record order doesn't always help: versions can be shuffled, and words like “now” and “before the move” require matching the question against a chain of events. So it slips up, especially smaller models.
The model's strength. Models are good with explicit labels in the text. If [historical] sits next to a fact, the model doesn't have to work out the status. It just reads the tag and matches it to the question. Picking the right version for the question is something it does well on its own.
How the method exploits this. TEM splits two jobs: what to show (relevant facts) and how to present it (in order, with a status tag). Code computes the status with a simple rule: anything that isn't the latest in its group is historical. So even if the fresh version didn't make it into the retrieved set, the old one still carries its tag and doesn't pose as current.
Control knobs: - Tag → swap it for “outdated,” “replaced by X as of date,” or add a date. Same idea: the status is visible at a glance. - Order → oldest to newest, as in the original. You still need the tag: in the experiment the order was identical, and the difference came from the tag alone. - Fact limit → up to 15 in the original. A short slice is cheaper and doesn't overload the model. - Rule in the instructions (“use untagged for ‘now’”) → the original had no such rule; the model chose on its own. This is your add-on, so test it.
Prompt template
<Memory>
Facts about {subject}, in order of appearance (oldest to newest).
Tag [historical] = value was replaced by a later one.
No tag = current value.
- {parameter_1} | {value_v1} | source: {date/session_1} [historical]
- {parameter_1} | {value_v2} | source: {date/session_2} [historical]
- {parameter_1} | {value_v3} | source: {date/session_3}
- {parameter_2} | {value} | source: {date/session}
</Memory>
Answer only from <Memory>.
If the question is about “now,” use the untagged values.
If the question is about the past, pick the version that was in effect at that moment.
Never present [historical] as current.
Question: {question}
Fill in:
- {subject} — a client, project, employee;
- {parameter} — whatever changes (address, plan, tech stack, job title);
- versions and dates — from your notes.
Rule: in each group (one subject + one parameter) every version except the latest gets the tag. The latest one stays untagged.
🚀 Quick start — paste into your chat:
Here is a timeline-memory template with [historical] tags. Adapt it to my task: [your task].
Ask me questions to fill in the fields.
[paste the template above]
The LLM will ask what changes over time in your case, which versions and dates you have, and what questions you plan to ask. It needs that to build the version groups correctly and place the tags. Then it assembles the <Memory> block from your notes.
Limitations
⚠️ Unambiguous updates only: the method assumes the new value replaces the old one (address, job). If preferences hold at the same time (likes coffee and likes tea), the
[historical]tag becomes a lie.
⚠️ Order = order of recording, not of events: status depends on when the fact entered memory. If someone mentions a move after talking about a more recent event, the order breaks. The authors acknowledge this.
⚠️ Contradictions within a single record aren't resolved: if one session contains two different values, the method won't sort them out.
⚠️ Tags barely helped on open-ended conversational questions: on a public benchmark with questions about event details and links between facts, results with and without tags were practically the same. The tag helps specifically with “which version of the fact is current.”
⚠️ Weak models and synthetic data: tested on small open models (4–20B parameters) and on generated dialogues. With strong models the gap may be smaller.
⚠️ The full system needs code: automatic fact extraction and search are software. By hand you can only replicate the presentation.
⚠️ Comparison with other memory systems isn't apples to apples: the authors themselves note that the generators and metrics behind the scores differ.
How they tested it
The authors generated 60 fictional personas and 600 dialogues in which people changed city, job and favorite drink. Then they wrote 420 questions of three kinds: “where do they live now,” “where did they live before,” and “where did they live before the last move.” They tested four open models and six memory-storage approaches, about 10 thousand answers in total.
The key design is an ablation (researchers remove one detail and see what changes). They took the same facts in the same order and compared two variants: with the [historical] tag and without. That ruled out the effect of retrieval and ordering: only the tag changed. Result: on questions about the past, accuracy rose to 97–100%. Without tags, with the same content, it was 42–82%. Simply overwriting old with new gave 0–8%.
Interestingly, the tags lifted “ordinary” questions too: “where do they live now” also improved. Without labels, models aren't sure even about the current value when old ones sit next to it. Another plus: the presented context averaged 94–118 tokens, roughly half of what RAG uses when it stuffs chunks of the original dialogues into context.
To be fair, the authors also tested the method on the public LoCoMo set (1,540 questions). Hybrid memory beat plain RAG on the same budget, but the tags made almost no difference there. Takeaway: the tag solves version selection, and the rest of the gain there came from how the context was organized.
Adaptations and extrapolations
🔧 Technique: add the event date → remove the method's main limitation
This is my adaptation; it's not in the paper. The authors admit that recording order isn't event order. Add two dates in your notes:
source: call Oct 3 | effective since: Sep 1. Then delayed reports won't confuse the model. Test it on your own data.
Extrapolation: a rule for an agent's memory file (not from the paper, but it follows from its finding). In CLAUDE.md/AGENTS.md or project memory:
## Project memory rules
- Do NOT delete decisions and parameters that have changed.
- Tag the old record [historical] and add: “replaced by: {what} as of {date}”.
- The current record has no tag and sits at the bottom of its group.
- When answering about “now,” use only untagged records.
- When answering “why did we previously…,” look for [historical] records.
This way the agent keeps the decision history (why you dropped database X) and doesn't mistake a reversed decision for an active one.
Resources
- TEM: Temporal State Tracking Memory for Large Language Model Agents — Pengfeng Song, Yunkai Zhuang, Liye Shi (Zhejiang Wanli University, Ningbo, China).
- Code: https://github.com/sungkites/TEM
- Benchmarks used: LoCoMo, LongMemEval, LongBench.
- Memory systems compared: MemGPT, MemoryBank, A-MEM, Mem0, MemoryOS, Zep.