Skip to main content

Task-aware memory management for LLMs. Extend conversations 5.7x longer with 88% recall.

Project description

CI Python 3.10+ License: MIT PyPI

MemCtrl

Stop paying for context window upgrades. MemCtrl extends your LLM conversations 5.7x longer with 88% recall accuracy — for free.

MemCtrl is a context budget optimizer SDK that sits between your app and any LLM API. It manages a 3-tier memory hierarchy, compresses old messages, preserves critical facts, and returns an optimized message list that fits your token budget.

No API calls on your behalf. No hosted service. No fees. Your keys stay in your memory — never logged, never stored.

Why MemCtrl?

Problem Without MemCtrl With MemCtrl
Long conversations Context window fills up, old messages lost 5.7x longer conversations, same budget
Token costs Pay for full context every call 8-13% token savings via compression
Critical facts Passwords, dosages, IDs vanish after compression Auto-pinned, 100% preserved
Medical/code context Treated same as casual chat Task-aware retention (medical kept 7 days, code 3 days)

Eval Results

Tested with Ollama (llama3) across 3 real-world scenarios:

Scenario Messages Recall Token Savings
Flask API Debugging 40 100% 11%
ML Tutoring Session 30 80% 13%
Medical Consultation 24 80%
Overall 94 88% 5.7x endurance

Install

pip install memctrl

With optional LLM backends:

pip install memctrl[anthropic]   # For Claude
pip install memctrl[openai]      # For GPT
pip install memctrl[all]         # Everything

Quick Start

SDK API (recommended)

import memctrl
from openai import OpenAI

client = OpenAI(api_key="sk-...")
mc = memctrl.MemoryController(user_id="dev1")

# Record messages as they happen
mc.add_message("user", "My DB connection is postgresql://admin:pass@localhost:5432/mydb")
mc.add_message("assistant", "Got it. I'll remember your connection string.")
mc.add_message("user", "Now help me write a migration script")

# Get optimized context for your next API call
messages = mc.optimize(max_tokens=4096)

# Pass directly to any LLM
response = client.chat.completions.create(model="gpt-4o", messages=messages)

Zero-Code Wrapper

import memctrl
from openai import OpenAI

client = OpenAI(api_key="sk-...")
wrapped = memctrl.wrap(client, max_tokens=4096)

# Use normally — memctrl manages memory automatically
response = wrapped.chat("Help me debug my Flask app")
response = wrapped.chat("The error is on line 42")
response = wrapped.chat("What was my DB connection string?")  # Still remembers

Pin Critical Facts

mc.pin("Patient is allergic to penicillin")
mc.pin("API key: sk-abc123", note="Production key")

# Pinned facts are always included in optimize() output
messages = mc.optimize(max_tokens=4096)

Auto-Pin Suggestions

MemCtrl detects critical information and suggests pinning — it never auto-pins user-facing data without asking.

mc.add_message("user", "The password is hunter2")

suggestions = mc.suggest_pins()
# [{"chunk_id": "...", "category": "credential",
#   "reason": "Detected credential — this looks important. Pin it?"}]

# User decides
mc.accept_pin_suggestion(suggestions[0]["chunk_id"])

During compression, critical values (credentials, medical data, connection strings) are automatically preserved so they survive summarization.

Stale Memory Cleanup

suggestions = mc.suggest_cleanup(stale_hours=24.0)
# [{"chunk_id": "...", "tokens": 45, "hours_stale": 36.2,
#   "reason": "Not accessed in 36.2 hours. Deleting saves 45 tokens."}]

# User decides what to delete
mc.accept_cleanup([s["chunk_id"] for s in suggestions])

Context Budget Debugger

report = mc.budget_report(max_tokens=4096)
# {
#   "total_used": 2847, "remaining": 1249, "usage_pct": 69.5,
#   "breakdown": {
#     "system_prompt": {"tokens": 12},
#     "pinned": {"tokens": 89, "count": 3},
#     "active": {"tokens": 1820, "count": 14},
#     "compressed": {"tokens": 926, "count": 8},
#   },
#   "recommendations": ["Plenty of budget remaining."]
# }

Forget and Temporary

# Soft-delete with confirmation
result = mc.forget("old project notes")
mc.forget_confirmed(result["matches"])

# Session-only memory (auto-deleted on close)
mc.temporary("Meeting at 3pm today")

# Restore from trash
mc.restore_from_trash(chunk_id)

Cross-Session Persistence

Pins, trash, and audit logs persist to SQLite across sessions:

# Session 1
mc = memctrl.MemoryController(user_id="dev1")
mc.pin("Deploy to us-east-1")
mc.close_session()

