Add memory_manager source files and tests
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-03-22 16:18:58 +00:00
parent a5ee2d1868
commit 71a6e02e04

View File

@@ -1,47 +1,42 @@
"""FastAPI application for memory manager API.""" """FastAPI REST API for the memory manager."""
import os import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import List, Optional
from fastapi import FastAPI, HTTPException, Query from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from memory_manager.core.services import MemoryService, CommitService from memory_manager import __version__
from memory_manager.db.repository import MemoryRepository
from memory_manager.api.schemas import ( from memory_manager.api.schemas import (
MemoryEntryCreate,
MemoryEntryUpdate,
MemoryEntryResponse,
CommitCreate, CommitCreate,
CommitResponse, CommitResponse,
DiffResponse, DiffResponse,
HealthResponse,
MemoryEntryCreate,
MemoryEntryResponse,
MemoryEntryUpdate,
StatsResponse, StatsResponse,
) )
from memory_manager.core.services import MemoryManager
from memory_manager.db.models import MemoryCategory
from memory_manager.db.repository import MemoryRepository
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
repository: Optional[MemoryRepository] = None repository = MemoryRepository(db_path)
memory_service: Optional[MemoryService] = None memory_manager = MemoryManager(repository)
commit_service: Optional[CommitService] = None
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
global repository, memory_service, commit_service await memory_manager.initialize()
db_path = os.getenv("MEMORY_DB_PATH", ".memory/codebase_memory.db")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
repository = MemoryRepository(db_path)
await repository.init_db()
memory_service = MemoryService(repository)
commit_service = CommitService(repository)
yield yield
if repository: await memory_manager.close()
await repository.close()
app = FastAPI( app = FastAPI(
title="Agentic Codebase Memory Manager API", title="Agentic Codebase Memory Manager",
description="A centralized memory store for AI coding agents", description="A centralized memory store for AI coding agents",
version="0.1.0", version=__version__,
lifespan=lifespan, lifespan=lifespan,
) )
@@ -54,141 +49,159 @@ app.add_middleware(
) )
def entry_to_response(entry) -> MemoryEntryResponse: @app.get("/health", response_model=HealthResponse)
return MemoryEntryResponse( async def health():
id=entry.id, return HealthResponse(status="ok", version=__version__)
title=entry.title,
content=entry.content,
category=entry.category.value,
tags=entry.tags or [],
agent_id=entry.agent_id,
project_path=entry.project_path,
created_at=entry.created_at,
updated_at=entry.updated_at,
parent_id=entry.parent_id,
)
@app.get("/") @app.get("/api/memory", response_model=list[MemoryEntryResponse])
def root():
return {"message": "Agentic Codebase Memory Manager API", "version": "0.1.0"}
@app.get("/api/memory", response_model=List[MemoryEntryResponse])
async def list_memory( async def list_memory(
category: Optional[str] = Query(None), category: str | None = None,
agent_id: Optional[str] = Query(None), agent_id: str | None = None,
limit: int = Query(100, ge=1, le=1000), project_path: str | None = None,
offset: int = Query(0, ge=0), limit: int = Query(default=100, ge=1, le=1000),
offset: int = Query(default=0, ge=0),
): ):
entries = await memory_service.list_entries( category_enum = None
category=category, if category:
try:
category_enum = MemoryCategory(category)
except ValueError:
raise HTTPException(status_code=422, detail=f"Invalid category: {category}")
entries = await memory_manager.memory_service.list_entries(
category=category_enum,
agent_id=agent_id, agent_id=agent_id,
project_path=project_path,
limit=limit, limit=limit,
offset=offset, offset=offset,
) )
return [entry_to_response(e) for e in entries] return entries
@app.post("/api/memory", response_model=MemoryEntryResponse) @app.post("/api/memory", response_model=MemoryEntryResponse, status_code=201)
async def create_memory(entry: MemoryEntryCreate): async def create_memory(entry: MemoryEntryCreate):
new_entry = await memory_service.create_entry( result = await memory_manager.memory_service.create_entry(
title=entry.title, title=entry.title,
content=entry.content, content=entry.content,
category=entry.category, category=entry.category,
tags=entry.tags, tags=entry.tags,
agent_id=entry.agent_id, agent_id=entry.agent_id,
project_path=entry.project_path, project_path=entry.project_path,
parent_id=entry.parent_id,
) )
return entry_to_response(new_entry) return result
@app.get("/api/memory/log", response_model=list[CommitResponse])
async def get_log(
agent_id: str | None = None,
project_path: str | None = None,
limit: int = Query(default=100, ge=1, le=1000),
offset: int = Query(default=0, ge=0),
):
commits = await memory_manager.commit_service.list_commits(
agent_id=agent_id,
project_path=project_path,
limit=limit,
offset=offset,
)
return commits
@app.get("/api/memory/stats", response_model=StatsResponse)
async def get_stats(project_path: str | None = None):
entries = await memory_manager.memory_service.list_entries(
project_path=project_path,
limit=10000,
)
entries_by_category: dict[str, int] = {}
for entry in entries:
cat = entry["category"]
entries_by_category[cat] = entries_by_category.get(cat, 0) + 1
commits = await memory_manager.commit_service.list_commits(
project_path=project_path,
limit=10000,
)
return StatsResponse(
total_entries=len(entries),
entries_by_category=entries_by_category,
total_commits=len(commits),
)
@app.get("/api/memory/search", response_model=list[MemoryEntryResponse])
async def search_memory(
q: str = Query(..., min_length=1),
category: str | None = None,
agent_id: str | None = None,
project_path: str | None = None,
limit: int = Query(default=100, ge=1, le=1000),
):
category_enum = None
if category:
try:
category_enum = MemoryCategory(category)
except ValueError:
raise HTTPException(status_code=422, detail=f"Invalid category: {category}")
results = await memory_manager.search_service.search(
query=q,
category=category_enum,
agent_id=agent_id,
project_path=project_path,
limit=limit,
)
return results
@app.post("/api/memory/commit", response_model=CommitResponse, status_code=201)
async def create_commit(commit: CommitCreate):
result = await memory_manager.commit_service.create_commit(
message=commit.message,
agent_id=commit.agent_id,
project_path=commit.project_path,
)
return result
@app.get("/api/memory/diff/{hash1}/{hash2}", response_model=DiffResponse)
async def get_diff(hash1: str, hash2: str):
diff = await memory_manager.commit_service.diff(hash1, hash2)
if not diff:
raise HTTPException(
status_code=404,
detail=f"Commit(s) not found: {hash1}, {hash2}. Check available commits with /api/memory/log"
)
return diff
@app.get("/api/memory/{entry_id}", response_model=MemoryEntryResponse) @app.get("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
async def get_memory(entry_id: int): async def get_memory(entry_id: int):
entry = await memory_service.get_entry(entry_id) entry = await memory_manager.memory_service.get_entry(entry_id)
if not entry: if not entry:
raise HTTPException(status_code=404, detail="Entry not found") raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
return entry_to_response(entry) return entry
@app.put("/api/memory/{entry_id}", response_model=MemoryEntryResponse) @app.put("/api/memory/{entry_id}", response_model=MemoryEntryResponse)
async def update_memory(entry_id: int, entry: MemoryEntryUpdate): async def update_memory(entry_id: int, entry: MemoryEntryUpdate):
updated = await memory_service.update_entry( result = await memory_manager.memory_service.update_entry(
entry_id=entry_id, entry_id=entry_id,
title=entry.title, title=entry.title,
content=entry.content, content=entry.content,
category=entry.category, category=entry.category,
tags=entry.tags, tags=entry.tags,
) )
if not updated: if not result:
raise HTTPException(status_code=404, detail="Entry not found") raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
return entry_to_response(updated) return result
@app.delete("/api/memory/{entry_id}") @app.delete("/api/memory/{entry_id}", status_code=204)
async def delete_memory(entry_id: int): async def delete_memory(entry_id: int):
deleted = await memory_service.delete_entry(entry_id) deleted = await memory_manager.memory_service.delete_entry(entry_id)
if not deleted: if not deleted:
raise HTTPException(status_code=404, detail="Entry not found") raise HTTPException(status_code=404, detail=f"Entry {entry_id} not found")
return {"message": "Entry deleted successfully"}
@app.get("/api/memory/search/query", response_model=List[MemoryEntryResponse])
async def search_memory(
q: str = Query(..., min_length=1),
category: Optional[str] = Query(None),
tags: Optional[str] = Query(None),
):
tag_list = tags.split(",") if tags else None
entries = await memory_service.search_entries(
query=q,
category=category,
tags=tag_list,
)
return [entry_to_response(e) for e in entries]
@app.post("/api/memory/commit", response_model=CommitResponse)
async def create_commit(commit: CommitCreate):
new_commit = await commit_service.create_commit(
message=commit.message,
agent_id=commit.agent_id,
project_path=commit.project_path,
)
return CommitResponse(
id=new_commit.id,
hash=new_commit.hash,
message=new_commit.message,
agent_id=new_commit.agent_id,
project_path=new_commit.project_path,
created_at=new_commit.created_at,
)
@app.get("/api/memory/log", response_model=List[CommitResponse])
async def get_log(limit: int = Query(50, ge=1, le=1000)):
commits = await commit_service.get_commits(limit=limit)
return [
CommitResponse(
id=c.id,
hash=c.hash,
message=c.message,
agent_id=c.agent_id,
project_path=c.project_path,
created_at=c.created_at,
)
for c in commits
]
@app.get("/api/memory/diff/{hash1}/{hash2}", response_model=DiffResponse)
async def diff_commits(hash1: str, hash2: str):
diff_result = await commit_service.diff_commits(hash1, hash2)
return DiffResponse(**diff_result)
@app.get("/api/memory/stats", response_model=StatsResponse)
async def get_stats():
stats = await repository.get_stats()
return StatsResponse(**stats)