forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
@@ -4,10 +4,6 @@ Architecture:
|
|||||||
- Tier 1 (Hot): MEMORY.md — always loaded, ~300 lines
|
- Tier 1 (Hot): MEMORY.md — always loaded, ~300 lines
|
||||||
- Tier 2 (Vault): memory/ — structured markdown, append-only
|
- Tier 2 (Vault): memory/ — structured markdown, append-only
|
||||||
- Tier 3 (Semantic): Vector search over vault (optional)
|
- Tier 3 (Semantic): Vector search over vault (optional)
|
||||||
|
|
||||||
Handoff Protocol:
|
|
||||||
- Write last-session-handoff.md at session end
|
|
||||||
- Inject into next session automatically
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -22,7 +18,6 @@ PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|||||||
HOT_MEMORY_PATH = PROJECT_ROOT / "MEMORY.md"
|
HOT_MEMORY_PATH = PROJECT_ROOT / "MEMORY.md"
|
||||||
VAULT_PATH = PROJECT_ROOT / "memory"
|
VAULT_PATH = PROJECT_ROOT / "memory"
|
||||||
SOUL_PATH = VAULT_PATH / "self" / "soul.md"
|
SOUL_PATH = VAULT_PATH / "self" / "soul.md"
|
||||||
HANDOFF_PATH = VAULT_PATH / "notes" / "last-session-handoff.md"
|
|
||||||
|
|
||||||
|
|
||||||
class HotMemory:
|
class HotMemory:
|
||||||
@@ -191,18 +186,6 @@ class VaultMemory:
|
|||||||
return ""
|
return ""
|
||||||
return filepath.read_text()
|
return filepath.read_text()
|
||||||
|
|
||||||
def list_files(self, namespace: str = "notes", pattern: str = "*.md") -> list[Path]:
|
|
||||||
"""List files in a namespace."""
|
|
||||||
dir_path = self.path / namespace
|
|
||||||
if not dir_path.exists():
|
|
||||||
return []
|
|
||||||
return sorted(dir_path.glob(pattern))
|
|
||||||
|
|
||||||
def get_latest(self, namespace: str = "notes", pattern: str = "*.md") -> Path | None:
|
|
||||||
"""Get most recent file in namespace."""
|
|
||||||
files = self.list_files(namespace, pattern)
|
|
||||||
return files[-1] if files else None
|
|
||||||
|
|
||||||
def update_user_profile(self, key: str, value: str) -> None:
|
def update_user_profile(self, key: str, value: str) -> None:
|
||||||
"""Update a field in user_profile.md."""
|
"""Update a field in user_profile.md."""
|
||||||
profile_path = self.path / "self" / "user_profile.md"
|
profile_path = self.path / "self" / "user_profile.md"
|
||||||
@@ -269,122 +252,16 @@ class VaultMemory:
|
|||||||
profile_path.write_text(default)
|
profile_path.write_text(default)
|
||||||
|
|
||||||
|
|
||||||
class HandoffProtocol:
|
|
||||||
"""Session handoff protocol for continuity."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.path = HANDOFF_PATH
|
|
||||||
self.vault = VaultMemory()
|
|
||||||
|
|
||||||
def write_handoff(
|
|
||||||
self,
|
|
||||||
session_summary: str,
|
|
||||||
key_decisions: list[str],
|
|
||||||
open_items: list[str],
|
|
||||||
next_steps: list[str],
|
|
||||||
) -> None:
|
|
||||||
"""Write handoff at session end."""
|
|
||||||
content = f"""# Last Session Handoff
|
|
||||||
|
|
||||||
**Session End:** {datetime.now(UTC).isoformat()}
|
|
||||||
**Duration:** (calculated on read)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
{session_summary}
|
|
||||||
|
|
||||||
## Key Decisions
|
|
||||||
|
|
||||||
{chr(10).join(f"- {d}" for d in key_decisions) if key_decisions else "- (none)"}
|
|
||||||
|
|
||||||
## Open Items
|
|
||||||
|
|
||||||
{chr(10).join(f"- [ ] {i}" for i in open_items) if open_items else "- (none)"}
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
{chr(10).join(f"- {s}" for s in next_steps) if next_steps else "- (none)"}
|
|
||||||
|
|
||||||
## Context for Next Session
|
|
||||||
|
|
||||||
The user was last working on: {session_summary[:200]}...
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*This handoff will be auto-loaded at next session start*
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.path.write_text(content)
|
|
||||||
|
|
||||||
# Also archive to notes
|
|
||||||
self.vault.write_note("session_handoff", content, namespace="notes")
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"HandoffProtocol: Wrote handoff with %d decisions, %d open items",
|
|
||||||
len(key_decisions),
|
|
||||||
len(open_items),
|
|
||||||
)
|
|
||||||
|
|
||||||
def read_handoff(self) -> str | None:
|
|
||||||
"""Read handoff if exists."""
|
|
||||||
if not self.path.exists():
|
|
||||||
return None
|
|
||||||
return self.path.read_text()
|
|
||||||
|
|
||||||
def clear_handoff(self) -> None:
|
|
||||||
"""Clear handoff after loading."""
|
|
||||||
if self.path.exists():
|
|
||||||
self.path.unlink()
|
|
||||||
logger.debug("HandoffProtocol: Cleared handoff")
|
|
||||||
|
|
||||||
|
|
||||||
class MemorySystem:
|
class MemorySystem:
|
||||||
"""Central memory system coordinating all tiers."""
|
"""Central memory system coordinating all tiers."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.hot = HotMemory()
|
self.hot = HotMemory()
|
||||||
self.vault = VaultMemory()
|
self.vault = VaultMemory()
|
||||||
self.handoff = HandoffProtocol()
|
|
||||||
self.session_start_time: datetime | None = None
|
|
||||||
self.session_decisions: list[str] = []
|
self.session_decisions: list[str] = []
|
||||||
self.session_open_items: list[str] = []
|
|
||||||
|
|
||||||
def start_session(self) -> str:
|
|
||||||
"""Start a new session, loading context from memory."""
|
|
||||||
self.session_start_time = datetime.now(UTC)
|
|
||||||
|
|
||||||
# Build context
|
|
||||||
context_parts = []
|
|
||||||
|
|
||||||
# 1. Hot memory
|
|
||||||
hot_content = self.hot.read()
|
|
||||||
context_parts.append("## Hot Memory\n" + hot_content)
|
|
||||||
|
|
||||||
# 2. Last session handoff
|
|
||||||
handoff_content = self.handoff.read_handoff()
|
|
||||||
if handoff_content:
|
|
||||||
context_parts.append("## Previous Session\n" + handoff_content)
|
|
||||||
self.handoff.clear_handoff()
|
|
||||||
|
|
||||||
# 3. User profile (key fields only)
|
|
||||||
profile = self._load_user_profile_summary()
|
|
||||||
if profile:
|
|
||||||
context_parts.append("## User Context\n" + profile)
|
|
||||||
|
|
||||||
full_context = "\n\n---\n\n".join(context_parts)
|
|
||||||
logger.info("MemorySystem: Session started with %d chars context", len(full_context))
|
|
||||||
|
|
||||||
return full_context
|
|
||||||
|
|
||||||
def end_session(self, summary: str) -> None:
|
def end_session(self, summary: str) -> None:
|
||||||
"""End session, write handoff."""
|
"""End session (retained for API compatibility)."""
|
||||||
self.handoff.write_handoff(
|
|
||||||
session_summary=summary,
|
|
||||||
key_decisions=self.session_decisions,
|
|
||||||
open_items=self.session_open_items,
|
|
||||||
next_steps=[],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update hot memory
|
# Update hot memory
|
||||||
self.hot.update_section(
|
self.hot.update_section(
|
||||||
"Current Session",
|
"Current Session",
|
||||||
@@ -392,20 +269,11 @@ class MemorySystem:
|
|||||||
+ f"**Summary:** {summary[:100]}...",
|
+ f"**Summary:** {summary[:100]}...",
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info("MemorySystem: Session ended, handoff written")
|
logger.info("MemorySystem: Session ended")
|
||||||
|
|
||||||
def record_decision(self, decision: str) -> None:
|
def record_decision(self, decision: str) -> None:
|
||||||
"""Record a key decision during session."""
|
"""Record a key decision during session (retained for API compatibility)."""
|
||||||
self.session_decisions.append(decision)
|
self.session_decisions.append(decision)
|
||||||
# Also add to hot memory
|
|
||||||
current = self.hot.read()
|
|
||||||
if "## Key Decisions" in current:
|
|
||||||
# Append to section
|
|
||||||
pass # Handled at session end
|
|
||||||
|
|
||||||
def record_open_item(self, item: str) -> None:
|
|
||||||
"""Record an open item for follow-up."""
|
|
||||||
self.session_open_items.append(item)
|
|
||||||
|
|
||||||
def update_user_fact(self, key: str, value: str) -> None:
|
def update_user_fact(self, key: str, value: str) -> None:
|
||||||
"""Update user profile in vault."""
|
"""Update user profile in vault."""
|
||||||
@@ -453,11 +321,7 @@ class MemorySystem:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
def get_system_context(self) -> str:
|
def get_system_context(self) -> str:
|
||||||
"""Get full context for system prompt injection.
|
"""Get full context for system prompt injection."""
|
||||||
|
|
||||||
Unlike start_session(), this does NOT clear the handoff.
|
|
||||||
Safe to call multiple times without data loss.
|
|
||||||
"""
|
|
||||||
context_parts = []
|
context_parts = []
|
||||||
|
|
||||||
# 0. Soul identity (immutable, always first)
|
# 0. Soul identity (immutable, always first)
|
||||||
@@ -469,17 +333,12 @@ class MemorySystem:
|
|||||||
hot_content = self.hot.read()
|
hot_content = self.hot.read()
|
||||||
context_parts.append("## Hot Memory\n" + hot_content)
|
context_parts.append("## Hot Memory\n" + hot_content)
|
||||||
|
|
||||||
# 2. Last session handoff (read-only, do NOT clear)
|
# 2. User profile (key fields only)
|
||||||
handoff_content = self.handoff.read_handoff()
|
|
||||||
if handoff_content:
|
|
||||||
context_parts.append("## Previous Session\n" + handoff_content)
|
|
||||||
|
|
||||||
# 3. User profile (key fields only)
|
|
||||||
profile = self._load_user_profile_summary()
|
profile = self._load_user_profile_summary()
|
||||||
if profile:
|
if profile:
|
||||||
context_parts.append("## User Context\n" + profile)
|
context_parts.append("## User Context\n" + profile)
|
||||||
|
|
||||||
# 4. Known facts from long-term memory
|
# 3. Known facts from long-term memory
|
||||||
facts_section = self._load_known_facts()
|
facts_section = self._load_known_facts()
|
||||||
if facts_section:
|
if facts_section:
|
||||||
context_parts.append(facts_section)
|
context_parts.append(facts_section)
|
||||||
|
|||||||
Reference in New Issue
Block a user