| name | remembering |
| description | Advanced memory operations reference. Basic patterns (profile loading, simple recall/remember) are in project instructions. Consult this skill for background writes, memory versioning, complex queries, edge cases, session scoping, retention management, type-safe results, proactive memory hints, GitHub access detection, and ops priority ordering. |
| metadata | {"version":"3.7.0"} |
⚠️ IMPORTANT FOR CLAUDE CODE AGENTS
Before working with this skill, read CLAUDE.md in this directory.
It contains critical development context, import patterns, and instructs you to use Muninn to track work on Muninn (meta-usage pattern).
Remembering - Advanced Operations
Basic patterns are in project instructions. This skill covers advanced features and edge cases.
Two-Table Architecture
| Table | Purpose | Growth |
|---|
config | Stable operational state (profile + ops + journal) | Small, mostly static |
memories | Timestamped observations | Unbounded |
Config loads fast at startup. Memories are queried as needed.
Boot Sequence
Load context at conversation start to maintain continuity across sessions.
Optimized: Compressed Boot (Recommended)
Use boot() for fast startup (~150ms):
from remembering import boot
print(boot())
Output: Complete profile and ops values for full context at boot.
Performance:
- Execution: ~150ms (single HTTP request)
- Populates local cache for fast subsequent recall()
Boot Output: CAPABILITIES Section (v3.5.0)
Boot now includes a # CAPABILITIES section reporting:
GitHub Access:
- Detects
gh CLI availability and authentication status
- Checks for
GITHUB_TOKEN / GH_TOKEN environment variables
- Reports recommended method (gh-cli preferred when authenticated)
- Shows authenticated user when available
# CAPABILITIES
## GitHub Access
Status: Available
Methods: gh-cli, api-token
Recommended: gh-cli
gh user: oaustegard
Usage: gh pr view, gh issue list, gh api repos/...
Utilities:
- Extracts utility-code memories to
/home/claude/muninn_utils/
- Adds to Python path for direct import
- Lists available utilities with import syntax
## Utilities (2)
from muninn_utils import my_helper
from muninn_utils import another_util
Detecting GitHub Access Programmatically
from remembering import detect_github_access
github = detect_github_access()
if github['available']:
print(f"Use {github['recommended']} for GitHub operations")
if github['gh_cli'] and github['gh_cli']['authenticated']:
print(f"Authenticated as: {github['gh_cli']['user']}")
Returns:
{
'available': True,
'methods': ['gh-cli', 'api-token'],
'recommended': 'gh-cli',
'gh_cli': {'path': '/usr/bin/gh', 'authenticated': True, 'user': 'username'},
'api_token': True
}
Journal System
Temporal awareness via rolling journal entries in config. Inspired by Strix's journal.jsonl pattern.
from remembering import journal, journal_recent, journal_prune
journal(
topics=["project-x", "debugging"],
user_stated="Will review PR tomorrow",
my_intent="Investigating memory leak"
)
for entry in journal_recent(10):
print(f"[{entry['t'][:10]}] {entry.get('topics', [])}: {entry.get('my_intent', '')}")
pruned = journal_prune(keep=40)
Entry structure:
t: ISO timestamp
topics: array of tags (enables filtering at scale)
user_stated: commitments/plans user verbalized
my_intent: current goal/task
Key insight from Strix: "If you didn't write it down, you won't remember it next message."
Config Table
Key-value store for profile (behavioral), ops (operational), and journal (temporal) settings.
from remembering import config_get, config_set, config_delete, config_list, config_set_boot_load, config_set_priority, profile, ops
config_get("identity")
profile()
ops()
config_list()
config_set("new-key", "value", "profile")
config_set("skill-foo", "usage notes", "ops")
config_set("bio", "Short bio here", "profile", char_limit=500)
config_set("core-rule", "Never modify this", "ops", read_only=True)
config_delete("old-key")
Config constraints:
char_limit: Enforces maximum character count on writes (raises ValueError if exceeded)
read_only: Prevents modifications (raises ValueError on attempted updates)
Progressive Disclosure (v2.1.0)
Ops entries can be marked as boot-loaded (default) or reference-only to reduce boot() output size:
from remembering import config_set_boot_load, ops
config_set_boot_load('github-api-endpoints', False)
config_set_boot_load('container-limits', False)
config_set_boot_load('storage-discipline', True)
boot_ops = ops()
all_ops = ops(include_reference=True)
How it works:
boot() outputs only ops with boot_load=1 (reduces token usage at boot)
- Reference-only ops (
boot_load=0) appear in a Reference Entries index at the end of boot output
- Reference entries remain fully accessible via
config_get(key) when needed
- Ideal for: API documentation, container specs, rarely-triggered guidance
Example boot output:
=== OPS ===
## Core Boot & Behavior
storage-discipline:
[Full content here...]
## Reference Entries (load via config_get)
container-limits, github-api-endpoints, network-tools, recall-triggers
Priority-Based Ordering (v3.6.0)
Ops entries within each topic category are sorted by priority (descending). Critical entries appear first.
from remembering import config_set_priority
config_set_priority('storage-rules', 10)
config_set_priority('boot-behavior', 5)
config_set_priority('fly-command', 0)
How it works:
- Entries with higher priority appear first within their topic category
- Equal-priority entries are sorted alphabetically by key
- Priority is stored in the
priority column of the config table
- Does not require entry recreation—just call
config_set_priority()
Dynamic Topic Categories (v3.6.0)
Topic categories can be loaded from config instead of being hardcoded:
from remembering import config_set, config_get
import json
topics = json.loads(config_get('ops-topics') or '{}')
new_topics = {
'Core Boot & Behavior': ['boot-behavior', 'dev-workflow'],
'Memory Operations': ['remembering-api', 'storage-rules'],
'My Custom Category': ['my-key-1', 'my-key-2']
}
config_set('ops-topics', json.dumps(new_topics), 'ops')
How it works:
boot() loads topics from config_get('ops-topics') if available
- Falls back to built-in defaults if config is missing or invalid
- Format: JSON object mapping topic name → list of ops keys
- Changes take effect on next
boot() call
Memory Type System
Type is required on all write operations. Valid types:
| Type | Use For |
|---|
decision | Explicit choices: prefers X, always/never do Y |
world | External facts: tasks, deadlines, project state |
anomaly | Errors, bugs, unexpected behavior |
experience | General observations, catch-all |
Note: profile is no longer a memory type—use config_set(key, value, "profile") instead.
from remembering import TYPES
Priority System (v2.0.0)
Memories have a priority field that affects ranking in search results:
| Priority | Value | Description |
|---|
| Background | -1 | Low-value, can age out first |
| Normal | 0 | Default for new memories |
| Important | 1 | Boosted in ranking |
| Critical | 2 | Always surface, never auto-age |
from remembering import remember, reprioritize
remember("Critical security finding", "anomaly", tags=["security"], priority=2)
reprioritize("memory-uuid", priority=1)
reprioritize("memory-uuid", priority=-1)
Ranking formula:
score = bm25_score * recency_weight * (1 + priority * 0.5)
Priority affects composite ranking score - higher priority memories surface more readily in search results.
Memory Consolidation (v3.3.0)
Biological memory consolidation pattern: memories that participate in active cognition consolidate more strongly.
from remembering import strengthen, weaken, recall
result = strengthen("memory-uuid", boost=1)
result = weaken("memory-uuid", drop=1)
results = recall("important topic", auto_strengthen=True, n=10)
Use cases:
- Consolidate memories that prove useful across conversations
- Implement spaced repetition patterns
- Automatically promote frequently accessed knowledge
- Simulate biological memory consolidation mechanisms
Notes:
strengthen() caps at priority=2 (critical)
weaken() floors at priority=-1 (background)
auto_strengthen=True only affects top 3 results with priority < 2
- Returns dict with old/new priority and whether change occurred
- Replaced no-op placeholder functions from v2.0.0 with working implementations
Background Writes (Agentic Pattern)
v0.6.0: Unified API with sync parameter. Use remember(..., sync=False) for background writes:
from remembering import remember, flush
remember("User's project uses Python 3.12 with FastAPI", "world", sync=False)
remember("Discovered: batch insert reduces latency 70%", "experience",
tags=["optimization"], sync=False)
flush()
Backwards compatibility: remember_bg() still works (deprecated, calls remember(..., sync=False)):
from remembering import remember_bg
remember_bg("Quick note", "world")
When to use sync=False (background):
- Storing derived insights during active work
- Memory write shouldn't block response
- Agentic pattern where latency matters
When to use sync=True (blocking, default):
- User explicitly requests storage
- Need confirmation of write success
- Critical memories (handoffs, decisions)
- End of workflow when durability matters
⚠️ IMPORTANT - Cache Sync Guarantee:
- If you use
sync=False for ANY writes in a conversation, you MUST call flush() before the conversation ends
- This ensures all background writes persist to the database before the ephemeral container is destroyed
- Single-user context: no concurrent write conflicts, all writes will succeed
- Prefer
sync=True (default) for critical writes to guarantee immediate persistence
Memory Versioning (Patch/Snapshot)
Supersede without losing history:
from remembering import supersede
original_id = "abc-123"
supersede(original_id, "User now prefers Python 3.12", "decision", conf=0.9)
Creates new memory with refs=[original_id]. Original preserved but not returned in default queries. Trace evolution via refs chain.
v3.3.0 Performance: supersede() now uses batched operations, reducing HTTP requests by 50% (single request instead of two).
Complex Queries
Multiple filters, custom confidence thresholds:
from remembering import recall
decisions = recall(type="decision", conf=0.85, n=20)
bugs = recall(type="anomaly", n=5)
tasks = recall("API", tags=["task"], n=15)
urgent_tasks = recall(tags=["task", "urgent"], tag_mode="all", n=10)
Date-Filtered Queries
Query memories by temporal range:
from remembering import recall_since, recall_between
recent = recall_since("2025-12-01T00:00:00Z", n=50)
recent_bugs = recall_since("2025-12-20T00:00:00Z", type="anomaly", tags=["critical"])
december = recall_between("2025-12-01T00:00:00Z", "2025-12-31T23:59:59Z", n=100)
sprint_mems = recall_between("2025-12-15T00:00:00Z", "2025-12-22T00:00:00Z",
type="decision", tags=["sprint-5"])
Use cases:
- Review decisions made during a project phase
- Analyze bugs discovered in a time window
- Track learning progress over specific periods
- Build time-based memory summaries
Notes:
- Timestamps are exclusive (use
> and < not >= and <=)
- Supports all standard filters:
search, type, tags, tag_mode
- Sorted by timestamp descending (newest first)
- Excludes soft-deleted and superseded memories
Therapy Helpers
Support for reflection and memory consolidation workflows:
from remembering import therapy_scope, therapy_session_count
cutoff_time, unprocessed_memories = therapy_scope()
count = therapy_session_count()
Therapy session workflow:
- Call
therapy_scope() to get unprocessed memories
- Analyze and consolidate memories (group patterns, extract insights)
- Record therapy session completion:
remember(f"Therapy Session #{count+1}: Consolidated {len(unprocessed)} memories...",
"experience", tags=["therapy"])
Pattern detection example:
cutoff, mems = therapy_scope()
by_type = group_by_type(mems)
print(f"Since {cutoff}:")
print(f" {len(by_type.get('decision', []))} decisions")
print(f" {len(by_type.get('anomaly', []))} anomalies to investigate")
Analysis Helpers
Group and organize memories for pattern detection:
from remembering import group_by_type, group_by_tag
memories = recall(n=100)
by_type = group_by_type(memories)
by_tag = group_by_tag(memories)
Use cases:
- Pattern detection: Find clusters of related memories
- Quality analysis: Identify over/under-represented memory types
- Tag hygiene: Discover inconsistent tagging patterns
- Therapy sessions: Organize unprocessed memories before consolidation
Example - Find overused tags:
mems = recall(n=200)
by_tag = group_by_tag(mems)
sorted_tags = sorted(by_tag.items(), key=lambda x: len(x[1]), reverse=True)
print("Top tags:")
for tag, tagged_mems in sorted_tags[:5]:
print(f" {tag}: {len(tagged_mems)} memories")
FTS5 Search with Porter Stemmer (v0.13.0)
Full-text search uses FTS5 with Porter stemmer for morphological variant matching:
from remembering import recall
results = recall("running performance")
sparse_results = recall("rare term")
results = recall("term", expansion_threshold=5)
results = recall("term", expansion_threshold=0)
How it works:
- FTS5 tokenizer:
porter unicode61 handles stemming
- BM25 ranking for relevance scoring
- Query expansion extracts tags from partial results when below threshold (default 3, configurable via
expansion_threshold)
- Set
expansion_threshold=0 to disable expansion entirely
- Composite ranking: BM25 × salience × recency × access patterns
Soft Delete
Remove without destroying data:
from remembering import forget
forget("memory-uuid")
Memories remain in database for audit/recovery. Hard deletes require direct SQL.
Memory Quality Guidelines
Write complete, searchable summaries that standalone without conversation context:
✓ "User prefers direct answers with code examples over lengthy conceptual explanations"
✗ "User wants code" (lacks context, unsearchable)
✗ "User asked question" + "gave code" + "seemed happy" (fragmented, no synthesis)
Handoff Convention
Cross-environment work coordination with version tracking and automatic completion marking.
Creating Handoffs
From Claude.ai (web/mobile) - cannot persist file changes:
from remembering import remember
remember("""
HANDOFF: Implement user authentication
## Context
User wants OAuth2 + JWT authentication for the API.
## Files to Modify
- src/auth/oauth.py
- src/middleware/auth.py
- tests/test_auth.py
## Implementation Notes
- Use FastAPI OAuth2PasswordBearer
- JWT tokens with 24h expiry
- Refresh token support
...
""", "world", tags=["handoff", "pending", "auth"])
Important: Tag with ["handoff", "pending", ...] so it appears in handoff_pending() queries.
Handoff structure:
- Title: Brief summary of what needs to be done
- Context: Why this work is needed
- Files to Modify: Specific paths
- Implementation Notes: Code patterns, constraints, dependencies
Completing Handoffs
From Claude Code - streamlined workflow:
from remembering import handoff_pending, handoff_complete
pending = handoff_pending()
print(f"{len(pending)} pending handoff(s)")
for h in pending:
print(f"[{h['created_at'][:10]}] {h['summary'][:80]}")
handoff_id = pending[0]['id']
handoff_complete(
handoff_id,
"COMPLETED: Implemented boot() function with batched queries...",
)
What happens:
- Original handoff is superseded (won't appear in future
handoff_pending() queries)
- Completion record created with tags
["handoff-completed", "v0.5.0"]
- Version tracked automatically from
VERSION file
- Full history preserved via
supersede() chain
Querying History
from remembering import recall
v050_work = recall(tags=["handoff-completed", "v0.5.0"])
completed = recall(tags=["handoff-completed"], n=50)
Use when:
- Working in Claude.ai (web/mobile) without file write access
- Planning work that needs Claude Code execution
- Coordinating between environments
- Leaving detailed instructions for future sessions
Session Scoping (v3.2.0)
Filter memories by conversation or work session using session_id:
from remembering import remember, recall, set_session_id
set_session_id("project-alpha-sprint-1")
remember("Feature spec approved", "decision", tags=["project-alpha"])
alpha_memories = recall(session_id="project-alpha-sprint-1", n=50)
import os
os.environ['MUNINN_SESSION_ID'] = 'my-session'
Note: Session filtering bypasses cache (queries Turso directly). Cache support planned for future release.
Retrieval Observability (v3.2.0)
Monitor query performance and usage patterns:
from remembering import recall_stats, top_queries
stats = recall_stats(limit=100)
print(f"Cache hit rate: {stats['cache_hit_rate']:.1%}")
print(f"Avg query time: {stats['avg_exec_time_ms']:.1f}ms")
for query_info in top_queries(n=10):
print(f"{query_info['query']}: {query_info['count']} times")
Retention Management (v3.2.0)
Analyze memory distribution and prune old/low-priority memories:
from remembering import memory_histogram, prune_by_age, prune_by_priority
hist = memory_histogram()
print(f"Total: {hist['total']}")
print(f"By type: {hist['by_type']}")
print(f"By priority: {hist['by_priority']}")
print(f"By age: {hist['by_age_days']}")
result = prune_by_age(older_than_days=90, priority_floor=0, dry_run=True)
print(f"Would delete {result['count']} memories")
result = prune_by_age(older_than_days=90, priority_floor=0, dry_run=False)
result = prune_by_priority(max_priority=-1, dry_run=False)
Export/Import for Portability
Backup or migrate Muninn state across environments:
from remembering import muninn_export, muninn_import
import json
state = muninn_export()
with open("muninn-backup.json", "w") as f:
json.dump(state, f, indent=2)
with open("muninn-backup.json") as f:
data = json.load(f)
stats = muninn_import(data, merge=True)
print(f"Imported {stats['config_count']} config, {stats['memory_count']} memories")
stats = muninn_import(data, merge=False)
Notes:
merge=False deletes all existing data before import (use with caution!)
- Memory IDs are regenerated on import to avoid conflicts
- Returns stats dict with counts and any errors
Type-Safe Results (v3.4.0)
recall(), recall_since(), and recall_between() now return MemoryResult objects that validate field access immediately:
from remembering import recall, MemoryResult, VALID_FIELDS
memories = recall("search term", n=10)
for m in memories:
print(m.summary)
print(m['summary'])
print(m.get('summary', 'default'))
print(m.content)
print(m['content'])
print(m.conf)
print(m.foo)
Parameter aliases (v3.7.0):
recall("search", limit=20)
Valid fields:
from remembering import VALID_FIELDS
Transparent aliases (v3.7.0):
| Alias | Resolves To |
|---|
m.content | m.summary |
m['text'] | m['summary'] |
m.conf | m.confidence |
m.timestamp | m.t |
m.created | m.created_at |
Normalized fields (v3.7.0):
All results now include summary_preview (first 100 chars) regardless of whether they came from cache or Turso. Fields like bm25_score and composite_rank are valid but only present on cache-sourced results.
Backward compatibility:
- MemoryResult supports all dict operations:
in, len(), iteration, keys(), values(), items()
- Use
m.to_dict() to convert back to plain dict when needed
- Use
raw=True parameter to get plain dicts: recall("term", raw=True)
Proactive Memory Hints (v3.4.0)
recall_hints() scans context for terms that match memories, helping surface relevant information before you make mistakes:
from remembering import recall_hints
hints = recall_hints("for m in memories: print(m['content'])")
if hints['hints']:
print("Relevant memories found:")
for h in hints['hints']:
print(f" [{h['type']}] {h['preview']}")
print(f" Matched: {h['matched_terms']}")
if hints['unmatched_terms']:
print(f"New terms: {hints['unmatched_terms']}")
Use explicit terms for targeted lookup:
hints = recall_hints(terms=["muninn", "field", "summary", "content"])
Hint structure:
{
'hints': [
{
'memory_id': 'abc-123...',
'type': 'decision',
'preview': 'First 100 chars of summary...',
'matched_terms': ['muninn', 'field'],
'matched_tags': ['muninn'],
'priority': 1,
'relevance_score': 3
}
],
'term_coverage': {'muninn': ['abc-123'], 'field': ['abc-123', 'def-456']},
'unmatched_terms': ['content'],
'warning': None
}
When to use:
- Before writing code that uses
recall() - catch field name errors early
- When starting work on a topic - surface forgotten context
- Before making decisions - check for relevant past decisions
- After receiving user instructions - find related memories
Performance: Uses local cache when available (~5ms). Falls back to config-based tag matching.
Edge Cases
Empty recall results: Returns MemoryResultList([]), not an error. Check list length before accessing.
Search literal matching: Current implementation uses SQL LIKE. Searches "API test" matches "API testing" but not "test API" (order matters).
Tag partial matching: tags=["task"] matches memories with tags ["task", "urgent"] via JSON substring search.
Confidence defaults: decision type defaults to 0.8 if not specified. Others default to NULL.
Invalid type: Raises ValueError with list of valid types.
Invalid category: config_set raises ValueError if category not 'profile', 'ops', or 'journal'.
Journal pruning: Call journal_prune() periodically to prevent unbounded growth. Default keeps 40 entries.
Tag mode: tag_mode="all" requires all specified tags to be present. tag_mode="any" (default) matches if any tag present.
Query expansion: When FTS5 returns fewer results than expansion_threshold (default 3), tags are automatically extracted from partial matches and used to find related memories. Set expansion_threshold=0 to disable.
Implementation Notes
- Backend: Turso SQLite HTTP API
- URL:
TURSO_URL environment variable or /mnt/project/muninn.env, falls back to default
- Token:
TURSO_TOKEN environment variable, /mnt/project/muninn.env, or /mnt/project/turso-token.txt
- Two tables:
config (KV) and memories (observations)
- FTS5 search: Porter stemmer tokenizer with BM25 ranking
- HTTP API required (libsql SDK bypasses egress proxy)
- Local SQLite cache for fast recall (< 5ms vs 150ms+ network)
- Thread-safe for background writes