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:
Jaroslav Benes
2026-04-08 14:48:48 +02:00
commit 083cbb1fa7
32 changed files with 1507 additions and 0 deletions

View File

View 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

View 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()