# Session 2 — pin is still there
mc = memctrl.MemoryController(user_id="dev1")
messages = mc.optimize(max_tokens=4096)  # Includes "Deploy to us-east-1"

How It Works

User App ──► add_message() ──► TierManager ──► optimize() ──► LLM API
                                    │
                 ┌──────────────────┼──────────────────┐
                 │                  │                  │
          ┌──────┴──────┐   ┌──────┴──────┐   ┌──────┴──────┐
          │   Tier 0    │   │   Tier 1    │   │   Tier 2    │
          │   Active    │   │  Compressed │   │  Persistent │
          │  LRU evict  │   │  distilbart │   │  SQLite+FTS │
          │  OrderedDict│   │  + entities │   │  Embeddings │
          └─────────────┘   └─────────────┘   └─────────────┘

Tier 0 (Active) — Recent messages in full. LRU eviction, pinned chunks protected.

Tier 1 (Compressed) — Older messages summarized by local distilbart model (free, no API calls). Named entities (numbers, URLs, field names, medical values) are extracted and appended to summaries so they survive compression.

Tier 2 (Persistent) — All chunks stored in SQLite with FTS5 full-text search and sentence-transformer embeddings for semantic retrieval.

Task-Aware Retention

MemCtrl classifies each message by domain and adjusts retention:

Task Type Retention Weight Decay Period Promotion Threshold
Medical 1.5x 7 days 30 (easiest)
Code 1.3x 3 days 35
Tutoring 1.2x 2 days 40
Writing 1.0x 1 day 45
General 0.8x 12 hours 50 (hardest)

Medical data is kept 14x longer than general chat before eviction.

Entity-Preserving Compression

When messages are compressed, MemCtrl extracts and preserves:

  • Version numbers, formatted numbers, percentages
  • URLs, connection strings, API keys
  • Medical measurements, lab results, vitals
  • Code identifiers (snake_case, CamelCase, function calls)
  • Math notation, X-Headers

These are appended to summaries in brackets so the LLM can still reference them.

BYOK (Bring Your Own Key)

MemCtrl supports multiple LLM backends. Keys are passed in memory only — never logged, never persisted.

# Anthropic
mc = memctrl.MemoryController(provider="anthropic", api_key="sk-...")

# OpenAI
mc = memctrl.MemoryController(provider="openai", api_key="sk-...")

# Ollama (local, no key needed)
mc = memctrl.MemoryController(provider="ollama")

# Auto-detect (tries Anthropic → OpenAI → Ollama → Echo)
mc = memctrl.MemoryController(provider="auto")

CLI

memctrl chat "Hello" --user dev1
memctrl pin "Allergic to penicillin" --user dev1
memctrl forget "old notes" --user dev1
memctrl show --user dev1 --category pinned
memctrl stats --user dev1
memctrl export --user dev1 --format json

Configuration

Override defaults with a YAML file:

# config.yaml
tier0_budget_gb: 4.0
tier1_budget_gb: 4.0
llm_provider: "auto"
embedding_model: "sentence-transformers/all-MiniLM-L6-v2"
tokenizer_model: "distilbert-base-uncased"
control_mode: "hybrid"
compression_ratio: 4.0
max_tokens_per_chunk: 512
max_context_tokens: 4096
export MEMCTRL_CONFIG=path/to/config.yaml

Testing

pip install memctrl[dev]
pytest tests/ -o addopts="" -q

160+ tests covering models, storage, tiers, controller, SDK, features, tokenizer, LLM backends, CLI, and web UI.

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

memctrl_llm-0.1.0.tar.gz (56.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

memctrl_llm-0.1.0-py3-none-any.whl (45.0 kB view details)

Uploaded Python 3

File details

Details for the file memctrl_llm-0.1.0.tar.gz.

File metadata

  • Download URL: memctrl_llm-0.1.0.tar.gz
  • Upload date:
  • Size: 56.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for memctrl_llm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 13ff365544b3044e479bf42552ce771481bfb1f6826289803892ef95d7e6b129
MD5 6a60c68aea1edf1ce7a20f0e4f9980cc
BLAKE2b-256 a217f4130e44a8700650e14a9926d7f969b5ef0782646655206b31e39ddb0697

See more details on using hashes here.

File details

Details for the file memctrl_llm-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: memctrl_llm-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 45.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for memctrl_llm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 17041b9b3e2b1d7451aa2b839b31d0189837002bafee4b8591664cd0520ebbfd
MD5 734222ef1d01ec539dcb8f19fdfc479b
BLAKE2b-256 334cbd1f09f8293476f6052eb330ac5e9e4f1d775ebfa1b8059f5d0dfe95c19c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page