Initial project structure
Scaffold all modules, route stubs, data models, and config. No logic implemented yet — all core methods raise NotImplementedError. Establishes the full directory layout matching the architecture in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
0
fellowship/store/__init__.py
Normal file
0
fellowship/store/__init__.py
Normal file
34
fellowship/store/memory_store.py
Normal file
34
fellowship/store/memory_store.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Memory store — SQLite-backed persistence for cross-session memory.
|
||||
Only active when a session is created with memory: new or memory: inherit:<token>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import aiosqlite
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DB_PATH = "fellowship_memory.db"
|
||||
|
||||
|
||||
class MemoryStore:
|
||||
def __init__(self, db_path: str = DB_PATH) -> None:
|
||||
self.db_path = db_path
|
||||
|
||||
async def init(self) -> None:
|
||||
"""Create tables if they don't exist. Call once at startup."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def save(self, session_token: str, memory: str) -> None:
|
||||
"""Persist a memory string for the given session token."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def load(self, session_token: str) -> Optional[str]:
|
||||
"""Load the stored memory for the given session token, or None if absent."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def delete(self, session_token: str) -> None:
|
||||
"""Delete memory for a session."""
|
||||
raise NotImplementedError
|
||||
51
fellowship/store/session_store.py
Normal file
51
fellowship/store/session_store.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Session store — in-memory registry of all active sessions.
|
||||
Keyed by session token. Also holds the associated SessionLoop and MessageQueue per session.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from fellowship.core.session import Session
|
||||
from fellowship.core.loop import SessionLoop
|
||||
from fellowship.core.queue import MessageQueue
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionEntry:
|
||||
session: Session
|
||||
loop: SessionLoop
|
||||
queue: MessageQueue
|
||||
|
||||
|
||||
class SessionStore:
|
||||
def __init__(self) -> None:
|
||||
self._sessions: dict[str, SessionEntry] = {}
|
||||
|
||||
def create(self, session: Session, loop: SessionLoop, queue: MessageQueue) -> str:
|
||||
"""Register a new session. Returns the session token."""
|
||||
self._sessions[session.token] = SessionEntry(session, loop, queue)
|
||||
return session.token
|
||||
|
||||
def get(self, token: str) -> Optional[SessionEntry]:
|
||||
"""Return the SessionEntry for the given token, or None if not found."""
|
||||
return self._sessions.get(token)
|
||||
|
||||
def remove(self, token: str) -> None:
|
||||
"""Remove a session from the store."""
|
||||
self._sessions.pop(token, None)
|
||||
|
||||
def generate_token(self) -> str:
|
||||
"""Generate a cryptographically random session token."""
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
def all_tokens(self) -> list[str]:
|
||||
return list(self._sessions.keys())
|
||||
|
||||
|
||||
# Global singleton — imported by routes and other modules
|
||||
session_store = SessionStore()
|
||||
Reference in New Issue
Block a user