Skip to content

Repository files navigation

SessionAnchor 🕰️

Temporal context grounding for LLM sessions.

Prevents language models from carrying stale time-based assumptions when conversations resume after a gap.


The Problem

When you end a chat session at 7pm on Monday and resume it Thursday morning, most LLM applications inject the old conversation history without any temporal grounding. The model may then:

  • Still think it's 7pm Monday
  • Treat a deadline from "tomorrow" as still upcoming — even though it passed 2 days ago
  • Reference "current" context that is days out of date
  • Answer questions about "now" based on the old session's temporal frame

This is not just a cosmetic issue. It causes real errors in scheduling assistants, task managers, coding agents, personal AI tools, and any application where time matters.


The Solution

SessionAnchor wraps every LLM call with three layers of protection:

1. Temporal Snapshot Injection

Every session start captures the exact current time, timezone, and day context. This is injected as a structured block into the system prompt before any history is included:

[SESSION ANCHOR]
The current time is Thursday, March 12 2026, 9:00 AM (America/New_York).
It is Thursday morning.
This session is resuming after a gap — the previous session ended at 2026-03-09T19:00:00.
That was 3 days later.
Any references to 'now', 'today', 'current time', or time-sensitive context
from the previous session should be treated as STALE unless explicitly
re-confirmed in this session.
[END SESSION ANCHOR]

2. Context Decay Scoring

Messages from prior sessions are classified by how quickly they go stale:

Decay ClassExamplesStaleness Threshold
PERMANENT"I prefer concise replies", "my name is Alex"Never stale
SLOW"I'm building a React app", project goals2 weeks
MEDIUM"meeting next Tuesday", deadlines2 days
FAST"see you at 3pm today", time-of-day plans6 hours
EPHEMERAL"I'm in a meeting right now"Immediately after session

Stale messages are either dropped or re-injected with an explicit warning — your choice.

3. Gap-Aware History Filtering

Only relevant, non-stale messages from prior sessions are carried forward. The model is never confused by context it can no longer trust.


Quick Start

pip install session-anchor
fromsession_anchorimportSessionAnchor# Initialize for a useranchor=SessionAnchor(
user_id="user_123",
timezone="America/New_York",
)
# Start a session (auto-detects gap from last session)session_id=anchor.start()
# Build grounded context to send to your LLMmessages=anchor.build_context(
user_message="What were we working on?",
system_prompt="You are a helpful assistant.",
)
# Use messages with any LLM APIimportanthropicclient=anthropic.Anthropic()
# Or use the built-in adapter:fromsession_anchor.adaptersimportto_anthropic_messagessystem, msgs=to_anthropic_messages(anchor, "What were we working on?")
response=client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
system=system,
messages=msgs,
)
# Log the turn (important — enables gap detection in the next session)anchor.log("user", "What were we working on?")
anchor.log("assistant", response.content[0].text)
# End the session when the user is doneanchor.end()

Adapters

SessionAnchor ships with adapters for popular LLM frameworks:

OpenAI

fromsession_anchor.adaptersimportto_openai_messagesfromopenaiimportOpenAIclient=OpenAI()
messages=to_openai_messages(anchor, "Continue where we left off")
response=client.chat.completions.create(model="gpt-4o", messages=messages)

Anthropic

fromsession_anchor.adaptersimportto_anthropic_messagesimportanthropicclient=anthropic.Anthropic()
system, messages=to_anthropic_messages(anchor, "Continue where we left off")
response=client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
system=system,
messages=messages,
)

LangChain

fromsession_anchor.adaptersimportto_langchain_messagesfromlangchain_openaiimportChatOpenAIllm=ChatOpenAI(model="gpt-4o")
messages=to_langchain_messages(anchor, "Continue where we left off")
response=llm.invoke(messages)

Advanced Usage

Custom decay classification

Override the built-in pattern matcher with your own classifier:

fromsession_anchor.decayimportDecayClass, score_message# Manually classify a messagescored=score_message(
role="user",
content="I need to finish this by the sprint deadline",
message_timestamp="2026-03-09T19:00:00+00:00",
current_timestamp="2026-03-12T14:00:00+00:00",
decay_class=DecayClass.MEDIUM, # Override auto-detection
)
print(scored.is_stale) # True (> 2 day threshold)print(scored.staleness_warning)

Include stale messages with warnings

anchor=SessionAnchor(
user_id="user_123",
timezone="UTC",
include_stale_messages=True, # Include stale, but flag them
)

When stale messages are included, they're prepended with:

[STALE CONTEXT — FAST] The following message may no longer be accurate:
Content classified as 'fast' with 6h threshold; message is 74.2h old.
I need to finish this before my 3pm meeting

Inspect the temporal snapshot

anchor.start()
snap=anchor.snapshotprint(snap.local_time_human) # "Thursday, March 12 2026, 9:00 AM"print(snap.gap_label) # "3 days later"print(snap.gap_seconds) # 259200print(snap.grounding_note) # Full text injected into the LLM promptprint(anchor.get_gap_summary()) # Human-readable summary

Custom storage backend

The default SessionStore writes JSON files to .session_anchor/. Swap it out:

fromsession_anchor.memory.storeimportSessionStore# The store interface is simple — implement these methods for any backend:# create_session(), end_session(), get_last_ended_session()# append_message(), get_messages(), get_envelope()classRedisSessionStore(SessionStore):
def__init__(self, redis_url: str):
importredisself.r=redis.from_url(redis_url)
# ... implement methods

How It Compares

ApproachTemporal groundingDecay scoringHistory filteringFramework support
Raw message injection
System prompt with datetime.now()⚠️ PartialManual
LangChain Memory⚠️ Token limit onlyLangChain only
SessionAnchorOpenAI / Anthropic / LangChain

Architecture

session_anchor/
├── anchor.py # Main public API — SessionAnchor class
├── core/
│ └── temporal.py # Temporal snapshot creation + grounding note generation
├── decay/
│ └── decay.py # Context decay classification + staleness scoring
├── memory/
│ └── store.py # Session persistence (file-backed, swappable)
└── adapters/
└── adapters.py # OpenAI / Anthropic / LangChain adapters

Contributing

Contributions welcome. Key areas for extension:

  • Richer decay classifiers — NLP-based or LLM-based semantic decay scoring
  • Vector memory integration — Connect to Chroma, Weaviate, or Pinecone for semantic retrieval of only relevant prior context
  • Storage backends — Redis, SQLite, PostgreSQL implementations
  • More adapters — Ollama, Mistral, Cohere, LiteLLM
  • Timezone detection — Auto-detect user timezone from browser/IP
  • Session summarization — Compress long sessions into structured memory objects before they're used as prior context

License

MIT

About

Temporal context grounding for LLM sessions. Stops AI from living in the past.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages