diff --git a/memora-control-plane/src/app/api/list/route.ts b/memora-control-plane/src/app/api/list/route.ts index 398fe589..2741f3d5 100644 --- a/memora-control-plane/src/app/api/list/route.ts +++ b/memora-control-plane/src/app/api/list/route.ts @@ -31,3 +31,38 @@ export async function GET(request: NextRequest) { ); } } + +export async function DELETE(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const agentId = searchParams.get('agent_id'); + const unitId = searchParams.get('unit_id'); + + if (!agentId) { + return NextResponse.json( + { error: 'agent_id is required' }, + { status: 400 } + ); + } + + if (!unitId) { + return NextResponse.json( + { error: 'unit_id is required' }, + { status: 400 } + ); + } + + const response = await fetch( + `${DATAPLANE_URL}/api/v1/agents/${agentId}/memories/${unitId}`, + { method: 'DELETE' } + ); + const data = await response.json(); + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error('Error deleting memory unit:', error); + return NextResponse.json( + { error: 'Failed to delete memory unit' }, + { status: 500 } + ); + } +} diff --git a/memora-control-plane/src/app/api/operations/[agentId]/route.ts b/memora-control-plane/src/app/api/operations/[agentId]/route.ts index 306cea16..9ee29200 100644 --- a/memora-control-plane/src/app/api/operations/[agentId]/route.ts +++ b/memora-control-plane/src/app/api/operations/[agentId]/route.ts @@ -19,3 +19,34 @@ export async function GET( ); } } + +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ agentId: string }> } +) { + try { + const { agentId } = await params; + const searchParams = request.nextUrl.searchParams; + const operationId = searchParams.get('operation_id'); + + if (!operationId) { + return NextResponse.json( + { error: 'operation_id is required' }, + { status: 400 } + ); + } + + const response = await fetch( + `${DATAPLANE_URL}/api/v1/agents/${agentId}/operations/${operationId}`, + { method: 'DELETE' } + ); + const data = await response.json(); + return NextResponse.json(data, { status: response.status }); + } catch (error) { + console.error('Error canceling operation:', error); + return NextResponse.json( + { error: 'Failed to cancel operation' }, + { status: 500 } + ); + } +} diff --git a/memora-control-plane/src/lib/agent-context.tsx b/memora-control-plane/src/lib/agent-context.tsx index ef37193a..1eb8202c 100644 --- a/memora-control-plane/src/lib/agent-context.tsx +++ b/memora-control-plane/src/lib/agent-context.tsx @@ -19,7 +19,9 @@ export function AgentProvider({ children }: { children: React.ReactNode }) { const loadAgents = async () => { try { const data = await dataplaneClient.listAgents(); - setAgents(data.agents); + // Extract agent_id from each agent object + const agentIds = data.agents.map((agent: any) => agent.agent_id); + setAgents(agentIds); } catch (error) { console.error('Error loading agents:', error); } diff --git a/memora-control-plane/src/lib/api.ts b/memora-control-plane/src/lib/api.ts index a41a473e..6961297e 100644 --- a/memora-control-plane/src/lib/api.ts +++ b/memora-control-plane/src/lib/api.ts @@ -36,10 +36,9 @@ export class DataplaneClient { reranker?: string; trace?: boolean; }) { - const { agent_id, ...body } = params; - return this.fetchApi(`/api/v1/agents/${agent_id}/memories/search`, { + return this.fetchApi(`/api/search`, { method: 'POST', - body: JSON.stringify(body), + body: JSON.stringify(params), }); } @@ -51,10 +50,9 @@ export class DataplaneClient { agent_id: string; thinking_budget?: number; }) { - const { agent_id, ...body } = params; - return this.fetchApi(`/api/v1/agents/${agent_id}/think`, { + return this.fetchApi(`/api/think`, { method: 'POST', - body: JSON.stringify(body), + body: JSON.stringify(params), }); } @@ -70,10 +68,9 @@ export class DataplaneClient { }>; document_id?: string; }) { - const { agent_id, ...body } = params; - return this.fetchApi(`/api/v1/agents/${agent_id}/memories`, { + return this.fetchApi(`/api/memories/batch`, { method: 'POST', - body: JSON.stringify(body), + body: JSON.stringify(params), }); } @@ -90,10 +87,9 @@ export class DataplaneClient { }>; document_id?: string; }) { - const { agent_id, ...body } = params; - return this.fetchApi(`/api/v1/agents/${agent_id}/memories/async`, { + return this.fetchApi(`/api/memories/batch_async`, { method: 'POST', - body: JSON.stringify(body), + body: JSON.stringify(params), }); } @@ -101,14 +97,14 @@ export class DataplaneClient { * List all agents */ async listAgents() { - return this.fetchApi<{ agents: any[] }>('/api/v1/agents', { cache: 'no-store' }); + return this.fetchApi<{ agents: any[] }>('/api/agents', { cache: 'no-store' }); } /** * Get agent statistics */ async getAgentStats(agentId: string) { - return this.fetchApi(`/api/v1/agents/${agentId}/stats`); + return this.fetchApi(`/api/stats/${agentId}`); } /** @@ -119,10 +115,10 @@ export class DataplaneClient { fact_type?: string; }) { const queryParams = new URLSearchParams(); + queryParams.append('agent_id', params.agent_id); if (params.fact_type) queryParams.append('fact_type', params.fact_type); - const path = `/api/v1/agents/${params.agent_id}/graph${queryParams.toString() ? `?${queryParams}` : ''}`; - return this.fetchApi(path); + return this.fetchApi(`/api/graph?${queryParams}`); } /** @@ -136,13 +132,13 @@ export class DataplaneClient { offset?: number; }) { const queryParams = new URLSearchParams(); + queryParams.append('agent_id', params.agent_id); if (params.fact_type) queryParams.append('fact_type', params.fact_type); if (params.q) queryParams.append('q', params.q); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.offset) queryParams.append('offset', params.offset.toString()); - const path = `/api/v1/agents/${params.agent_id}/memories/list${queryParams.toString() ? `?${queryParams}` : ''}`; - return this.fetchApi(path); + return this.fetchApi(`/api/list?${queryParams}`); } /** @@ -155,33 +151,33 @@ export class DataplaneClient { offset?: number; }) { const queryParams = new URLSearchParams(); + queryParams.append('agent_id', params.agent_id); if (params.q) queryParams.append('q', params.q); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.offset) queryParams.append('offset', params.offset.toString()); - const path = `/api/v1/agents/${params.agent_id}/documents${queryParams.toString() ? `?${queryParams}` : ''}`; - return this.fetchApi(path); + return this.fetchApi(`/api/documents?${queryParams}`); } /** * Get document by ID */ async getDocument(documentId: string, agentId: string) { - return this.fetchApi(`/api/v1/agents/${agentId}/documents/${documentId}`); + return this.fetchApi(`/api/documents/${documentId}?agent_id=${agentId}`); } /** * List async operations for an agent */ async listOperations(agentId: string) { - return this.fetchApi(`/api/v1/agents/${agentId}/operations`); + return this.fetchApi(`/api/operations/${agentId}`); } /** * Cancel a pending async operation */ async cancelOperation(agentId: string, operationId: string) { - return this.fetchApi(`/api/v1/agents/${agentId}/operations/${operationId}`, { + return this.fetchApi(`/api/operations/${agentId}?operation_id=${operationId}`, { method: 'DELETE', }); } @@ -190,7 +186,7 @@ export class DataplaneClient { * Delete a memory unit */ async deleteMemoryUnit(agentId: string, unitId: string) { - return this.fetchApi(`/api/v1/agents/${agentId}/memories/${unitId}`, { + return this.fetchApi(`/api/list?agent_id=${agentId}&unit_id=${unitId}`, { method: 'DELETE', }); } diff --git a/memora/alembic.ini b/memora/alembic.ini deleted file mode 100644 index 12d31688..00000000 --- a/memora/alembic.ini +++ /dev/null @@ -1,147 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts. -# this is typically a path given in POSIX (e.g. forward slashes) -# format, relative to the token %(here)s which refers to the location of this -# ini file -script_location = %(here)s/alembic - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. for multiple paths, the path separator -# is defined by "path_separator" below. -prepend_sys_path = . - - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the tzdata library which can be installed by adding -# `alembic[tz]` to the pip requirements. -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to /versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "path_separator" -# below. -# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions - -# path_separator; This indicates what character is used to split lists of file -# paths, including version_locations and prepend_sys_path within configparser -# files such as alembic.ini. -# The default rendered in new alembic.ini files is "os", which uses os.pathsep -# to provide os-dependent path splitting. -# -# Note that in order to support legacy alembic.ini files, this default does NOT -# take place if path_separator is not present in alembic.ini. If this -# option is omitted entirely, fallback logic is as follows: -# -# 1. Parsing of the version_locations option falls back to using the legacy -# "version_path_separator" key, which if absent then falls back to the legacy -# behavior of splitting on spaces and/or commas. -# 2. Parsing of the prepend_sys_path option falls back to the legacy -# behavior of splitting on spaces, commas, or colons. -# -# Valid values for path_separator are: -# -# path_separator = : -# path_separator = ; -# path_separator = space -# path_separator = newline -# -# Use os.pathsep. Default configuration used for new projects. -path_separator = os - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -# database URL. This is consumed by the user-maintained env.py script only. -# other means of configuring database URLs may be customized within the env.py -# file. -# sqlalchemy.url = driver://user:pass@localhost/dbname # Disabled, using DATABASE_URL from .env - - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module -# hooks = ruff -# ruff.type = module -# ruff.module = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Alternatively, use the exec runner to execute a binary found on your PATH -# hooks = ruff -# ruff.type = exec -# ruff.executable = ruff -# ruff.options = check --fix REVISION_SCRIPT_FILENAME - -# Logging configuration. This is also consumed by the user-maintained -# env.py script only. -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARNING -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARNING -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/memora/memora/api.py b/memora/memora/api.py index 7b4808ac..241bc844 100644 --- a/memora/memora/api.py +++ b/memora/memora/api.py @@ -61,7 +61,6 @@ class SearchResult(BaseModel): id: str text: str type: Optional[str] = None # fact type: world, agent, opinion - activation: Optional[float] = None context: Optional[str] = None event_date: Optional[str] = None # ISO format date string @@ -79,7 +78,6 @@ class SearchResponse(BaseModel): "id": "123e4567-e89b-12d3-a456-426614174000", "text": "Alice works at Google on the AI team", "type": "world", - "activation": 0.95, "context": "work info", "event_date": "2024-01-15T10:30:00Z" } @@ -201,7 +199,6 @@ class ThinkFact(BaseModel): id: Optional[str] = None text: str type: Optional[str] = None # fact type: world, agent, opinion - activation: Optional[float] = None context: Optional[str] = None event_date: Optional[str] = None @@ -231,14 +228,12 @@ class ThinkResponse(BaseModel): { "id": "123", "text": "AI is used in healthcare", - "type": "world", - "activation": 0.9 + "type": "world" }, { "id": "456", "text": "I discussed AI applications last week", - "type": "agent", - "activation": 0.85 + "type": "agent" } ], "new_opinions": [ diff --git a/memora/memora/temporal_semantic_memory.py b/memora/memora/temporal_semantic_memory.py index 60dde545..91170d37 100644 --- a/memora/memora/temporal_semantic_memory.py +++ b/memora/memora/temporal_semantic_memory.py @@ -799,6 +799,21 @@ class TemporalSemanticMemory( async with conn.transaction(): logger.debug("Inside transaction") try: + # Ensure agent exists in agents table (create with defaults if not exists) + # Update updated_at to reflect recent activity + logger.debug(f"Ensuring agent '{agent_id}' exists in agents table") + await conn.execute( + """ + INSERT INTO agents (agent_id, personality, background) + VALUES ($1, $2::jsonb, $3) + ON CONFLICT (agent_id) DO UPDATE + SET updated_at = NOW() + """, + agent_id, + '{"openness": 0.5, "conscientiousness": 0.5, "extraversion": 0.5, "agreeableness": 0.5, "neuroticism": 0.5, "bias_strength": 0.5}', + "" + ) + # Handle document tracking with automatic upsert if document_id: logger.debug(f"Handling document tracking for {document_id}") @@ -1593,25 +1608,6 @@ class TemporalSemanticMemory( except Exception as e: raise Exception(f"Failed to delete agent data: {str(e)}") - async def list_agents(self) -> List[str]: - """ - Get list of all agent IDs in the database. - - Returns: - List of agent IDs - """ - pool = await self._get_pool() - async with pool.acquire() as conn: - # Get distinct agent IDs from memory_units - agents = await conn.fetch(""" - SELECT DISTINCT agent_id - FROM memory_units - WHERE agent_id IS NOT NULL - ORDER BY agent_id - """) - - return [row['agent_id'] for row in agents] - async def get_graph_data(self, agent_id: Optional[str] = None, fact_type: Optional[str] = None): """ Get graph data for visualization. diff --git a/uv.lock b/uv.lock index 33870eac..1568b029 100644 --- a/uv.lock +++ b/uv.lock @@ -248,7 +248,7 @@ wheels = [ [[package]] name = "benchmarks" -version = "0.0.5" +version = "0.0.7" source = { editable = "memora-dev/benchmarks" } dependencies = [ { name = "memora" }, @@ -1408,7 +1408,7 @@ wheels = [ [[package]] name = "memora" -version = "0.0.5" +version = "0.0.7" source = { editable = "memora" } dependencies = [ { name = "alembic" }, @@ -1464,7 +1464,7 @@ provides-extras = ["test"] [[package]] name = "memora-dev" -version = "0.0.5" +version = "0.0.7" source = { editable = "memora-dev" } dependencies = [ { name = "memora" },