diff --git a/CLAUDE.md b/CLAUDE.md index f2c2e2a4..a7a53357 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,7 +100,7 @@ cd hindsight-control-plane && npm run dev Main operations: - **Retain**: Store memories, extracts facts/entities/relationships - **Recall**: Retrieve memories via 4 parallel strategies (semantic, BM25, graph, temporal) + reranking -- **Reflect**: Disposition-aware reasoning using memories and mental models +- **Reflect**: Disposition-aware reasoning using memories and mental models. ### Database PostgreSQL with pgvector. Schema managed via Alembic migrations in `hindsight-api/hindsight_api/alembic/`. Migrations run automatically on API startup. diff --git a/hindsight-api/hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py b/hindsight-api/hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py new file mode 100644 index 00000000..81ba7ac3 --- /dev/null +++ b/hindsight-api/hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py @@ -0,0 +1,134 @@ +"""Rename mental_model fact_type to observation and reflections table to mental_models + +Revision ID: t5o6p7q8r9s0 +Revises: s4n5o6p7q8r9 +Create Date: 2026-01-26 + +This migration implements the terminology rename: +1. mental_model (fact_type in memory_units) -> observation +2. reflections table -> mental_models table + +The new terminology: +- Observations: Consolidated knowledge synthesized from facts (was mental_model) +- Mental Models: Stored reflect responses (was reflections) +""" + +from collections.abc import Sequence + +from alembic import context, op + +revision: str = "t5o6p7q8r9s0" +down_revision: str | Sequence[str] | None = "s4n5o6p7q8r9" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def _get_schema_prefix() -> str: + """Get schema prefix for table names (required for multi-tenant support).""" + schema = context.config.get_main_option("target_schema") + return f'"{schema}".' if schema else "" + + +def upgrade() -> None: + """Rename mental_model -> observation and reflections -> mental_models.""" + schema = _get_schema_prefix() + + # 1. Update fact_type values: mental_model -> observation + op.execute(f""" + UPDATE {schema}memory_units + SET fact_type = 'observation' + WHERE fact_type = 'mental_model' + """) + + # 2. Update the CHECK constraint - remove mental_model, keep observation + op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check") + op.execute(f""" + ALTER TABLE {schema}memory_units + ADD CONSTRAINT memory_units_fact_type_check + CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation')) + """) + + # 3. Rename the index for observations (was for mental_models) + op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_mental_models") + op.execute(f""" + CREATE INDEX IF NOT EXISTS idx_memory_units_observations + ON {schema}memory_units(bank_id, fact_type) + WHERE fact_type = 'observation' + """) + + # 4. Update the unconsolidated index to not filter by fact_type since observations + # are now the consolidated type + op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_unconsolidated") + op.execute(f""" + CREATE INDEX IF NOT EXISTS idx_memory_units_unconsolidated + ON {schema}memory_units (bank_id, created_at) + WHERE consolidated_at IS NULL AND fact_type IN ('experience', 'world') + """) + + # 5. Rename reflections table to mental_models + op.execute(f"ALTER TABLE IF EXISTS {schema}reflections RENAME TO mental_models") + + # 6. Rename indexes for mental_models (was reflections) + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_bank_id RENAME TO idx_mental_models_bank_id") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_embedding RENAME TO idx_mental_models_embedding") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_tags RENAME TO idx_mental_models_tags") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_reflections_text_search RENAME TO idx_mental_models_text_search") + + # 7. Rename foreign key constraint + op.execute(f""" + ALTER TABLE {schema}mental_models + DROP CONSTRAINT IF EXISTS fk_reflections_bank_id + """) + op.execute(f""" + ALTER TABLE {schema}mental_models + ADD CONSTRAINT fk_mental_models_bank_id + FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE + """) + + +def downgrade() -> None: + """Reverse: observation -> mental_model and mental_models -> reflections.""" + schema = _get_schema_prefix() + + # 1. Rename mental_models table back to reflections + op.execute(f"ALTER TABLE IF EXISTS {schema}mental_models RENAME TO reflections") + + # 2. Rename indexes back + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_bank_id RENAME TO idx_reflections_bank_id") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_embedding RENAME TO idx_reflections_embedding") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_tags RENAME TO idx_reflections_tags") + op.execute(f"ALTER INDEX IF EXISTS {schema}idx_mental_models_text_search RENAME TO idx_reflections_text_search") + + # 3. Rename foreign key back + op.execute(f""" + ALTER TABLE {schema}reflections + DROP CONSTRAINT IF EXISTS fk_mental_models_bank_id + """) + op.execute(f""" + ALTER TABLE {schema}reflections + ADD CONSTRAINT fk_reflections_bank_id + FOREIGN KEY (bank_id) REFERENCES {schema}banks(bank_id) ON DELETE CASCADE + """) + + # 4. Update fact_type values: observation -> mental_model + op.execute(f""" + UPDATE {schema}memory_units + SET fact_type = 'mental_model' + WHERE fact_type = 'observation' + """) + + # 5. Update the CHECK constraint back + op.execute(f"ALTER TABLE {schema}memory_units DROP CONSTRAINT IF EXISTS memory_units_fact_type_check") + op.execute(f""" + ALTER TABLE {schema}memory_units + ADD CONSTRAINT memory_units_fact_type_check + CHECK (fact_type IN ('world', 'experience', 'opinion', 'observation', 'mental_model')) + """) + + # 6. Rename index back + op.execute(f"DROP INDEX IF EXISTS {schema}idx_memory_units_observations") + op.execute(f""" + CREATE INDEX IF NOT EXISTS idx_memory_units_mental_models + ON {schema}memory_units(bank_id, fact_type) + WHERE fact_type = 'mental_model' + """) diff --git a/hindsight-api/hindsight_api/api/http.py b/hindsight-api/hindsight_api/api/http.py index 6350965b..f50b59cf 100644 --- a/hindsight-api/hindsight_api/api/http.py +++ b/hindsight-api/hindsight_api/api/http.py @@ -92,7 +92,7 @@ class RecallRequest(BaseModel): query: str types: list[str] | None = Field( default=None, - description="List of fact types to recall: 'world', 'experience', 'mental_model'. Defaults to world and experience if not specified. " + description="List of fact types to recall: 'world', 'experience', 'observation'. Defaults to world and experience if not specified. " "Note: 'opinion' is accepted but ignored (opinions are excluded from recall).", ) budget: Budget = Budget.MID @@ -588,6 +588,14 @@ class ReflectResponse(BaseModel): "trace": { "tool_calls": [{"tool": "recall", "input": {"query": "AI"}, "duration_ms": 150}], "llm_calls": [{"scope": "agent_1", "duration_ms": 1200}], + "observations": [ + { + "id": "obs-1", + "name": "AI Technology", + "type": "concept", + "subtype": "structural", + } + ], }, } } @@ -991,7 +999,7 @@ class BankStatsResponse(BaseModel): "failed_operations": 0, "last_consolidated_at": "2024-01-15T10:30:00Z", "pending_consolidation": 0, - "total_mental_models": 45, + "total_observations": 45, } } ) @@ -1008,8 +1016,8 @@ class BankStatsResponse(BaseModel): failed_operations: int # Consolidation stats last_consolidated_at: str | None = Field(default=None, description="When consolidation last ran (ISO format)") - pending_consolidation: int = Field(default=0, description="Number of memories not yet processed into mental models") - total_mental_models: int = Field(default=0, description="Total number of mental models") + pending_consolidation: int = Field(default=0, description="Number of memories not yet processed into observations") + total_observations: int = Field(default=0, description="Total number of observations") # Mental Model models @@ -1070,12 +1078,12 @@ class UpdateDirectiveRequest(BaseModel): # ========================================================================= -# Reflections Models +# Mental Models (stored reflect responses) # ========================================================================= -class ReflectionResponse(BaseModel): - """Response model for a reflection.""" +class MentalModelResponse(BaseModel): + """Response model for a mental model (stored reflect response).""" id: str bank_id: str @@ -1087,18 +1095,18 @@ class ReflectionResponse(BaseModel): created_at: str | None = None reflect_response: dict | None = Field( default=None, - description="Full reflect API response payload including based_on facts and mental_models", + description="Full reflect API response payload including based_on facts and observations", ) -class ReflectionListResponse(BaseModel): - """Response model for listing reflections.""" +class MentalModelListResponse(BaseModel): + """Response model for listing mental models.""" - items: list[ReflectionResponse] + items: list[MentalModelResponse] -class CreateReflectionRequest(BaseModel): - """Request model for creating a reflection.""" +class CreateMentalModelRequest(BaseModel): + """Request model for creating a mental model.""" model_config = ConfigDict( json_schema_extra={ @@ -1111,20 +1119,20 @@ class CreateReflectionRequest(BaseModel): } ) - name: str = Field(description="Human-readable name for the reflection") + name: str = Field(description="Human-readable name for the mental model") source_query: str = Field(description="The query to run to generate content") tags: list[str] = Field(default_factory=list, description="Tags for scoped visibility") max_tokens: int = Field(default=2048, ge=256, le=8192, description="Maximum tokens for generated content") -class CreateReflectionResponse(BaseModel): - """Response model for reflection creation.""" +class CreateMentalModelResponse(BaseModel): + """Response model for mental model creation.""" operation_id: str = Field(description="Operation ID to track progress") -class UpdateReflectionRequest(BaseModel): - """Request model for updating a reflection.""" +class UpdateMentalModelRequest(BaseModel): + """Request model for updating a mental model.""" model_config = ConfigDict( json_schema_extra={ @@ -1134,7 +1142,7 @@ class UpdateReflectionRequest(BaseModel): } ) - name: str | None = Field(default=None, description="New name for the reflection") + name: str | None = Field(default=None, description="New name for the mental model") class OperationResponse(BaseModel): @@ -1263,7 +1271,7 @@ class AsyncOperationSubmitResponse(BaseModel): class FeaturesInfo(BaseModel): """Feature flags indicating which capabilities are enabled.""" - mental_models: bool = Field(description="Whether mental models (auto-consolidation) are enabled") + observations: bool = Field(description="Whether observations (auto-consolidation) are enabled") mcp: bool = Field(description="Whether MCP (Model Context Protocol) server is enabled") worker: bool = Field(description="Whether the background worker is enabled") @@ -1276,7 +1284,7 @@ class VersionResponse(BaseModel): "example": { "api_version": "1.0.0", "features": { - "mental_models": False, + "observations": False, "mcp": True, "worker": True, }, @@ -1523,7 +1531,7 @@ def _register_routes(app: FastAPI): return VersionResponse( api_version="1.0.0", features=FeaturesInfo( - mental_models=config.enable_mental_models, + observations=config.enable_observations, mcp=config.mcp_enabled, worker=config.worker_enabled, ), @@ -1837,7 +1845,7 @@ def _register_routes(app: FastAPI): tags_match=request.tags_match, ) - # Build based_on (memories + mental_models) if facts are requested + # Build based_on (memories + observations) if facts are requested based_on_result: ReflectBasedOn | None = None if request.include.facts is not None: memories = [] @@ -1855,7 +1863,7 @@ def _register_routes(app: FastAPI): ) based_on_result = ReflectBasedOn(memories=memories) - # Build trace (tool_calls + llm_calls + mental_models) if tool_calls is requested + # Build trace (tool_calls + llm_calls + observations) if tool_calls is requested trace_result: ReflectTrace | None = None if request.include.tool_calls is not None: include_output = request.include.tool_calls.output @@ -2020,16 +2028,16 @@ def _register_routes(app: FastAPI): last_consolidated_at = consolidation_stats["last_consolidated_at"] if consolidation_stats else None pending_consolidation = consolidation_stats["pending"] if consolidation_stats else 0 - # Count total mental models - mental_model_count_result = await conn.fetchrow( + # Count total observations (consolidated knowledge) + observation_count_result = await conn.fetchrow( f""" SELECT COUNT(*) as count FROM {fq_table("memory_units")} - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - total_mental_models = mental_model_count_result["count"] if mental_model_count_result else 0 + total_observations = observation_count_result["count"] if observation_count_result else 0 # Format results nodes_by_type = {row["fact_type"]: row["count"] for row in node_stats} @@ -2062,7 +2070,7 @@ def _register_routes(app: FastAPI): failed_operations=failed_operations, last_consolidated_at=(last_consolidated_at.isoformat() if last_consolidated_at else None), pending_consolidation=pending_consolidation, - total_mental_models=total_mental_models, + total_observations=total_observations, ) except (AuthenticationError, HTTPException): @@ -2169,18 +2177,18 @@ def _register_routes(app: FastAPI): # ========================================================================= # ========================================================================= - # REFLECTIONS ENDPOINTS + # MENTAL MODELS ENDPOINTS (stored reflect responses) # ========================================================================= @app.get( - "/v1/default/banks/{bank_id}/reflections", - response_model=ReflectionListResponse, - summary="List reflections", + "/v1/default/banks/{bank_id}/mental-models", + response_model=MentalModelListResponse, + summary="List mental models", description="List user-curated living documents that stay current.", - operation_id="list_reflections", - tags=["Reflections"], + operation_id="list_mental_models", + tags=["Mental Models"], ) - async def api_list_reflections( + async def api_list_mental_models( bank_id: str, tags_filter: list[str] | None = Query(None, alias="tags", description="Filter by tags"), tags_match: Literal["any", "all", "exact"] = Query("any", description="How to match tags"), @@ -2188,9 +2196,9 @@ def _register_routes(app: FastAPI): offset: int = Query(0, ge=0), request_context: RequestContext = Depends(get_request_context), ): - """List reflections for a bank.""" + """List mental models for a bank.""" try: - reflections = await app.state.memory.list_reflections( + mental_models = await app.state.memory.list_mental_models( bank_id=bank_id, tags=tags_filter, tags_match=tags_match, @@ -2198,66 +2206,66 @@ def _register_routes(app: FastAPI): offset=offset, request_context=request_context, ) - return ReflectionListResponse(items=[ReflectionResponse(**r) for r in reflections]) + return MentalModelListResponse(items=[MentalModelResponse(**m) for m in mental_models]) except (AuthenticationError, HTTPException): raise except Exception as e: import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in GET /v1/default/banks/{bank_id}/reflections: {error_detail}") + logger.error(f"Error in GET /v1/default/banks/{bank_id}/mental-models: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) @app.get( - "/v1/default/banks/{bank_id}/reflections/{reflection_id}", - response_model=ReflectionResponse, - summary="Get reflection", - description="Get a specific reflection by ID.", - operation_id="get_reflection", - tags=["Reflections"], + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", + response_model=MentalModelResponse, + summary="Get mental model", + description="Get a specific mental model by ID.", + operation_id="get_mental_model", + tags=["Mental Models"], ) - async def api_get_reflection( + async def api_get_mental_model( bank_id: str, - reflection_id: str, + mental_model_id: str, request_context: RequestContext = Depends(get_request_context), ): - """Get a reflection by ID.""" + """Get a mental model by ID.""" try: - reflection = await app.state.memory.get_reflection( + mental_model = await app.state.memory.get_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, request_context=request_context, ) - if reflection is None: - raise HTTPException(status_code=404, detail=f"Reflection '{reflection_id}' not found") - return ReflectionResponse(**reflection) + if mental_model is None: + raise HTTPException(status_code=404, detail=f"Mental model '{mental_model_id}' not found") + return MentalModelResponse(**mental_model) except (AuthenticationError, HTTPException): raise except Exception as e: import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in GET /v1/default/banks/{bank_id}/reflections/{reflection_id}: {error_detail}") + logger.error(f"Error in GET /v1/default/banks/{bank_id}/mental-models/{mental_model_id}: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) @app.post( - "/v1/default/banks/{bank_id}/reflections", - response_model=CreateReflectionResponse, - summary="Create reflection", - description="Create a reflection by running reflect with the source query in the background. " + "/v1/default/banks/{bank_id}/mental-models", + response_model=CreateMentalModelResponse, + summary="Create mental model", + description="Create a mental model by running reflect with the source query in the background. " "Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. " "Use the operations endpoint to check completion status.", - operation_id="create_reflection", - tags=["Reflections"], + operation_id="create_mental_model", + tags=["Mental Models"], ) - async def api_create_reflection( + async def api_create_mental_model( bank_id: str, - body: CreateReflectionRequest, + body: CreateMentalModelRequest, request_context: RequestContext = Depends(get_request_context), ): - """Create a reflection (async - returns operation_id).""" + """Create a mental model (async - returns operation_id).""" try: - result = await app.state.memory.submit_async_create_reflection( + result = await app.state.memory.submit_async_create_mental_model( bank_id=bank_id, name=body.name, source_query=body.source_query, @@ -2265,7 +2273,7 @@ def _register_routes(app: FastAPI): max_tokens=body.max_tokens, request_context=request_context, ) - return CreateReflectionResponse(operation_id=result["operation_id"]) + return CreateMentalModelResponse(operation_id=result["operation_id"]) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except (AuthenticationError, HTTPException): @@ -2274,27 +2282,27 @@ def _register_routes(app: FastAPI): import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in POST /v1/default/banks/{bank_id}/reflections: {error_detail}") + logger.error(f"Error in POST /v1/default/banks/{bank_id}/mental-models: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) @app.post( - "/v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh", + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh", response_model=AsyncOperationSubmitResponse, - summary="Refresh reflection", + summary="Refresh mental model", description="Submit an async task to re-run the source query through reflect and update the content.", - operation_id="refresh_reflection", - tags=["Reflections"], + operation_id="refresh_mental_model", + tags=["Mental Models"], ) - async def api_refresh_reflection( + async def api_refresh_mental_model( bank_id: str, - reflection_id: str, + mental_model_id: str, request_context: RequestContext = Depends(get_request_context), ): - """Refresh a reflection by re-running its source query (async).""" + """Refresh a mental model by re-running its source query (async).""" try: - result = await app.state.memory.submit_async_refresh_reflection( + result = await app.state.memory.submit_async_refresh_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, request_context=request_context, ) return AsyncOperationSubmitResponse(operation_id=result["operation_id"], status="queued") @@ -2307,65 +2315,65 @@ def _register_routes(app: FastAPI): error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" logger.error( - f"Error in POST /v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh: {error_detail}" + f"Error in POST /v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh: {error_detail}" ) raise HTTPException(status_code=500, detail=str(e)) @app.patch( - "/v1/default/banks/{bank_id}/reflections/{reflection_id}", - response_model=ReflectionResponse, - summary="Update reflection", - description="Update a reflection's name.", - operation_id="update_reflection", - tags=["Reflections"], + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", + response_model=MentalModelResponse, + summary="Update mental model", + description="Update a mental model's name.", + operation_id="update_mental_model", + tags=["Mental Models"], ) - async def api_update_reflection( + async def api_update_mental_model( bank_id: str, - reflection_id: str, - body: UpdateReflectionRequest, + mental_model_id: str, + body: UpdateMentalModelRequest, request_context: RequestContext = Depends(get_request_context), ): - """Update a reflection.""" + """Update a mental model.""" try: - reflection = await app.state.memory.update_reflection( + mental_model = await app.state.memory.update_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, name=body.name, request_context=request_context, ) - if reflection is None: - raise HTTPException(status_code=404, detail=f"Reflection '{reflection_id}' not found") - return ReflectionResponse(**reflection) + if mental_model is None: + raise HTTPException(status_code=404, detail=f"Mental model '{mental_model_id}' not found") + return MentalModelResponse(**mental_model) except (AuthenticationError, HTTPException): raise except Exception as e: import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in PATCH /v1/default/banks/{bank_id}/reflections/{reflection_id}: {error_detail}") + logger.error(f"Error in PATCH /v1/default/banks/{bank_id}/mental-models/{mental_model_id}: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) @app.delete( - "/v1/default/banks/{bank_id}/reflections/{reflection_id}", - summary="Delete reflection", - description="Delete a reflection.", - operation_id="delete_reflection", - tags=["Reflections"], + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", + summary="Delete mental model", + description="Delete a mental model.", + operation_id="delete_mental_model", + tags=["Mental Models"], ) - async def api_delete_reflection( + async def api_delete_mental_model( bank_id: str, - reflection_id: str, + mental_model_id: str, request_context: RequestContext = Depends(get_request_context), ): - """Delete a reflection.""" + """Delete a mental model.""" try: - deleted = await app.state.memory.delete_reflection( + deleted = await app.state.memory.delete_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, request_context=request_context, ) if not deleted: - raise HTTPException(status_code=404, detail=f"Reflection '{reflection_id}' not found") + raise HTTPException(status_code=404, detail=f"Mental model '{mental_model_id}' not found") return {"status": "deleted"} except (AuthenticationError, HTTPException): raise @@ -2373,7 +2381,7 @@ def _register_routes(app: FastAPI): import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in DELETE /v1/default/banks/{bank_id}/reflections/{reflection_id}: {error_detail}") + logger.error(f"Error in DELETE /v1/default/banks/{bank_id}/mental-models/{mental_model_id}: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) # ========================================================================= @@ -3097,20 +3105,20 @@ def _register_routes(app: FastAPI): raise HTTPException(status_code=500, detail=str(e)) @app.delete( - "/v1/default/banks/{bank_id}/mental-models", + "/v1/default/banks/{bank_id}/observations", response_model=DeleteResponse, - summary="Clear all mental models", - description="Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge.", - operation_id="clear_mental_models", + summary="Clear all observations", + description="Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge.", + operation_id="clear_observations", tags=["Banks"], ) - async def api_clear_mental_models(bank_id: str, request_context: RequestContext = Depends(get_request_context)): - """Clear all mental models for a bank.""" + async def api_clear_observations(bank_id: str, request_context: RequestContext = Depends(get_request_context)): + """Clear all observations for a bank.""" try: - result = await app.state.memory.clear_mental_models(bank_id, request_context=request_context) + result = await app.state.memory.clear_observations(bank_id, request_context=request_context) return DeleteResponse( success=True, - message=f"Cleared {result.get('deleted_count', 0)} mental models", + message=f"Cleared {result.get('deleted_count', 0)} observations", deleted_count=result.get("deleted_count", 0), ) except (AuthenticationError, HTTPException): @@ -3119,14 +3127,14 @@ def _register_routes(app: FastAPI): import traceback error_detail = f"{str(e)}\n\nTraceback:\n{traceback.format_exc()}" - logger.error(f"Error in DELETE /v1/default/banks/{bank_id}/mental-models: {error_detail}") + logger.error(f"Error in DELETE /v1/default/banks/{bank_id}/observations: {error_detail}") raise HTTPException(status_code=500, detail=str(e)) @app.post( "/v1/default/banks/{bank_id}/consolidate", response_model=ConsolidationResponse, summary="Trigger consolidation", - description="Run memory consolidation to create/update mental models from recent memories.", + description="Run memory consolidation to create/update observations from recent memories.", operation_id="trigger_consolidation", tags=["Banks"], ) diff --git a/hindsight-api/hindsight_api/config.py b/hindsight-api/hindsight_api/config.py index c7bd9f37..f4c3265d 100644 --- a/hindsight-api/hindsight_api/config.py +++ b/hindsight-api/hindsight_api/config.py @@ -87,7 +87,7 @@ ENV_MCP_LOCAL_BANK_ID = "HINDSIGHT_API_MCP_LOCAL_BANK_ID" ENV_MCP_INSTRUCTIONS = "HINDSIGHT_API_MCP_INSTRUCTIONS" ENV_MENTAL_MODEL_REFRESH_CONCURRENCY = "HINDSIGHT_API_MENTAL_MODEL_REFRESH_CONCURRENCY" -# Observation thresholds +# Observation settings (consolidated knowledge from facts) ENV_OBSERVATION_MIN_FACTS = "HINDSIGHT_API_OBSERVATION_MIN_FACTS" ENV_OBSERVATION_TOP_ENTITIES = "HINDSIGHT_API_OBSERVATION_TOP_ENTITIES" @@ -98,8 +98,8 @@ ENV_RETAIN_EXTRACT_CAUSAL_LINKS = "HINDSIGHT_API_RETAIN_EXTRACT_CAUSAL_LINKS" ENV_RETAIN_EXTRACTION_MODE = "HINDSIGHT_API_RETAIN_EXTRACTION_MODE" ENV_RETAIN_OBSERVATIONS_ASYNC = "HINDSIGHT_API_RETAIN_OBSERVATIONS_ASYNC" -# Mental models settings -ENV_ENABLE_MENTAL_MODELS = "HINDSIGHT_API_ENABLE_MENTAL_MODELS" +# Observations settings (consolidated knowledge from facts) +ENV_ENABLE_OBSERVATIONS = "HINDSIGHT_API_ENABLE_OBSERVATIONS" ENV_CONSOLIDATION_SIMILARITY_THRESHOLD = "HINDSIGHT_API_CONSOLIDATION_SIMILARITY_THRESHOLD" ENV_CONSOLIDATION_BATCH_SIZE = "HINDSIGHT_API_CONSOLIDATION_BATCH_SIZE" @@ -181,8 +181,8 @@ DEFAULT_RETAIN_EXTRACTION_MODE = "concise" # Extraction mode: "concise" or "ver RETAIN_EXTRACTION_MODES = ("concise", "verbose") # Allowed extraction modes DEFAULT_RETAIN_OBSERVATIONS_ASYNC = False # Run observation generation async (after retain completes) -# Mental models defaults -DEFAULT_ENABLE_MENTAL_MODELS = False # Mental models disabled by default (experimental) +# Observations defaults (consolidated knowledge from facts) +DEFAULT_ENABLE_OBSERVATIONS = False # Observations disabled by default (experimental) DEFAULT_CONSOLIDATION_SIMILARITY_THRESHOLD = 0.75 # Minimum similarity to consider a learning related DEFAULT_CONSOLIDATION_BATCH_SIZE = 50 # Memories to load per batch (internal memory optimization) @@ -344,8 +344,8 @@ class HindsightConfig: retain_extraction_mode: str retain_observations_async: bool - # Mental models settings - enable_mental_models: bool + # Observations settings (consolidated knowledge from facts) + enable_observations: bool consolidation_similarity_threshold: float consolidation_batch_size: int @@ -455,9 +455,8 @@ class HindsightConfig: ENV_RETAIN_OBSERVATIONS_ASYNC, str(DEFAULT_RETAIN_OBSERVATIONS_ASYNC) ).lower() == "true", - # Mental models settings - enable_mental_models=os.getenv(ENV_ENABLE_MENTAL_MODELS, str(DEFAULT_ENABLE_MENTAL_MODELS)).lower() - == "true", + # Observations settings (consolidated knowledge from facts) + enable_observations=os.getenv(ENV_ENABLE_OBSERVATIONS, str(DEFAULT_ENABLE_OBSERVATIONS)).lower() == "true", consolidation_similarity_threshold=float( os.getenv(ENV_CONSOLIDATION_SIMILARITY_THRESHOLD, str(DEFAULT_CONSOLIDATION_SIMILARITY_THRESHOLD)) ), diff --git a/hindsight-api/hindsight_api/engine/consolidation/consolidator.py b/hindsight-api/hindsight_api/engine/consolidation/consolidator.py index d80b23f3..9410b0b6 100644 --- a/hindsight-api/hindsight_api/engine/consolidation/consolidator.py +++ b/hindsight-api/hindsight_api/engine/consolidation/consolidator.py @@ -1,13 +1,13 @@ -"""Consolidation engine for automatic mental model creation from memories. +"""Consolidation engine for automatic observation creation from memories. The consolidation engine runs as a background job after retain operations complete. It processes new memories and either: -- Creates new mental models from novel facts -- Updates existing mental models when new evidence supports/contradicts/refines them +- Creates new observations from novel facts +- Updates existing observations when new evidence supports/contradicts/refines them -Mental models are stored in memory_units with fact_type='mental_model' and include: +Observations are stored in memory_units with fact_type='observation' and include: - proof_count: Number of supporting memories -- source_memory_ids: Array of memory UUIDs that contribute to this mental model +- source_memory_ids: Array of memory UUIDs that contribute to this observation - history: JSONB tracking changes over time """ @@ -89,7 +89,7 @@ async def run_consolidation_job( max_memories_per_batch = config.consolidation_batch_size # Check if consolidation is enabled - if not config.enable_mental_models: + if not config.enable_observations: logger.debug(f"Consolidation disabled for bank {bank_id}") return {"status": "disabled", "bank_id": bank_id} @@ -136,9 +136,9 @@ async def run_consolidation_job( # Process each memory with individual commits for crash recovery stats = { "memories_processed": 0, - "mental_models_created": 0, - "mental_models_updated": 0, - "mental_models_merged": 0, + "observations_created": 0, + "observations_updated": 0, + "observations_merged": 0, "actions_executed": 0, "skipped": 0, } @@ -201,18 +201,18 @@ async def run_consolidation_job( action = result.get("action") if action == "created": - stats["mental_models_created"] += 1 + stats["observations_created"] += 1 stats["actions_executed"] += 1 elif action == "updated": - stats["mental_models_updated"] += 1 + stats["observations_updated"] += 1 stats["actions_executed"] += 1 elif action == "merged": - stats["mental_models_merged"] += 1 + stats["observations_merged"] += 1 stats["actions_executed"] += 1 elif action == "multiple": - stats["mental_models_created"] += result.get("created", 0) - stats["mental_models_updated"] += result.get("updated", 0) - stats["mental_models_merged"] += result.get("merged", 0) + stats["observations_created"] += result.get("created", 0) + stats["observations_updated"] += result.get("updated", 0) + stats["observations_merged"] += result.get("merged", 0) stats["actions_executed"] += result.get("total_actions", 0) elif action == "skipped": stats["skipped"] += 1 @@ -234,9 +234,9 @@ async def run_consolidation_job( perf.log( f"[3] Results: {stats['memories_processed']} memories -> " f"{stats['actions_executed']} actions " - f"({stats['mental_models_created']} created, " - f"{stats['mental_models_updated']} updated, " - f"{stats['mental_models_merged']} merged, " + f"({stats['observations_created']} created, " + f"{stats['observations_updated']} updated, " + f"{stats['observations_merged']} merged, " f"{stats['skipped']} skipped)" ) @@ -272,13 +272,13 @@ async def _process_memory( Process a single memory for consolidation using a SINGLE LLM call. This function: - 1. Finds related mental models (can be empty) + 1. Finds related observations (can be empty) 2. Uses ONE LLM call to extract durable knowledge AND decide on actions 3. Executes array of actions (can be multiple creates/updates) The LLM handles all cases: - - No related models: returns create action(s) with extracted durable knowledge - - Related models exist: returns update/create actions based on tag routing + - No related observations: returns create action(s) with extracted durable knowledge + - Related observations exist: returns update/create actions based on tag routing - Purely ephemeral fact: returns empty array (skip) Returns: @@ -288,9 +288,9 @@ async def _process_memory( memory_id = memory["id"] fact_tags = memory.get("tags") or [] - # Find related mental models using the full recall system (NO tag filtering) + # Find related observations using the full recall system (NO tag filtering) t0 = time.time() - related_mental_models = await _find_related_mental_models( + related_observations = await _find_related_observations( conn=conn, memory_engine=memory_engine, bank_id=bank_id, @@ -300,13 +300,13 @@ async def _process_memory( if perf: perf.record_timing("recall", time.time() - t0) - # Single LLM call handles ALL cases (with or without existing models) + # Single LLM call handles ALL cases (with or without existing observations) t0 = time.time() actions = await _consolidate_with_llm( memory_engine=memory_engine, fact_text=fact_text, fact_tags=fact_tags, - mental_models=related_mental_models, # Can be empty list + observations=related_observations, # Can be empty list mission=mission, ) if perf: @@ -327,7 +327,7 @@ async def _process_memory( bank_id=bank_id, memory_id=memory_id, action=action, - mental_models=related_mental_models, + observations=related_observations, source_mentioned_at=memory.get("mentioned_at"), perf=perf, ) @@ -373,14 +373,14 @@ async def _execute_update_action( bank_id: str, memory_id: uuid.UUID, action: dict[str, Any], - mental_models: list[dict[str, Any]], + observations: list[dict[str, Any]], source_mentioned_at: datetime | None = None, perf: ConsolidationPerfLog | None = None, ) -> dict[str, Any]: """ - Execute an update action on an existing mental model. + Execute an update action on an existing observation. - Updates the mental model text, adds to history, increments proof_count, + Updates the observation text, adds to history, increments proof_count, and updates mentioned_at if the new source memory has a more recent date. """ learning_id = action.get("learning_id") @@ -390,8 +390,8 @@ async def _execute_update_action( if not learning_id or not new_text: return {"action": "skipped", "reason": "missing_learning_id_or_text"} - # Find the mental model - model = next((m for m in mental_models if str(m["id"]) == learning_id), None) + # Find the observation + model = next((m for m in observations if str(m["id"]) == learning_id), None) if not model: return {"action": "skipped", "reason": "learning_not_found"} @@ -441,14 +441,14 @@ async def _execute_update_action( source_mentioned_at, ) - # Create links from memory to mental model + # Create links from memory to observation await _create_memory_links(conn, memory_id, uuid.UUID(learning_id)) if perf: perf.record_timing("db_write", time.time() - t0) - logger.debug(f"Updated mental model {learning_id} with memory {memory_id}") + logger.debug(f"Updated observation {learning_id} with memory {memory_id}") - return {"action": "updated", "mental_model_id": learning_id} + return {"action": "updated", "observation_id": learning_id} async def _execute_create_action( @@ -463,9 +463,9 @@ async def _execute_create_action( perf: ConsolidationPerfLog | None = None, ) -> dict[str, Any]: """ - Execute a create action for a new mental model. + Execute a create action for a new observation. - Creates a new mental model with the specified text and tags. + Creates a new observation with the specified text and tags. The text comes directly from the classify LLM - no second LLM call needed. """ text = action.get("text") @@ -475,12 +475,12 @@ async def _execute_create_action( return {"action": "skipped", "reason": "missing_text"} # Use text directly from classify - skip the redundant LLM call - result = await _create_mental_model_directly( + result = await _create_observation_directly( conn=conn, memory_engine=memory_engine, bank_id=bank_id, source_memory_id=memory_id, - mental_model_text=text, # Text already processed by classify LLM + observation_text=text, # Text already processed by classify LLM tags=tags, event_date=event_date, occurred_start=occurred_start, @@ -488,7 +488,7 @@ async def _execute_create_action( perf=perf, ) - logger.debug(f"Created mental model {result.get('mental_model_id')} from memory {memory_id} (tags: {tags})") + logger.debug(f"Created observation {result.get('observation_id')} from memory {memory_id} (tags: {tags})") return result @@ -496,17 +496,17 @@ async def _execute_create_action( async def _create_memory_links( conn: "Connection", memory_id: uuid.UUID, - mental_model_id: uuid.UUID, + observation_id: uuid.UUID, ) -> None: """ - Create links between a source memory and its mental model. + Create links between a source memory and its observation. This: - 1. Creates bidirectional semantic links between memory and mental model - 2. Copies existing memory_links from the source memory to the mental model - 3. Copies entity links from the source memory to the mental model + 1. Creates bidirectional semantic links between memory and observation + 2. Copies existing memory_links from the source memory to the observation + 3. Copies entity links from the source memory to the observation - This enables graph traversal to find related memories via their mental models. + This enables graph traversal to find related memories via their observations. Note: Uses EXISTS checks to handle the case where source memory was deleted by a concurrent operation between fetching and link creation. @@ -515,7 +515,7 @@ async def _create_memory_links( ml_table = fq_table("memory_links") ue_table = fq_table("unit_entities") - # 1. Bidirectional link between memory and mental model + # 1. Bidirectional link between memory and observation # Only insert if both units exist (handles concurrent deletion) await conn.execute( f""" @@ -526,7 +526,7 @@ async def _create_memory_links( ON CONFLICT DO NOTHING """, memory_id, - mental_model_id, + observation_id, ) await conn.execute( f""" @@ -536,12 +536,12 @@ async def _create_memory_links( AND EXISTS (SELECT 1 FROM {mu_table} WHERE id = $2) ON CONFLICT DO NOTHING """, - mental_model_id, + observation_id, memory_id, ) - # 2. Copy outgoing memory_links from source memory to mental model - # If source memory links to X, mental model should also link to X + # 2. Copy outgoing memory_links from source memory to observation + # If source memory links to X, observation should also link to X await conn.execute( f""" INSERT INTO {ml_table} (from_unit_id, to_unit_id, link_type, entity_id, weight) @@ -552,12 +552,12 @@ async def _create_memory_links( AND EXISTS (SELECT 1 FROM {mu_table} WHERE id = ml.to_unit_id) ON CONFLICT DO NOTHING """, - mental_model_id, + observation_id, memory_id, ) - # 3. Copy incoming memory_links from source memory to mental model - # If X links to source memory, X should also link to mental model + # 3. Copy incoming memory_links from source memory to observation + # If X links to source memory, X should also link to observation await conn.execute( f""" INSERT INTO {ml_table} (from_unit_id, to_unit_id, link_type, entity_id, weight) @@ -568,11 +568,11 @@ async def _create_memory_links( AND EXISTS (SELECT 1 FROM {mu_table} WHERE id = ml.from_unit_id) ON CONFLICT DO NOTHING """, - mental_model_id, + observation_id, memory_id, ) - # 4. Copy entity links from source memory to mental model + # 4. Copy entity links from source memory to observation await conn.execute( f""" INSERT INTO {ue_table} (unit_id, entity_id) @@ -582,12 +582,12 @@ async def _create_memory_links( AND EXISTS (SELECT 1 FROM {mu_table} WHERE id = $1) ON CONFLICT DO NOTHING """, - mental_model_id, + observation_id, memory_id, ) -async def _find_related_mental_models( +async def _find_related_observations( conn: "Connection", memory_engine: "MemoryEngine", bank_id: str, @@ -595,10 +595,10 @@ async def _find_related_mental_models( request_context: "RequestContext", ) -> list[dict[str, Any]]: """ - Find mental models related to the given query using the full recall system. + Find observations related to the given query using the full recall system. IMPORTANT: We do NOT filter by tags here. Consolidation needs to see ALL - potentially related mental models regardless of scope, so the LLM can + potentially related observations regardless of scope, so the LLM can decide on tag routing (same scope update vs cross-scope create). This leverages: @@ -608,37 +608,37 @@ async def _find_related_mental_models( - Graph traversal (connected via entity links) Returns: - List of related mental models with their tags for LLM tag routing + List of related observations with their tags for LLM tag routing """ - # Use recall to find related mental models - # NO tags parameter - we want ALL mental models regardless of scope - # Use low max_tokens since we only need mental models, not memories + # Use recall to find related observations + # NO tags parameter - we want ALL observations regardless of scope + # Use low max_tokens since we only need observations, not memories recall_result = await memory_engine.recall_async( bank_id=bank_id, query=query, - max_tokens=5000, # Token budget for mental models - fact_type=["mental_model"], # Only retrieve mental models + max_tokens=5000, # Token budget for observations + fact_type=["observation"], # Only retrieve observations request_context=request_context, _quiet=True, # Suppress logging - # NO tags parameter - intentionally get ALL mental models + # NO tags parameter - intentionally get ALL observations ) - # If no mental models returned, return empty list - # When fact_type=["mental_model"], results come back in `results` field + # If no observations returned, return empty list + # When fact_type=["observation"], results come back in `results` field if not recall_result.results: return [] - # Trust recall's relevance filtering - fetch full data for each mental model + # Trust recall's relevance filtering - fetch full data for each observation results = [] - for mm in recall_result.results: - # Fetch full mental model data from DB to get history, source_memory_ids, tags + for obs in recall_result.results: + # Fetch full observation data from DB to get history, source_memory_ids, tags row = await conn.fetchrow( f""" SELECT id, text, proof_count, history, tags, source_memory_ids, created_at, updated_at FROM {fq_table("memory_units")} - WHERE id = $1 AND bank_id = $2 AND fact_type = 'mental_model' + WHERE id = $1 AND bank_id = $2 AND fact_type = 'observation' """, - uuid.UUID(mm.id), + uuid.UUID(obs.id), bank_id, ) @@ -668,15 +668,15 @@ async def _consolidate_with_llm( memory_engine: "MemoryEngine", fact_text: str, fact_tags: list[str], - mental_models: list[dict[str, Any]], + observations: list[dict[str, Any]], mission: str, ) -> list[dict[str, Any]]: """ Single LLM call to extract durable knowledge and decide on consolidation actions. This handles ALL cases: - - No related mental models: extracts durable knowledge, returns create action - - Related models exist: compares and returns update/create actions + - No related observations: extracts durable knowledge, returns create action + - Related observations exist: compares and returns update/create actions - Purely ephemeral fact: returns empty array Returns: @@ -685,14 +685,14 @@ async def _consolidate_with_llm( - {"action": "create", "tags": [...], "text": "...", "reason": "..."} - [] if fact is purely ephemeral (no durable knowledge) """ - # Format mental models WITH their tags (or "None" if empty) - if mental_models: - mental_models_text = "\n".join( - f'- ID: {mm["id"]}, Tags: {json.dumps(mm["tags"])}, Text: "{mm["text"]}" (proof_count: {mm["proof_count"]})' - for mm in mental_models + # Format observations WITH their tags (or "None" if empty) + if observations: + observations_text = "\n".join( + f'- ID: {obs["id"]}, Tags: {json.dumps(obs["tags"])}, Text: "{obs["text"]}" (proof_count: {obs["proof_count"]})' + for obs in observations ) else: - mental_models_text = "None (this is a new topic - create if fact contains durable knowledge)" + observations_text = "None (this is a new topic - create if fact contains durable knowledge)" # Only include mission section if mission is set and not the default mission_section = "" @@ -707,7 +707,7 @@ Focus on DURABLE knowledge that serves this mission, not ephemeral state. mission_section=mission_section, fact_text=fact_text, fact_tags=json.dumps(fact_tags), - mental_models_text=mental_models_text, + observations_text=observations_text, ) messages = [ @@ -746,12 +746,12 @@ Focus on DURABLE knowledge that serves this mission, not ephemeral state. return [] -async def _create_mental_model_directly( +async def _create_observation_directly( conn: "Connection", memory_engine: "MemoryEngine", bank_id: str, source_memory_id: uuid.UUID, - mental_model_text: str, + observation_text: str, tags: list[str] | None = None, event_date: datetime | None = None, occurred_start: datetime | None = None, @@ -759,52 +759,52 @@ async def _create_mental_model_directly( perf: ConsolidationPerfLog | None = None, ) -> dict[str, Any]: """ - Create a mental model directly with pre-processed text (no LLM call). + Create an observation directly with pre-processed text (no LLM call). Used when the classify LLM has already provided the learning text. This avoids the redundant second LLM call. """ - # Generate embedding for the mental model (convert to string for pgvector) + # Generate embedding for the observation (convert to string for pgvector) t0 = time.time() - embeddings = await embedding_utils.generate_embeddings_batch(memory_engine.embeddings, [mental_model_text]) + embeddings = await embedding_utils.generate_embeddings_batch(memory_engine.embeddings, [observation_text]) embedding_str = str(embeddings[0]) if embeddings else None if perf: perf.record_timing("embedding", time.time() - t0) - # Create the mental model as a memory_unit + # Create the observation as a memory_unit now = datetime.now(timezone.utc) - mm_event_date = event_date or now - mm_occurred_start = occurred_start or now - mm_mentioned_at = mentioned_at or now - mm_tags = tags or [] + obs_event_date = event_date or now + obs_occurred_start = occurred_start or now + obs_mentioned_at = mentioned_at or now + obs_tags = tags or [] t0 = time.time() - mental_model_id = uuid.uuid4() + observation_id = uuid.uuid4() row = await conn.fetchrow( f""" INSERT INTO {fq_table("memory_units")} ( id, bank_id, text, fact_type, embedding, proof_count, source_memory_ids, history, tags, event_date, occurred_start, mentioned_at ) - VALUES ($1, $2, $3, 'mental_model', $4::vector, 1, $5, '[]'::jsonb, $6, $7, $8, $9) + VALUES ($1, $2, $3, 'observation', $4::vector, 1, $5, '[]'::jsonb, $6, $7, $8, $9) RETURNING id """, - mental_model_id, + observation_id, bank_id, - mental_model_text, + observation_text, embedding_str, [source_memory_id], - mm_tags, - mm_event_date, - mm_occurred_start, - mm_mentioned_at, + obs_tags, + obs_event_date, + obs_occurred_start, + obs_mentioned_at, ) - # Create links between memory and mental model (includes entity links, memory_links) - await _create_memory_links(conn, source_memory_id, mental_model_id) + # Create links between memory and observation (includes entity links, memory_links) + await _create_memory_links(conn, source_memory_id, observation_id) if perf: perf.record_timing("db_write", time.time() - t0) - logger.debug(f"Created mental model {mental_model_id} from memory {source_memory_id} (tags: {mm_tags})") + logger.debug(f"Created observation {observation_id} from memory {source_memory_id} (tags: {obs_tags})") - return {"action": "created", "mental_model_id": str(row["id"]), "tags": mm_tags} + return {"action": "created", "observation_id": str(row["id"]), "tags": obs_tags} diff --git a/hindsight-api/hindsight_api/engine/consolidation/prompts.py b/hindsight-api/hindsight_api/engine/consolidation/prompts.py index 59e12977..45189f2f 100644 --- a/hindsight-api/hindsight_api/engine/consolidation/prompts.py +++ b/hindsight-api/hindsight_api/engine/consolidation/prompts.py @@ -1,6 +1,6 @@ """Prompts for the consolidation engine.""" -CONSOLIDATION_SYSTEM_PROMPT = """You are a memory consolidation system. Your job is to convert facts into durable knowledge (mental models) and merge with existing knowledge when appropriate. +CONSOLIDATION_SYSTEM_PROMPT = """You are a memory consolidation system. Your job is to convert facts into durable knowledge (observations) and merge with existing knowledge when appropriate. You must output ONLY valid JSON with no markdown formatting, no code blocks, and no additional text. @@ -30,28 +30,28 @@ BAD examples: - "John likes pizza" -> "Understanding dietary preferences helps..." (TOO ABSTRACT) - "User is at Room 203" -> "User is currently at Room 203" (EPHEMERAL STATE) -## MERGE RULES (when comparing to existing mental models): +## MERGE RULES (when comparing to existing observations): 1. REDUNDANT: Same information worded differently → update existing 2. CONTRADICTION: Opposite information about same topic → update with history (e.g., "used to X, now Y") 3. UPDATE: New state replacing old state → update with history ## TAG ROUTING RULES: -Tags define visibility scopes. The fact and each mental model have tags (can be empty = global). +Tags define visibility scopes. The fact and each observation have tags (can be empty = global). -| Fact Tags | Model Tags | Action | -|-----------|------------|--------| -| [alice] | [alice] | UPDATE the model (same scope) | -| [alice] | [] | UPDATE the model (global absorbs all scopes) | -| [alice] | [bob] | CREATE new untagged model (cross-scope insight) | -| [] | [alice] | UPDATE the model (untagged facts can update any scope) | -| [] | [] | UPDATE the model (global to global) | +| Fact Tags | Obs Tags | Action | +|-----------|----------|--------| +| [alice] | [alice] | UPDATE the observation (same scope) | +| [alice] | [] | UPDATE the observation (global absorbs all scopes) | +| [alice] | [bob] | CREATE new untagged observation (cross-scope insight) | +| [] | [alice] | UPDATE the observation (untagged facts can update any scope) | +| [] | [] | UPDATE the observation (global to global) | -When NO existing model matches the fact's topic: CREATE new model with fact's tags. +When NO existing observation matches the fact's topic: CREATE new observation with fact's tags. ## MULTIPLE ACTIONS: One fact can trigger MULTIPLE actions. For example: -- Update a scoped model [alice] about pizza preferences -- AND update a global model [] about pizza in general +- Update a scoped observation [alice] about pizza preferences +- AND update a global observation [] about pizza in general Output an ARRAY of actions (can be empty, one, or many). @@ -59,7 +59,7 @@ Output an ARRAY of actions (can be empty, one, or many). - NEVER merge facts about DIFFERENT people - NEVER merge unrelated topics (food preferences vs work vs hobbies) - When merging contradictions, capture the CHANGE (before → after) -- Keep mental models focused on ONE specific topic per person +- Keep observations focused on ONE specific topic per person - Cross-scope insights (alice's fact about bob's topic) become UNTAGGED (global) - The "text" field MUST contain durable knowledge, not ephemeral state""" @@ -68,14 +68,14 @@ CONSOLIDATION_USER_PROMPT = """Analyze this new fact and consolidate into knowle NEW FACT: {fact_text} FACT TAGS: {fact_tags} -EXISTING MENTAL MODELS: -{mental_models_text} +EXISTING OBSERVATIONS: +{observations_text} Instructions: 1. First, extract the DURABLE KNOWLEDGE from the fact (not ephemeral state like "user is at X") -2. Then compare with existing mental models: - - If a model covers the same topic: UPDATE it with the new knowledge - - If no model covers the topic: CREATE a new one +2. Then compare with existing observations: + - If an observation covers the same topic: UPDATE it with the new knowledge + - If no observation covers the topic: CREATE a new one - If fact is about different scope: apply tag routing rules Output JSON array of actions (ALWAYS an array, even for single action): @@ -87,5 +87,5 @@ Output JSON array of actions (ALWAYS an array, even for single action): If NO consolidation is needed (fact is purely ephemeral with no durable knowledge): [] -If no models exist and fact contains durable knowledge: +If no observations exist and fact contains durable knowledge: [{{"action": "create", "tags": {fact_tags}, "text": "durable knowledge text", "reason": "new topic"}}]""" diff --git a/hindsight-api/hindsight_api/engine/memory_engine.py b/hindsight-api/hindsight_api/engine/memory_engine.py index b322ee7f..f70cd12b 100644 --- a/hindsight-api/hindsight_api/engine/memory_engine.py +++ b/hindsight-api/hindsight_api/engine/memory_engine.py @@ -141,15 +141,15 @@ from .entity_resolver import EntityResolver from .llm_wrapper import LLMConfig from .query_analyzer import QueryAnalyzer from .reflect import run_reflect_agent -from .reflect.models import MentalModelInput -from .reflect.tools import tool_expand, tool_recall, tool_search_mental_models +from .reflect.models import ObservationInput +from .reflect.tools import tool_expand, tool_recall, tool_search_mental_models, tool_search_observations from .response_models import ( VALID_RECALL_FACT_TYPES, EntityObservation, EntityState, LLMCallTrace, MemoryFact, - MentalModelRef, + ObservationRef, ReflectResult, TokenUsage, ToolCallTrace, @@ -561,29 +561,29 @@ class MemoryEngine(MemoryEngineInterface): logger.info(f"[CONSOLIDATION] bank={bank_id} completed: {result.get('memories_processed', 0)} processed") - async def _handle_create_reflection(self, task_dict: dict[str, Any]): + async def _handle_create_mental_model(self, task_dict: dict[str, Any]): """ - Handler for create_reflection tasks. + Handler for create_mental_model tasks. - Runs reflect with the source query and updates the reflection with the generated content. - The reflection should already exist in the database (created during submit_async_create_reflection). + Runs reflect with the source query and updates the mental model with the generated content. + The mental model should already exist in the database (created during submit_async_create_mental_model). Args: - task_dict: Dict with 'bank_id', 'reflection_id', 'source_query', 'max_tokens', 'operation_id' + task_dict: Dict with 'bank_id', 'mental_model_id', 'source_query', 'max_tokens', 'operation_id' Raises: ValueError: If required fields are missing Exception: Any exception from reflect/update (propagates to execute_task for retry) """ bank_id = task_dict.get("bank_id") - reflection_id = task_dict.get("reflection_id") + mental_model_id = task_dict.get("mental_model_id") source_query = task_dict.get("source_query") max_tokens = task_dict.get("max_tokens", 2048) - if not bank_id or not reflection_id or not source_query: - raise ValueError("bank_id, reflection_id, and source_query are required for create_reflection task") + if not bank_id or not mental_model_id or not source_query: + raise ValueError("bank_id, mental_model_id, and source_query are required for create_mental_model task") - logger.info(f"[CREATE_REFLECTION_TASK] Starting for bank_id={bank_id}, reflection_id={reflection_id}") + logger.info(f"[CREATE_MENTAL_MODEL_TASK] Starting for bank_id={bank_id}, mental_model_id={mental_model_id}") from hindsight_api.models import RequestContext @@ -615,55 +615,55 @@ class MemoryEngine(MemoryEngineInterface): }, } - # Update the reflection with the generated content and reflect_response - await self.update_reflection( + # Update the mental model with the generated content and reflect_response + await self.update_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, content=generated_content, reflect_response=reflect_response, request_context=internal_context, ) - logger.info(f"[CREATE_REFLECTION_TASK] Completed for bank_id={bank_id}, reflection_id={reflection_id}") + logger.info(f"[CREATE_MENTAL_MODEL_TASK] Completed for bank_id={bank_id}, mental_model_id={mental_model_id}") - async def _handle_refresh_reflection(self, task_dict: dict[str, Any]): + async def _handle_refresh_mental_model(self, task_dict: dict[str, Any]): """ - Handler for refresh_reflection tasks. + Handler for refresh_mental_model tasks. - Re-runs the source query through reflect and updates the reflection content. + Re-runs the source query through reflect and updates the mental model content. Args: - task_dict: Dict with 'bank_id', 'reflection_id', 'operation_id' + task_dict: Dict with 'bank_id', 'mental_model_id', 'operation_id' Raises: ValueError: If required fields are missing Exception: Any exception from reflect/update (propagates to execute_task for retry) """ bank_id = task_dict.get("bank_id") - reflection_id = task_dict.get("reflection_id") + mental_model_id = task_dict.get("mental_model_id") - if not bank_id or not reflection_id: - raise ValueError("bank_id and reflection_id are required for refresh_reflection task") + if not bank_id or not mental_model_id: + raise ValueError("bank_id and mental_model_id are required for refresh_mental_model task") - logger.info(f"[REFRESH_REFLECTION_TASK] Starting for bank_id={bank_id}, reflection_id={reflection_id}") + logger.info(f"[REFRESH_MENTAL_MODEL_TASK] Starting for bank_id={bank_id}, mental_model_id={mental_model_id}") from hindsight_api.models import RequestContext internal_context = RequestContext() - # Get the current reflection to get source_query - reflection = await self.get_reflection(bank_id, reflection_id, request_context=internal_context) - if not reflection: - raise ValueError(f"Reflection {reflection_id} not found in bank {bank_id}") + # Get the current mental model to get source_query + mental_model = await self.get_mental_model(bank_id, mental_model_id, request_context=internal_context) + if not mental_model: + raise ValueError(f"Mental model {mental_model_id} not found in bank {bank_id}") - source_query = reflection["source_query"] + source_query = mental_model["source_query"] - # Run reflect to generate new content, excluding the reflection being refreshed + # Run reflect to generate new content, excluding the mental model being refreshed reflect_result = await self.reflect_async( bank_id=bank_id, query=source_query, request_context=internal_context, - exclude_reflection_ids=[reflection_id], + exclude_mental_model_ids=[mental_model_id], ) generated_content = reflect_result.text or "No content generated" @@ -684,16 +684,16 @@ class MemoryEngine(MemoryEngineInterface): }, } - # Update the reflection with the generated content and reflect_response - await self.update_reflection( + # Update the mental model with the generated content and reflect_response + await self.update_mental_model( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, content=generated_content, reflect_response=reflect_response, request_context=internal_context, ) - logger.info(f"[REFRESH_REFLECTION_TASK] Completed for bank_id={bank_id}, reflection_id={reflection_id}") + logger.info(f"[REFRESH_MENTAL_MODEL_TASK] Completed for bank_id={bank_id}, mental_model_id={mental_model_id}") async def execute_task(self, task_dict: dict[str, Any]): """ @@ -733,10 +733,10 @@ class MemoryEngine(MemoryEngineInterface): await self._handle_batch_retain(task_dict) elif task_type == "consolidation": await self._handle_consolidation(task_dict) - elif task_type == "create_reflection": - await self._handle_create_reflection(task_dict) - elif task_type == "refresh_reflection": - await self._handle_refresh_reflection(task_dict) + elif task_type == "create_mental_model": + await self._handle_create_mental_model(task_dict) + elif task_type == "refresh_mental_model": + await self._handle_refresh_mental_model(task_dict) else: logger.error(f"Unknown task type: {task_type}") # Don't retry unknown task types @@ -1436,7 +1436,7 @@ class MemoryEngine(MemoryEngineInterface): from ..config import get_config config = get_config() - if config.enable_mental_models: + if config.enable_observations: try: await self.submit_async_consolidation(bank_id=bank_id, request_context=request_context) except Exception as e: @@ -2690,35 +2690,35 @@ class MemoryEngine(MemoryEngineInterface): except Exception as e: raise Exception(f"Failed to delete agent data: {str(e)}") - async def clear_mental_models( + async def clear_observations( self, bank_id: str, *, request_context: "RequestContext", ) -> dict[str, int]: """ - Clear all mental models for a bank. + Clear all observations for a bank (consolidated knowledge). Args: - bank_id: Bank ID to clear mental models for + bank_id: Bank ID to clear observations for request_context: Request context for authentication. Returns: - Dictionary with count of deleted mental models + Dictionary with count of deleted observations """ await self._authenticate_tenant(request_context) pool = await self._get_pool() async with acquire_with_retry(pool) as conn: async with conn.transaction(): - # Count mental models before deletion + # Count observations before deletion count = await conn.fetchval( - f"SELECT COUNT(*) FROM {fq_table('memory_units')} WHERE bank_id = $1 AND fact_type = 'mental_model'", + f"SELECT COUNT(*) FROM {fq_table('memory_units')} WHERE bank_id = $1 AND fact_type = 'observation'", bank_id, ) - # Delete all mental models + # Delete all observations await conn.execute( - f"DELETE FROM {fq_table('memory_units')} WHERE bank_id = $1 AND fact_type = 'mental_model'", + f"DELETE FROM {fq_table('memory_units')} WHERE bank_id = $1 AND fact_type = 'observation'", bank_id, ) @@ -3153,8 +3153,8 @@ class MemoryEngine(MemoryEngineInterface): "tags": row["tags"] if row["tags"] else [], } - # For mental models, include source_memory_ids and fetch source_memories - if row["fact_type"] == "mental_model" and row["source_memory_ids"]: + # For observations, include source_memory_ids and fetch source_memories + if row["fact_type"] == "observation" and row["source_memory_ids"]: source_ids = row["source_memory_ids"] result["source_memory_ids"] = [str(sid) for sid in source_ids] @@ -3485,7 +3485,7 @@ class MemoryEngine(MemoryEngineInterface): request_context: "RequestContext", tags: list[str] | None = None, tags_match: TagsMatch = "any", - exclude_reflection_ids: list[str] | None = None, + exclude_mental_model_ids: list[str] | None = None, ) -> ReflectResult: """ Reflect and formulate an answer using an agentic loop with tools. @@ -3509,8 +3509,8 @@ class MemoryEngine(MemoryEngineInterface): response_schema: Optional JSON Schema for structured output (not yet supported) tags: Optional tags to filter memories tags_match: How to match tags - "any" (OR), "all" (AND) - exclude_reflection_ids: Optional list of reflection IDs to exclude from search - (used when refreshing a reflection to avoid circular reference) + exclude_mental_model_ids: Optional list of mental model IDs to exclude from search + (used when refreshing a mental model to avoid circular reference) Returns: ReflectResult containing: @@ -3569,15 +3569,14 @@ class MemoryEngine(MemoryEngineInterface): pending_consolidation = bank_stats.pending_consolidation if hasattr(bank_stats, "pending_consolidation") else 0 # Create tool callbacks that acquire connections only when needed - from .reflect.tools import tool_search_reflections from .retain import embedding_utils - async def search_reflections_fn(q: str, max_results: int = 5) -> dict[str, Any]: + async def search_mental_models_fn(q: str, max_results: int = 5) -> dict[str, Any]: # Generate embedding for the query embeddings = await embedding_utils.generate_embeddings_batch(self.embeddings, [q]) query_embedding = embeddings[0] async with pool.acquire() as conn: - return await tool_search_reflections( + return await tool_search_mental_models( conn, bank_id, q, @@ -3585,11 +3584,11 @@ class MemoryEngine(MemoryEngineInterface): max_results=max_results, tags=tags, tags_match=tags_match, - exclude_ids=exclude_reflection_ids, + exclude_ids=exclude_mental_model_ids, ) - async def search_mental_models_fn(q: str, max_tokens: int = 5000) -> dict[str, Any]: - return await tool_search_mental_models( + async def search_observations_fn(q: str, max_tokens: int = 5000) -> dict[str, Any]: + return await tool_search_observations( self, bank_id, q, @@ -3638,8 +3637,8 @@ class MemoryEngine(MemoryEngineInterface): bank_id=bank_id, query=query, bank_profile=profile, - search_reflections_fn=search_reflections_fn, search_mental_models_fn=search_mental_models_fn, + search_observations_fn=search_observations_fn, recall_fn=recall_fn, expand_fn=expand_fn, context=context, @@ -3673,7 +3672,7 @@ class MemoryEngine(MemoryEngineInterface): # Extract memories from recall tool outputs - only include memories the agent actually used # agent_result.used_memory_ids contains validated IDs from the done action used_memory_ids_set = set(agent_result.used_memory_ids) if agent_result.used_memory_ids else set() - based_on: dict[str, list[MemoryFact]] = {"world": [], "experience": [], "opinion": []} + based_on: dict[str, list[MemoryFact]] = {"world": [], "experience": [], "opinion": [], "observation": []} seen_memory_ids: set[str] = set() for tc in agent_result.tool_trace: if tc.tool == "recall" and "memories" in tc.output: @@ -3714,13 +3713,14 @@ class MemoryEngine(MemoryEngineInterface): continue # Skip models not actually used by the agent seen_model_ids.add(model_id) # Add to based_on as MemoryFact with type "mental-models" - # Mental models have a "text" field containing the consolidated knowledge + model_name = model.get("name", "") + model_summary = model.get("summary") or model.get("description", "") based_on["mental-models"].append( MemoryFact( id=model_id, - text=model.get("text", ""), + text=f"{model_name}: {model_summary}", fact_type="mental-models", - context=None, + context=f"{model.get('type', 'concept')} ({model.get('subtype', 'structural')})", occurred_start=None, occurred_end=None, ) @@ -3735,38 +3735,39 @@ class MemoryEngine(MemoryEngineInterface): continue # Skip models not actually used by the agent seen_model_ids.add(model_id) # Add to based_on as MemoryFact with type "mental-models" - # Mental models have a "text" field containing the consolidated knowledge + model_name = model.get("name", "") + model_summary = model.get("summary") or model.get("description", "") based_on["mental-models"].append( MemoryFact( id=model_id, - text=model.get("text", ""), + text=f"{model_name}: {model_summary}", fact_type="mental-models", - context=None, + context=f"{model.get('type', 'concept')} ({model.get('subtype', 'structural')})", occurred_start=None, occurred_end=None, ) ) - elif tc.tool == "search_reflections": - # Search reflections - include all returned reflections (filtered by used_reflection_ids_set if specified) - used_reflection_ids_set = ( - set(agent_result.used_reflection_ids) if agent_result.used_reflection_ids else set() + elif tc.tool == "search_mental_models": + # Search mental models - include all returned mental models (filtered by used_mental_model_ids_set if specified) + used_mental_model_ids_set = ( + set(agent_result.used_mental_model_ids) if agent_result.used_mental_model_ids else set() ) - for reflection in tc.output.get("reflections", []): - reflection_id = reflection.get("id") - if reflection_id and reflection_id not in seen_model_ids: - # Only include reflections that the agent declared as used (or all if none specified) - if used_reflection_ids_set and reflection_id not in used_reflection_ids_set: - continue # Skip reflections not actually used by the agent - seen_model_ids.add(reflection_id) - # Add to based_on as MemoryFact with type "mental-models" (reflections are synthesized knowledge) - reflection_name = reflection.get("name", "") - reflection_content = reflection.get("content", "") + for mental_model in tc.output.get("mental_models", []): + mental_model_id = mental_model.get("id") + if mental_model_id and mental_model_id not in seen_model_ids: + # Only include mental models that the agent declared as used (or all if none specified) + if used_mental_model_ids_set and mental_model_id not in used_mental_model_ids_set: + continue # Skip mental models not actually used by the agent + seen_model_ids.add(mental_model_id) + # Add to based_on as MemoryFact with type "mental-models" (mental models are synthesized knowledge) + mental_model_name = mental_model.get("name", "") + mental_model_content = mental_model.get("content", "") based_on["mental-models"].append( MemoryFact( - id=reflection_id, - text=f"{reflection_name}: {reflection_content}", + id=mental_model_id, + text=f"{mental_model_name}: {mental_model_content}", fact_type="mental-models", - context="reflection (user-curated)", + context="mental model (user-curated)", occurred_start=None, occurred_end=None, ) @@ -4309,18 +4310,18 @@ class MemoryEngine(MemoryEngineInterface): fact_ids: list[str], ) -> int: """ - Remove fact IDs from mental model source_memory_ids when memories are deleted. + Remove fact IDs from observation source_memory_ids when memories are deleted. - Mental models are now stored in memory_units with fact_type='mental_model' + Observations are stored in memory_units with fact_type='observation' and have a source_memory_ids column (UUID[]) tracking their source memories. Args: conn: Database connection bank_id: Bank identifier - fact_ids: List of fact IDs to remove from mental models + fact_ids: List of fact IDs to remove from observations Returns: - Number of mental models updated + Number of observations updated """ if not fact_ids: return 0 @@ -4330,7 +4331,7 @@ class MemoryEngine(MemoryEngineInterface): fact_uuids = [uuid_module.UUID(fid) for fid in fact_ids] - # Update mental models (memory_units with fact_type='mental_model') + # Update observations (memory_units with fact_type='observation') # by removing the deleted fact IDs from source_memory_ids # Use array subtraction: source_memory_ids - deleted_ids result = await conn.execute( @@ -4343,7 +4344,7 @@ class MemoryEngine(MemoryEngineInterface): ), updated_at = NOW() WHERE bank_id = $1 - AND fact_type = 'mental_model' + AND fact_type = 'observation' AND source_memory_ids && $2::uuid[] """, bank_id, @@ -4354,7 +4355,7 @@ class MemoryEngine(MemoryEngineInterface): updated_count = int(result.split()[-1]) if result and "UPDATE" in result else 0 if updated_count > 0: logger.info( - f"[MENTAL_MODELS] Invalidated {len(fact_ids)} fact IDs from {updated_count} mental models in bank {bank_id}" + f"[OBSERVATIONS] Invalidated {len(fact_ids)} fact IDs from {updated_count} observations in bank {bank_id}" ) return updated_count @@ -4680,9 +4681,9 @@ class MemoryEngine(MemoryEngineInterface): offset: int = 0, request_context: "RequestContext", ) -> list[dict[str, Any]]: - """List auto-consolidated mental models for a bank. + """List auto-consolidated observations for a bank. - Mental models are stored in memory_units with fact_type='mental_model'. + Observations are stored in memory_units with fact_type='observation'. They are automatically created and updated by the consolidation engine. Args: @@ -4694,7 +4695,7 @@ class MemoryEngine(MemoryEngineInterface): request_context: Request context for authentication Returns: - List of mental model dicts + List of observation dicts """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -4716,33 +4717,33 @@ class MemoryEngine(MemoryEngineInterface): f""" SELECT id, bank_id, text, proof_count, history, tags, source_memory_ids, created_at, updated_at FROM {fq_table("memory_units")} - WHERE bank_id = $1 AND fact_type = 'mental_model' {tag_filter} + WHERE bank_id = $1 AND fact_type = 'observation' {tag_filter} ORDER BY updated_at DESC NULLS LAST LIMIT $2 OFFSET $3 """, *params, ) - return [self._row_to_mental_model_consolidated(row) for row in rows] + return [self._row_to_observation_consolidated(row) for row in rows] - async def get_mental_model_consolidated( + async def get_observation_consolidated( self, bank_id: str, - model_id: str, + observation_id: str, *, include_source_memories: bool = True, request_context: "RequestContext", ) -> dict[str, Any] | None: - """Get a single mental model by ID. + """Get a single observation by ID. Args: bank_id: Bank identifier - model_id: Mental model ID + observation_id: Observation ID include_source_memories: Whether to include full source memory details request_context: Request context for authentication Returns: - Mental model dict or None if not found + Observation dict or None if not found """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -4752,16 +4753,16 @@ class MemoryEngine(MemoryEngineInterface): f""" SELECT id, bank_id, text, proof_count, history, tags, source_memory_ids, created_at, updated_at FROM {fq_table("memory_units")} - WHERE bank_id = $1 AND id = $2 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND id = $2 AND fact_type = 'observation' """, bank_id, - model_id, + observation_id, ) if not row: return None - result = self._row_to_mental_model_consolidated(row) + result = self._row_to_observation_consolidated(row) # Fetch source memories if requested and source_memory_ids exist if include_source_memories and result.get("source_memory_ids"): @@ -4789,8 +4790,8 @@ class MemoryEngine(MemoryEngineInterface): return result - def _row_to_mental_model_consolidated(self, row: Any) -> dict[str, Any]: - """Convert a database row to a mental model dict.""" + def _row_to_observation_consolidated(self, row: Any) -> dict[str, Any]: + """Convert a database row to an observation dict.""" import json history = row["history"] @@ -4817,10 +4818,10 @@ class MemoryEngine(MemoryEngineInterface): } # ========================================================================= - # REFLECTIONS CRUD + # MENTAL MODELS CRUD # ========================================================================= - async def list_reflections( + async def list_mental_models( self, bank_id: str, *, @@ -4830,7 +4831,7 @@ class MemoryEngine(MemoryEngineInterface): offset: int = 0, request_context: "RequestContext", ) -> list[dict[str, Any]]: - """List pinned reflections for a bank. + """List pinned mental models for a bank. Args: bank_id: Bank identifier @@ -4841,7 +4842,7 @@ class MemoryEngine(MemoryEngineInterface): request_context: Request context for authentication Returns: - List of pinned reflection dicts + List of pinned mental model dicts """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -4863,7 +4864,7 @@ class MemoryEngine(MemoryEngineInterface): f""" SELECT id, bank_id, name, source_query, content, tags, last_refreshed_at, created_at, reflect_response - FROM {fq_table("reflections")} + FROM {fq_table("mental_models")} WHERE bank_id = $1 {tag_filter} ORDER BY last_refreshed_at DESC LIMIT $2 OFFSET $3 @@ -4871,24 +4872,24 @@ class MemoryEngine(MemoryEngineInterface): *params, ) - return [self._row_to_reflection(row) for row in rows] + return [self._row_to_mental_model(row) for row in rows] - async def get_reflection( + async def get_mental_model( self, bank_id: str, - reflection_id: str, + mental_model_id: str, *, request_context: "RequestContext", ) -> dict[str, Any] | None: - """Get a single pinned reflection by ID. + """Get a single pinned mental model by ID. Args: bank_id: Bank identifier - reflection_id: Pinned reflection UUID + mental_model_id: Pinned mental model UUID request_context: Request context for authentication Returns: - Pinned reflection dict or None if not found + Pinned mental model dict or None if not found """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -4898,16 +4899,16 @@ class MemoryEngine(MemoryEngineInterface): f""" SELECT id, bank_id, name, source_query, content, tags, last_refreshed_at, created_at, reflect_response - FROM {fq_table("reflections")} + FROM {fq_table("mental_models")} WHERE bank_id = $1 AND id = $2 """, bank_id, - reflection_id, + mental_model_id, ) - return self._row_to_reflection(row) if row else None + return self._row_to_mental_model(row) if row else None - async def create_reflection( + async def create_mental_model( self, bank_id: str, name: str, @@ -4917,18 +4918,18 @@ class MemoryEngine(MemoryEngineInterface): tags: list[str] | None = None, request_context: "RequestContext", ) -> dict[str, Any]: - """Create a new pinned reflection. + """Create a new pinned mental model. Args: bank_id: Bank identifier - name: Human-readable name for the reflection - source_query: The query that generated this reflection + name: Human-readable name for the mental model + source_query: The query that generated this mental model content: The synthesized content tags: Optional tags for scoped visibility request_context: Request context for authentication Returns: - The created pinned reflection dict + The created pinned mental model dict """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -4942,7 +4943,7 @@ class MemoryEngine(MemoryEngineInterface): async with acquire_with_retry(pool) as conn: row = await conn.fetchrow( f""" - INSERT INTO {fq_table("reflections")} + INSERT INTO {fq_table("mental_models")} (bank_id, name, source_query, content, embedding, tags) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, bank_id, name, source_query, content, tags, @@ -4956,45 +4957,45 @@ class MemoryEngine(MemoryEngineInterface): tags or [], ) - logger.info(f"[REFLECTIONS] Created pinned reflection '{name}' for bank {bank_id}") - return self._row_to_reflection(row) + logger.info(f"[MENTAL_MODELS] Created pinned mental model '{name}' for bank {bank_id}") + return self._row_to_mental_model(row) - async def refresh_reflection( + async def refresh_mental_model( self, bank_id: str, - reflection_id: str, + mental_model_id: str, *, request_context: "RequestContext", ) -> dict[str, Any] | None: - """Refresh a pinned reflection by re-running its source query. + """Refresh a pinned mental model by re-running its source query. This method: - 1. Gets the pinned reflection + 1. Gets the pinned mental model 2. Runs the source_query through reflect 3. Updates the content with the new synthesis 4. Updates last_refreshed_at Args: bank_id: Bank identifier - reflection_id: Pinned reflection UUID + mental_model_id: Pinned mental model UUID request_context: Request context for authentication Returns: - Updated pinned reflection dict or None if not found + Updated pinned mental model dict or None if not found """ await self._authenticate_tenant(request_context) - # Get the current reflection - reflection = await self.get_reflection(bank_id, reflection_id, request_context=request_context) - if not reflection: + # Get the current mental model + mental_model = await self.get_mental_model(bank_id, mental_model_id, request_context=request_context) + if not mental_model: return None - # Run reflect with the source query, excluding the reflection being refreshed + # Run reflect with the source query, excluding the mental model being refreshed reflect_result = await self.reflect_async( bank_id=bank_id, - query=reflection["source_query"], + query=mental_model["source_query"], request_context=request_context, - exclude_reflection_ids=[reflection_id], + exclude_mental_model_ids=[mental_model_id], ) # Build reflect_response payload to store @@ -5011,39 +5012,40 @@ class MemoryEngine(MemoryEngineInterface): ] for fact_type, facts in reflect_result.based_on.items() }, + "mental_models": [], # Mental models are included in based_on["mental-models"] } - # Update the reflection with new content and reflect_response - return await self.update_reflection( + # Update the mental model with new content and reflect_response + return await self.update_mental_model( bank_id, - reflection_id, + mental_model_id, content=reflect_result.text, reflect_response=reflect_response_payload, request_context=request_context, ) - async def update_reflection( + async def update_mental_model( self, bank_id: str, - reflection_id: str, + mental_model_id: str, *, name: str | None = None, content: str | None = None, reflect_response: dict[str, Any] | None = None, request_context: "RequestContext", ) -> dict[str, Any] | None: - """Update a pinned reflection. + """Update a pinned mental model. Args: bank_id: Bank identifier - reflection_id: Pinned reflection UUID + mental_model_id: Pinned mental model UUID name: New name (if changing) content: New content (if changing) reflect_response: Full reflect API response payload (if changing) request_context: Request context for authentication Returns: - Updated pinned reflection dict or None if not found + Updated pinned mental model dict or None if not found """ await self._authenticate_tenant(request_context) pool = await self._get_pool() @@ -5051,7 +5053,7 @@ class MemoryEngine(MemoryEngineInterface): async with acquire_with_retry(pool) as conn: # Build dynamic update updates = [] - params: list[Any] = [bank_id, reflection_id] + params: list[Any] = [bank_id, mental_model_id] param_idx = 3 if name is not None: @@ -5081,7 +5083,7 @@ class MemoryEngine(MemoryEngineInterface): return None query = f""" - UPDATE {fq_table("reflections")} + UPDATE {fq_table("mental_models")} SET {", ".join(updates)} WHERE bank_id = $1 AND id = $2 RETURNING id, bank_id, name, source_query, content, tags, @@ -5090,20 +5092,20 @@ class MemoryEngine(MemoryEngineInterface): row = await conn.fetchrow(query, *params) - return self._row_to_reflection(row) if row else None + return self._row_to_mental_model(row) if row else None - async def delete_reflection( + async def delete_mental_model( self, bank_id: str, - reflection_id: str, + mental_model_id: str, *, request_context: "RequestContext", ) -> bool: - """Delete a pinned reflection. + """Delete a pinned mental model. Args: bank_id: Bank identifier - reflection_id: Pinned reflection UUID + mental_model_id: Pinned mental model UUID request_context: Request context for authentication Returns: @@ -5114,15 +5116,15 @@ class MemoryEngine(MemoryEngineInterface): async with acquire_with_retry(pool) as conn: result = await conn.execute( - f"DELETE FROM {fq_table('reflections')} WHERE bank_id = $1 AND id = $2", + f"DELETE FROM {fq_table('mental_models')} WHERE bank_id = $1 AND id = $2", bank_id, - reflection_id, + mental_model_id, ) return result == "DELETE 1" - def _row_to_reflection(self, row) -> dict[str, Any]: - """Convert a database row to a reflection dict.""" + def _row_to_mental_model(self, row) -> dict[str, Any]: + """Convert a database row to a mental model dict.""" reflect_response = row.get("reflect_response") # Parse JSON string to dict if needed (asyncpg may return JSONB as string) if isinstance(reflect_response, str): @@ -5747,7 +5749,7 @@ class MemoryEngine(MemoryEngineInterface): dedupe_by_bank=True, ) - async def submit_async_create_reflection( + async def submit_async_create_mental_model( self, bank_id: str, name: str, @@ -5757,16 +5759,16 @@ class MemoryEngine(MemoryEngineInterface): max_tokens: int = 2048, request_context: "RequestContext", ) -> dict[str, Any]: - """Submit an async reflection creation operation. + """Submit an async mental model creation operation. This: - 1. Creates the reflection in the database immediately (with placeholder content) + 1. Creates the mental model in the database immediately (with placeholder content) 2. Schedules a background task to run reflect and update the content 3. Returns operation_id for tracking Args: bank_id: Bank identifier - name: Human-readable name for the reflection + name: Human-readable name for the mental model source_query: The query to run to generate content tags: Optional tags for scoped visibility max_tokens: Maximum tokens for the reflect response @@ -5777,8 +5779,8 @@ class MemoryEngine(MemoryEngineInterface): """ await self._authenticate_tenant(request_context) - # 1. Create the reflection in the database with placeholder content - reflection = await self.create_reflection( + # 1. Create the mental model in the database with placeholder content + mental_model = await self.create_mental_model( bank_id=bank_id, name=name, source_query=source_query, @@ -5786,36 +5788,36 @@ class MemoryEngine(MemoryEngineInterface): tags=tags, request_context=request_context, ) - reflection_id = reflection["id"] + mental_model_id = mental_model["id"] # 2. Submit async operation return await self._submit_async_operation( bank_id=bank_id, - operation_type="create_reflection", - task_type="create_reflection", + operation_type="create_mental_model", + task_type="create_mental_model", task_payload={ - "reflection_id": reflection_id, + "mental_model_id": mental_model_id, "source_query": source_query, "max_tokens": max_tokens, }, - result_metadata={"reflection_id": reflection_id, "name": name, "source_query": source_query}, + result_metadata={"mental_model_id": mental_model_id, "name": name, "source_query": source_query}, dedupe_by_bank=False, ) - async def submit_async_refresh_reflection( + async def submit_async_refresh_mental_model( self, bank_id: str, - reflection_id: str, + mental_model_id: str, *, request_context: "RequestContext", ) -> dict[str, Any]: - """Submit an async reflection refresh operation. + """Submit an async mental model refresh operation. This schedules a background task to re-run the source query and update the content. Args: bank_id: Bank identifier - reflection_id: Reflection UUID to refresh + mental_model_id: Mental model UUID to refresh request_context: Request context for authentication Returns: @@ -5823,18 +5825,18 @@ class MemoryEngine(MemoryEngineInterface): """ await self._authenticate_tenant(request_context) - # Verify reflection exists - reflection = await self.get_reflection(bank_id, reflection_id, request_context=request_context) - if not reflection: - raise ValueError(f"Reflection {reflection_id} not found in bank {bank_id}") + # Verify mental model exists + mental_model = await self.get_mental_model(bank_id, mental_model_id, request_context=request_context) + if not mental_model: + raise ValueError(f"Mental model {mental_model_id} not found in bank {bank_id}") return await self._submit_async_operation( bank_id=bank_id, - operation_type="refresh_reflection", - task_type="refresh_reflection", + operation_type="refresh_mental_model", + task_type="refresh_mental_model", task_payload={ - "reflection_id": reflection_id, + "mental_model_id": mental_model_id, }, - result_metadata={"reflection_id": reflection_id, "name": reflection["name"]}, + result_metadata={"mental_model_id": mental_model_id, "name": mental_model["name"]}, dedupe_by_bank=False, ) diff --git a/hindsight-api/hindsight_api/engine/reflect/__init__.py b/hindsight-api/hindsight_api/engine/reflect/__init__.py index d266e807..0c1ac1dd 100644 --- a/hindsight-api/hindsight_api/engine/reflect/__init__.py +++ b/hindsight-api/hindsight_api/engine/reflect/__init__.py @@ -4,17 +4,17 @@ Reflect agent module for agentic reflection with tools. The reflect agent uses an iterative loop with tools to: 1. Lookup mental models (existing knowledge) 2. Recall facts (semantic + temporal search) -3. Learn new insights (create/update mental models) +3. Learn new insights (create/update observations) 4. Expand memories (get chunk/document context) """ from .agent import ReflectAgentResult, run_reflect_agent -from .models import MentalModelInput, ReflectAction, ReflectActionBatch +from .models import ObservationInput, ReflectAction, ReflectActionBatch __all__ = [ "run_reflect_agent", "ReflectAgentResult", "ReflectAction", "ReflectActionBatch", - "MentalModelInput", + "ObservationInput", ] diff --git a/hindsight-api/hindsight_api/engine/reflect/agent.py b/hindsight-api/hindsight_api/engine/reflect/agent.py index 603f5fdb..bc385189 100644 --- a/hindsight-api/hindsight_api/engine/reflect/agent.py +++ b/hindsight-api/hindsight_api/engine/reflect/agent.py @@ -2,8 +2,8 @@ Reflect agent - agentic loop for reflection with native tool calling. Uses hierarchical retrieval: -1. search_reflections - User-curated summaries (highest quality) -2. search_mental_models - Consolidated knowledge with freshness +1. search_mental_models - User-curated summaries (highest quality) +2. search_observations - Consolidated knowledge with freshness 3. recall - Raw facts as ground truth """ @@ -202,8 +202,8 @@ async def run_reflect_agent( bank_id: str, query: str, bank_profile: dict[str, Any], - search_reflections_fn: Callable[[str, int], Awaitable[dict[str, Any]]], search_mental_models_fn: Callable[[str, int], Awaitable[dict[str, Any]]], + search_observations_fn: Callable[[str, int], Awaitable[dict[str, Any]]], recall_fn: Callable[[str, int], Awaitable[dict[str, Any]]], expand_fn: Callable[[list[str], str], Awaitable[dict[str, Any]]], context: str | None = None, @@ -216,8 +216,8 @@ async def run_reflect_agent( Execute the reflect agent loop using native tool calling. The agent uses hierarchical retrieval: - 1. search_reflections - User-curated summaries (try first) - 2. search_mental_models - Consolidated knowledge with freshness + 1. search_mental_models - User-curated summaries (try first) + 2. search_observations - Consolidated knowledge with freshness 3. recall - Raw facts as ground truth Args: @@ -225,8 +225,8 @@ async def run_reflect_agent( bank_id: Bank identifier query: Question to answer bank_profile: Bank profile with name and mission - search_reflections_fn: Tool callback for searching reflections (query, max_results) -> result search_mental_models_fn: Tool callback for searching mental models (query, max_results) -> result + search_observations_fn: Tool callback for searching observations (query, max_results) -> result recall_fn: Tool callback for recall (query, max_tokens) -> result expand_fn: Tool callback for expand (memory_ids, depth) -> result context: Optional additional context @@ -270,8 +270,8 @@ async def run_reflect_agent( # Track available IDs for validation (prevents hallucinated citations) available_memory_ids: set[str] = set() - available_reflection_ids: set[str] = set() available_mental_model_ids: set[str] = set() + available_observation_ids: set[str] = set() def _get_llm_trace() -> list[LLMCall]: return [ @@ -394,7 +394,7 @@ async def run_reflect_agent( llm_trace.append({"scope": f"agent_{iteration + 1}_err", "duration_ms": err_duration}) # Guardrail: If no evidence gathered yet, retry has_gathered_evidence = ( - bool(available_memory_ids) or bool(available_reflection_ids) or bool(available_mental_model_ids) + bool(available_memory_ids) or bool(available_mental_model_ids) or bool(available_observation_ids) ) if not has_gathered_evidence and iteration < max_iterations - 1: continue @@ -519,7 +519,7 @@ async def run_reflect_agent( if done_call: # Guardrail: Require evidence before done has_gathered_evidence = ( - bool(available_memory_ids) or bool(available_reflection_ids) or bool(available_mental_model_ids) + bool(available_memory_ids) or bool(available_mental_model_ids) or bool(available_observation_ids) ) if not has_gathered_evidence and iteration < max_iterations - 1: # Add assistant message and fake tool result asking for evidence @@ -536,7 +536,7 @@ async def run_reflect_agent( "name": done_call.name, # Required by Gemini "content": json.dumps( { - "error": "You must search for information first. Use search_reflections(), search_mental_models(), or recall() before providing your final answer." + "error": "You must search for information first. Use search_mental_models(), search_observations(), or recall() before providing your final answer." } ), } @@ -547,8 +547,8 @@ async def run_reflect_agent( return await _process_done_tool( done_call, available_memory_ids, - available_reflection_ids, available_mental_model_ids, + available_observation_ids, iteration + 1, total_tools_called, tool_trace, @@ -576,8 +576,8 @@ async def run_reflect_agent( tool_tasks = [ _execute_tool_with_timing( tc, - search_reflections_fn, search_mental_models_fn, + search_observations_fn, recall_fn, expand_fn, ) @@ -606,15 +606,6 @@ async def run_reflect_agent( ) # Track available IDs from tool results (only for successful responses) - if ( - normalized_tool_name == "search_reflections" - and isinstance(output, dict) - and "reflections" in output - ): - for reflection in output["reflections"]: - if "id" in reflection: - available_reflection_ids.add(reflection["id"]) - if ( normalized_tool_name == "search_mental_models" and isinstance(output, dict) @@ -624,6 +615,15 @@ async def run_reflect_agent( if "id" in mm: available_mental_model_ids.add(mm["id"]) + if ( + normalized_tool_name == "search_observations" + and isinstance(output, dict) + and "observations" in output + ): + for obs in output["observations"]: + if "id" in obs: + available_observation_ids.add(obs["id"]) + if normalized_tool_name == "recall" and isinstance(output, dict) and "memories" in output: for memory in output["memories"]: if "id" in memory: @@ -695,8 +695,8 @@ def _tool_call_to_dict(tc: "LLMToolCall") -> dict[str, Any]: async def _process_done_tool( done_call: "LLMToolCall", available_memory_ids: set[str], - available_reflection_ids: set[str], available_mental_model_ids: set[str], + available_observation_ids: set[str], iterations: int, total_tools_called: int, tool_trace: list[ToolCall], @@ -717,8 +717,8 @@ async def _process_done_tool( # Validate IDs (only include IDs that were actually retrieved) used_memory_ids = [mid for mid in args.get("memory_ids", []) if mid in available_memory_ids] - used_reflection_ids = [rid for rid in args.get("reflection_ids", []) if rid in available_reflection_ids] used_mental_model_ids = [mid for mid in args.get("mental_model_ids", []) if mid in available_mental_model_ids] + used_observation_ids = [oid for oid in args.get("observation_ids", []) if oid in available_observation_ids] # Generate structured output if schema provided structured_output = None @@ -744,16 +744,16 @@ async def _process_done_tool( llm_trace=llm_trace, usage=final_usage, used_memory_ids=used_memory_ids, - used_reflection_ids=used_reflection_ids, used_mental_model_ids=used_mental_model_ids, + used_observation_ids=used_observation_ids, directives_applied=directives_applied, ) async def _execute_tool_with_timing( tc: "LLMToolCall", - search_reflections_fn: Callable[[str, int], Awaitable[dict[str, Any]]], search_mental_models_fn: Callable[[str, int], Awaitable[dict[str, Any]]], + search_observations_fn: Callable[[str, int], Awaitable[dict[str, Any]]], recall_fn: Callable[[str, int], Awaitable[dict[str, Any]]], expand_fn: Callable[[list[str], str], Awaitable[dict[str, Any]]], ) -> tuple[dict[str, Any], int]: @@ -762,8 +762,8 @@ async def _execute_tool_with_timing( result = await _execute_tool( tc.name, tc.arguments, - search_reflections_fn, search_mental_models_fn, + search_observations_fn, recall_fn, expand_fn, ) @@ -774,8 +774,8 @@ async def _execute_tool_with_timing( async def _execute_tool( tool_name: str, args: dict[str, Any], - search_reflections_fn: Callable[[str, int], Awaitable[dict[str, Any]]], search_mental_models_fn: Callable[[str, int], Awaitable[dict[str, Any]]], + search_observations_fn: Callable[[str, int], Awaitable[dict[str, Any]]], recall_fn: Callable[[str, int], Awaitable[dict[str, Any]]], expand_fn: Callable[[list[str], str], Awaitable[dict[str, Any]]], ) -> dict[str, Any]: @@ -783,19 +783,19 @@ async def _execute_tool( # Normalize tool name for various LLM output formats tool_name = _normalize_tool_name(tool_name) - if tool_name == "search_reflections": - query = args.get("query") - if not query: - return {"error": "search_reflections requires a query parameter"} - max_results = args.get("max_results") or 5 - return await search_reflections_fn(query, max_results) - - elif tool_name == "search_mental_models": + if tool_name == "search_mental_models": query = args.get("query") if not query: return {"error": "search_mental_models requires a query parameter"} + max_results = args.get("max_results") or 5 + return await search_mental_models_fn(query, max_results) + + elif tool_name == "search_observations": + query = args.get("query") + if not query: + return {"error": "search_observations requires a query parameter"} max_tokens = max(args.get("max_tokens") or 5000, 1000) # Default 5000, min 1000 - return await search_mental_models_fn(query, max_tokens) + return await search_observations_fn(query, max_tokens) elif tool_name == "recall": query = args.get("query") @@ -817,12 +817,12 @@ async def _execute_tool( def _summarize_input(tool_name: str, args: dict[str, Any]) -> str: """Create a summary of tool input for logging, showing all params.""" - if tool_name == "search_reflections": + if tool_name == "search_mental_models": query = args.get("query", "") query_preview = f"'{query[:30]}...'" if len(query) > 30 else f"'{query}'" max_results = args.get("max_results") or 5 return f"(query={query_preview}, max_results={max_results})" - elif tool_name == "search_mental_models": + elif tool_name == "search_observations": query = args.get("query", "") query_preview = f"'{query[:30]}...'" if len(query) > 30 else f"'{query}'" max_tokens = max(args.get("max_tokens") or 5000, 1000) @@ -841,9 +841,9 @@ def _summarize_input(tool_name: str, args: dict[str, Any]) -> str: answer = args.get("answer", "") answer_preview = f"'{answer[:30]}...'" if len(answer) > 30 else f"'{answer}'" memory_ids = args.get("memory_ids", []) - reflection_ids = args.get("reflection_ids", []) mental_model_ids = args.get("mental_model_ids", []) + observation_ids = args.get("observation_ids", []) return ( - f"(answer={answer_preview}, mem={len(memory_ids)}, ref={len(reflection_ids)}, mm={len(mental_model_ids)})" + f"(answer={answer_preview}, mem={len(memory_ids)}, mm={len(mental_model_ids)}, obs={len(observation_ids)})" ) return str(args) diff --git a/hindsight-api/hindsight_api/engine/reflect/models.py b/hindsight-api/hindsight_api/engine/reflect/models.py index 6c1ad452..af030fc9 100644 --- a/hindsight-api/hindsight_api/engine/reflect/models.py +++ b/hindsight-api/hindsight_api/engine/reflect/models.py @@ -7,22 +7,22 @@ from typing import Any, Literal from pydantic import BaseModel, Field -class MentalModelObservation(BaseModel): - """An observation within a mental model with its supporting memories.""" +class ObservationSection(BaseModel): + """A section within an observation with its supporting memories.""" - title: str = Field(description="Observation header (can be empty for intro)") - text: str = Field(description="Observation content - no headers, use lists/tables/bold") - memory_ids: list[str] = Field(default_factory=list, description="Memory IDs supporting this observation") + title: str = Field(description="Section header (can be empty for intro)") + text: str = Field(description="Section content - no headers, use lists/tables/bold") + memory_ids: list[str] = Field(default_factory=list, description="Memory IDs supporting this section") -class MentalModelInput(BaseModel): - """Input for the learn tool to create a mental model placeholder. +class ObservationInput(BaseModel): + """Input for the learn tool to create an observation placeholder. - The agent only specifies name and description - the actual content/observations + The agent only specifies name and description - the actual content/sections are generated during refresh, similar to pinned models. """ - name: str = Field(description="Human-readable name for the mental model") + name: str = Field(description="Human-readable name for the observation") description: str = Field(description="What to track - used as prompt for content generation during refresh") entity_id: str | None = Field(default=None, description="Optional link to existing entity ID") @@ -39,19 +39,19 @@ class AnswerSection(BaseModel): class ReflectAction(BaseModel): """Single action the reflect agent can take.""" - tool: Literal["list_mental_models", "get_mental_model", "recall", "learn", "expand", "done"] = Field( - description="Tool to invoke: list_mental_models, get_mental_model, recall, learn, expand, or done" + tool: Literal["list_observations", "get_observation", "recall", "learn", "expand", "done"] = Field( + description="Tool to invoke: list_observations, get_observation, recall, learn, expand, or done" ) # Tool-specific parameters - model_id: str | None = Field(default=None, description="Mental model ID for get_mental_model") + observation_id: str | None = Field(default=None, description="Observation ID for get_observation") query: str | None = Field(default=None, description="Search query for recall") max_tokens: int | None = Field(default=None, description="Max tokens for recall results (default 2048)") - mental_model: MentalModelInput | None = Field(default=None, description="Mental model to create/update for learn") + observation: ObservationInput | None = Field(default=None, description="Observation to create/update for learn") memory_ids: list[str] | None = Field(default=None, description="Memory unit IDs for expand (batched)") depth: Literal["chunk", "document"] | None = Field(default=None, description="Expansion depth for expand") sections: list[AnswerSection] | None = Field(default=None, description="DEPRECATED: Use answer field instead") - observations: list[MentalModelObservation] | None = Field( - default=None, description="Observations for done action (when output_mode=observations)" + observation_sections: list[ObservationSection] | None = Field( + default=None, description="Observation sections for done action (when output_mode=observations)" ) # Plain text answer fields (for output_mode=answer) answer: str | None = Field(default=None, description="Plain text answer for done action (no markdown)") @@ -120,12 +120,12 @@ class ReflectAgentResult(BaseModel): default_factory=TokenUsageSummary, description="Total token usage across all LLM calls" ) used_memory_ids: list[str] = Field(default_factory=list, description="Validated memory IDs actually used in answer") - used_reflection_ids: list[str] = Field( - default_factory=list, description="Validated reflection IDs actually used in answer" - ) used_mental_model_ids: list[str] = Field( default_factory=list, description="Validated mental model IDs actually used in answer" ) + used_observation_ids: list[str] = Field( + default_factory=list, description="Validated observation IDs actually used in answer" + ) directives_applied: list[DirectiveInfo] = Field( default_factory=list, description="Directive mental models that affected this reflection" ) diff --git a/hindsight-api/hindsight_api/engine/reflect/prompts.py b/hindsight-api/hindsight_api/engine/reflect/prompts.py index 71099ed4..f423c822 100644 --- a/hindsight-api/hindsight_api/engine/reflect/prompts.py +++ b/hindsight-api/hindsight_api/engine/reflect/prompts.py @@ -2,8 +2,8 @@ System prompts for the reflect agent. The reflect agent uses hierarchical retrieval: -1. search_reflections - User-curated summaries (highest quality) -2. search_mental_models - Consolidated knowledge with freshness awareness +1. search_mental_models - User-curated summaries (highest quality) +2. search_observations - Consolidated knowledge with freshness awareness 3. recall - Raw facts as ground truth fallback """ @@ -125,21 +125,21 @@ def build_system_prompt_for_tools( bank_profile: dict[str, Any], context: str | None = None, directives: list[dict[str, Any]] | None = None, - has_reflections: bool = False, + has_mental_models: bool = False, ) -> str: """ Build the system prompt for tool-calling reflect agent. The agent uses hierarchical retrieval: - 1. search_reflections - User-curated summaries (try first, if available) - 2. search_mental_models - Consolidated knowledge with freshness + 1. search_mental_models - User-curated summaries (try first, if available) + 2. search_observations - Consolidated knowledge with freshness 3. recall - Raw facts as ground truth Args: bank_profile: Bank profile with name and mission context: Optional additional context directives: Optional list of directive mental models to inject as hard rules - has_reflections: Whether the bank has any reflections (skip if not) + has_mental_models: Whether the bank has any mental models (skip if not) """ name = bank_profile.get("name", "Assistant") mission = bank_profile.get("mission", "") @@ -176,25 +176,25 @@ def build_system_prompt_for_tools( ) # Build retrieval levels based on what's available - if has_reflections: + if has_mental_models: parts.extend( [ "You have access to THREE levels of knowledge. Use them in this order:", "", - "### 1. REFLECTIONS (search_reflections) - Try First", + "### 1. MENTAL MODELS (search_mental_models) - Try First", "- User-curated summaries about specific topics", "- HIGHEST quality - manually created and maintained", - "- If a relevant reflection exists and is FRESH, it may fully answer the question", + "- If a relevant mental model exists and is FRESH, it may fully answer the question", "- Check `is_stale` field - if stale, also verify with lower levels", "", - "### 2. MENTAL MODELS (search_mental_models) - Second Priority", + "### 2. OBSERVATIONS (search_observations) - Second Priority", "- Auto-consolidated knowledge from memories", "- Check `is_stale` field - if stale, ALSO use recall() to verify", "- Good for understanding patterns and summaries", "", "### 3. RAW FACTS (recall) - Ground Truth", "- Individual memories (world facts and experiences)", - "- Use when: no reflections/models exist, they're stale, or you need specific details", + "- Use when: no mental models/observations exist, they're stale, or you need specific details", "- This is the source of truth that other levels are built from", "", ] @@ -204,15 +204,15 @@ def build_system_prompt_for_tools( [ "You have access to TWO levels of knowledge. Use them in this order:", "", - "### 1. MENTAL MODELS (search_mental_models) - Try First", + "### 1. OBSERVATIONS (search_observations) - Try First", "- Auto-consolidated knowledge from memories", "- Check `is_stale` field - if stale, ALSO use recall() to verify", "- Good for understanding patterns and summaries", "", "### 2. RAW FACTS (recall) - Ground Truth", "- Individual memories (world facts and experiences)", - "- Use when: no mental models exist, they're stale, or you need specific details", - "- This is the source of truth that mental models are built from", + "- Use when: no observations exist, they're stale, or you need specific details", + "- This is the source of truth that observations are built from", "", ] ) @@ -234,12 +234,12 @@ def build_system_prompt_for_tools( ] ) - if has_reflections: + if has_mental_models: parts.extend( [ - "1. First, try search_reflections() - check if a curated summary exists", - "2. If no reflection or it's stale, try search_mental_models() for consolidated knowledge", - "3. If mental models are stale OR you need specific details, use recall() for raw facts", + "1. First, try search_mental_models() - check if a curated summary exists", + "2. If no mental model or it's stale, try search_observations() for consolidated knowledge", + "3. If observations are stale OR you need specific details, use recall() for raw facts", "4. Use expand() if you need more context on specific memories", "5. When ready, call done() with your answer and supporting IDs", ] @@ -247,8 +247,8 @@ def build_system_prompt_for_tools( else: parts.extend( [ - "1. First, try search_mental_models() - check for consolidated knowledge", - "2. If mental models are stale OR you need specific details, use recall() for raw facts", + "1. First, try search_observations() - check for consolidated knowledge", + "2. If observations are stale OR you need specific details, use recall() for raw facts", "3. Use expand() if you need more context on specific memories", "4. When ready, call done() with your answer and supporting IDs", ] @@ -261,7 +261,7 @@ def build_system_prompt_for_tools( "Call done() with a plain text 'answer' field.", "- Do NOT use markdown formatting", "- NEVER include memory IDs, UUIDs, or 'Memory references' in the answer text", - "- Put IDs ONLY in the memory_ids/reflection_ids/mental_model_ids arrays, not in the answer", + "- Put IDs ONLY in the memory_ids/mental_model_ids/observation_ids arrays, not in the answer", ] ) @@ -356,8 +356,8 @@ def build_agent_prompt( parts.append( "\n## Instructions\n" "Start by searching for relevant information using the hierarchical retrieval strategy:\n" - "1. Try search_reflections() first for curated summaries\n" - "2. Try search_mental_models() for consolidated knowledge\n" + "1. Try search_mental_models() first for curated summaries\n" + "2. Try search_observations() for consolidated knowledge\n" "3. Use recall() for specific details or to verify stale data" ) diff --git a/hindsight-api/hindsight_api/engine/reflect/tools.py b/hindsight-api/hindsight_api/engine/reflect/tools.py index ef74ba24..a5a8a90b 100644 --- a/hindsight-api/hindsight_api/engine/reflect/tools.py +++ b/hindsight-api/hindsight_api/engine/reflect/tools.py @@ -2,8 +2,8 @@ Tool implementations for the reflect agent. Implements hierarchical retrieval: -1. search_reflections - User-curated summaries (highest quality) -2. search_mental_models - Consolidated knowledge with freshness +1. search_mental_models - User-curated stored reflect responses (highest quality) +2. search_observations - Consolidated knowledge with freshness 3. recall - Raw facts as ground truth """ @@ -20,11 +20,11 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) -# Mental model is considered stale if not updated in this many days +# Observation is considered stale if not updated in this many days STALE_THRESHOLD_DAYS = 7 -async def tool_search_reflections( +async def tool_search_mental_models( conn: "Connection", bank_id: str, query: str, @@ -35,9 +35,9 @@ async def tool_search_reflections( exclude_ids: list[str] | None = None, ) -> dict[str, Any]: """ - Search user-curated reflections by semantic similarity. + Search user-curated mental models by semantic similarity. - Reflections are high-quality, manually created summaries about specific topics. + Mental models are high-quality, manually created summaries about specific topics. They should be searched FIRST as they represent the most reliable synthesized knowledge. Args: @@ -45,13 +45,13 @@ async def tool_search_reflections( bank_id: Bank identifier query: Search query (for logging/tracing) query_embedding: Pre-computed embedding for semantic search - max_results: Maximum number of reflections to return - tags: Optional tags to filter reflections + max_results: Maximum number of mental models to return + tags: Optional tags to filter mental models tags_match: How to match tags - "any" (OR), "all" (AND) - exclude_ids: Optional list of reflection IDs to exclude (e.g., when refreshing a reflection) + exclude_ids: Optional list of mental model IDs to exclude (e.g., when refreshing a mental model) Returns: - Dict with matching reflections including content and freshness info + Dict with matching mental models including content and freshness info """ from ..memory_engine import fq_table @@ -73,14 +73,14 @@ async def tool_search_reflections( params.append(exclude_ids) next_param += 1 - # Search reflections by embedding similarity + # Search mental models by embedding similarity rows = await conn.fetch( f""" SELECT id, name, content, reflect_response, tags, created_at, last_refreshed_at, 1 - (embedding <=> $2::vector) as relevance - FROM {fq_table("reflections")} + FROM {fq_table("mental_models")} WHERE bank_id = $1 AND embedding IS NOT NULL {filters} ORDER BY embedding <=> $2::vector LIMIT $3 @@ -89,7 +89,7 @@ async def tool_search_reflections( ) now = datetime.now(timezone.utc) - reflections = [] + mental_models = [] for row in rows: last_refreshed_at = row["last_refreshed_at"] @@ -102,7 +102,7 @@ async def tool_search_reflections( age = now - last_refreshed_at is_stale = age > timedelta(days=STALE_THRESHOLD_DAYS) - reflections.append( + mental_models.append( { "id": str(row["id"]), "name": row["name"], @@ -117,12 +117,12 @@ async def tool_search_reflections( return { "query": query, - "count": len(reflections), - "reflections": reflections, + "count": len(mental_models), + "mental_models": mental_models, } -async def tool_search_mental_models( +async def tool_search_observations( memory_engine: "MemoryEngine", bank_id: str, query: str, @@ -134,9 +134,9 @@ async def tool_search_mental_models( pending_consolidation: int = 0, ) -> dict[str, Any]: """ - Search consolidated mental models using recall with include_mental_models. + Search consolidated observations using recall with include_observations. - Mental models are auto-generated from memories. Returns freshness info + Observations are auto-generated from memories. Returns freshness info so the agent knows if it should also verify with recall(). Args: @@ -145,22 +145,22 @@ async def tool_search_mental_models( query: Search query request_context: Request context for authentication max_tokens: Maximum tokens for results (default 5000) - tags: Optional tags to filter models + tags: Optional tags to filter observations tags_match: How to match tags - "any" (OR), "all" (AND) last_consolidated_at: When consolidation last ran (for staleness check) pending_consolidation: Number of memories waiting to be consolidated Returns: - Dict with matching mental models including freshness info + Dict with matching observations including freshness info """ from ..memory_engine import fq_table - # Use recall to search mental models (they come back in results field when fact_type=["mental_model"]) + # Use recall to search observations (they come back in results field when fact_type=["observation"]) result = await memory_engine.recall_async( bank_id=bank_id, query=query, - fact_type=["mental_model"], # Only retrieve mental models - max_tokens=max_tokens, # Token budget controls how many mental models are returned + fact_type=["observation"], # Only retrieve observations + max_tokens=max_tokens, # Token budget controls how many observations are returned enable_trace=False, request_context=request_context, tags=tags, @@ -169,29 +169,29 @@ async def tool_search_mental_models( _quiet=True, ) - mental_models = [] + observations = [] - # When fact_type=["mental_model"], results come back in `results` field as MemoryFact objects + # When fact_type=["observation"], results come back in `results` field as MemoryFact objects # We need to fetch additional fields (proof_count, source_memory_ids) from the database if result.results: - mm_ids = [m.id for m in result.results] + obs_ids = [m.id for m in result.results] - # Fetch proof_count and source_memory_ids for these mental models + # Fetch proof_count and source_memory_ids for these observations pool = await memory_engine._get_pool() async with pool.acquire() as conn: - mm_rows = await conn.fetch( + obs_rows = await conn.fetch( f""" SELECT id, proof_count, source_memory_ids FROM {fq_table("memory_units")} WHERE id = ANY($1::uuid[]) """, - mm_ids, + obs_ids, ) - mm_data = {str(row["id"]): row for row in mm_rows} + obs_data = {str(row["id"]): row for row in obs_rows} for m in result.results: # Get additional data from DB lookup - extra = mm_data.get(m.id, {}) + extra = obs_data.get(m.id, {}) proof_count = extra.get("proof_count", 1) if extra else 1 source_ids = extra.get("source_memory_ids", []) if extra else [] # Convert UUIDs to strings @@ -204,7 +204,7 @@ async def tool_search_mental_models( is_stale = True staleness_reason = f"{pending_consolidation} memories pending consolidation" - mental_models.append( + observations.append( { "id": str(m.id), "text": m.text, @@ -226,8 +226,8 @@ async def tool_search_mental_models( return { "query": query, - "count": len(mental_models), - "mental_models": mental_models, + "count": len(observations), + "observations": observations, "freshness": freshness, } @@ -247,7 +247,7 @@ async def tool_recall( Search memories using TEMPR retrieval. This is the ground truth - raw facts and experiences. - Use when reflections/mental models don't exist, are stale, or need verification. + Use when mental models/observations don't exist, are stale, or need verification. Args: memory_engine: Memory engine instance @@ -266,7 +266,7 @@ async def tool_recall( result = await memory_engine.recall_async( bank_id=bank_id, query=query, - fact_type=["experience", "world"], # Exclude opinions and mental_models + fact_type=["experience", "world"], # Exclude opinions and observations max_tokens=max_tokens, enable_trace=False, request_context=request_context, diff --git a/hindsight-api/hindsight_api/engine/reflect/tools_schema.py b/hindsight-api/hindsight_api/engine/reflect/tools_schema.py index 4b85e31e..50d6e57c 100644 --- a/hindsight-api/hindsight_api/engine/reflect/tools_schema.py +++ b/hindsight-api/hindsight_api/engine/reflect/tools_schema.py @@ -3,47 +3,21 @@ Tool schema definitions for the reflect agent. These are OpenAI-format tool definitions used with native tool calling. The reflect agent uses a hierarchical retrieval strategy: -1. search_reflections - User-curated summaries (highest quality, if applicable) -2. search_mental_models - Consolidated knowledge with freshness awareness +1. search_mental_models - User-curated stored reflect responses (highest quality, if applicable) +2. search_observations - Consolidated knowledge with freshness awareness 3. recall - Raw facts (world/experience) as ground truth fallback """ # Tool definitions in OpenAI format -TOOL_SEARCH_REFLECTIONS = { - "type": "function", - "function": { - "name": "search_reflections", - "description": ( - "Search user-curated reflections (summaries). These are high-quality, manually created " - "summaries about specific topics. Use FIRST when the question might be covered by an " - "existing reflection. Returns reflections with their content and last refresh time." - ), - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Search query to find relevant reflections", - }, - "max_results": { - "type": "integer", - "description": "Maximum number of reflections to return (default 5)", - }, - }, - "required": ["query"], - }, - }, -} - TOOL_SEARCH_MENTAL_MODELS = { "type": "function", "function": { "name": "search_mental_models", "description": ( - "Search consolidated mental models (auto-generated knowledge). These are automatically " - "synthesized from memories. Returns models with freshness info (updated_at, is_stale). " - "If a model is STALE, you should ALSO use recall() to verify with current facts." + "Search user-curated mental models (stored reflect responses). These are high-quality, manually created " + "summaries about specific topics. Use FIRST when the question might be covered by an " + "existing mental model. Returns mental models with their content and last refresh time." ), "parameters": { "type": "object", @@ -52,6 +26,32 @@ TOOL_SEARCH_MENTAL_MODELS = { "type": "string", "description": "Search query to find relevant mental models", }, + "max_results": { + "type": "integer", + "description": "Maximum number of mental models to return (default 5)", + }, + }, + "required": ["query"], + }, + }, +} + +TOOL_SEARCH_OBSERVATIONS = { + "type": "function", + "function": { + "name": "search_observations", + "description": ( + "Search consolidated observations (auto-generated knowledge). These are automatically " + "synthesized from memories. Returns observations with freshness info (updated_at, is_stale). " + "If an observation is STALE, you should ALSO use recall() to verify with current facts." + ), + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query to find relevant observations", + }, "max_tokens": { "type": "integer", "description": "Maximum tokens for results (default 5000). Use higher values for broader searches.", @@ -130,16 +130,16 @@ TOOL_DONE_ANSWER = { "items": {"type": "string"}, "description": "Array of memory IDs that support your answer (put IDs here, NOT in answer text)", }, - "reflection_ids": { - "type": "array", - "items": {"type": "string"}, - "description": "Array of reflection IDs that support your answer", - }, "mental_model_ids": { "type": "array", "items": {"type": "string"}, "description": "Array of mental model IDs that support your answer", }, + "observation_ids": { + "type": "array", + "items": {"type": "string"}, + "description": "Array of observation IDs that support your answer", + }, }, "required": ["answer"], }, @@ -181,16 +181,16 @@ def _build_done_tool_with_directives(directive_rules: list[str]) -> dict: "items": {"type": "string"}, "description": "Array of memory IDs that support your answer (put IDs here, NOT in answer text)", }, - "reflection_ids": { - "type": "array", - "items": {"type": "string"}, - "description": "Array of reflection IDs that support your answer", - }, "mental_model_ids": { "type": "array", "items": {"type": "string"}, "description": "Array of mental model IDs that support your answer", }, + "observation_ids": { + "type": "array", + "items": {"type": "string"}, + "description": "Array of observation IDs that support your answer", + }, "directive_compliance": { "type": "string", "description": f"REQUIRED: Confirm your answer complies with ALL directives. List each directive and how your answer follows it:\n{rules_list}\n\nFormat: 'Directive 1: [how answer complies]. Directive 2: [how answer complies]...'", @@ -207,8 +207,8 @@ def get_reflect_tools(directive_rules: list[str] | None = None) -> list[dict]: Get the list of tools for the reflect agent. The tools support a hierarchical retrieval strategy: - 1. search_reflections - User-curated summaries (try first) - 2. search_mental_models - Consolidated knowledge with freshness + 1. search_mental_models - User-curated stored reflect responses (try first) + 2. search_observations - Consolidated knowledge with freshness 3. recall - Raw facts as ground truth Args: @@ -219,8 +219,8 @@ def get_reflect_tools(directive_rules: list[str] | None = None) -> list[dict]: List of tool definitions in OpenAI format """ tools = [ - TOOL_SEARCH_REFLECTIONS, TOOL_SEARCH_MENTAL_MODELS, + TOOL_SEARCH_OBSERVATIONS, TOOL_RECALL, TOOL_EXPAND, ] diff --git a/hindsight-api/hindsight_api/engine/response_models.py b/hindsight-api/hindsight_api/engine/response_models.py index 4f24e50d..c2ec9768 100644 --- a/hindsight-api/hindsight_api/engine/response_models.py +++ b/hindsight-api/hindsight_api/engine/response_models.py @@ -10,8 +10,8 @@ from typing import Any from pydantic import BaseModel, ConfigDict, Field -# Valid fact types for recall operations (excludes 'observation' which is internal, and 'opinion' which is deprecated) -VALID_RECALL_FACT_TYPES = frozenset(["world", "experience", "mental_model"]) +# Valid fact types for recall operations (excludes 'opinion' which is deprecated) +VALID_RECALL_FACT_TYPES = frozenset(["world", "experience", "observation"]) class LLMToolCall(BaseModel): @@ -49,13 +49,13 @@ class LLMCallTrace(BaseModel): duration_ms: int = Field(description="Execution time in milliseconds") -class MentalModelRef(BaseModel): - """Reference to a mental model accessed during reflect.""" +class ObservationRef(BaseModel): + """Reference to an observation accessed during reflect.""" - id: str = Field(description="Mental model ID") - name: str = Field(description="Mental model name") - type: str = Field(description="Mental model type: entity, concept, event") - subtype: str = Field(description="Mental model subtype: structural, emergent, learned") + id: str = Field(description="Observation ID") + name: str = Field(description="Observation name") + type: str = Field(description="Observation type: entity, concept, event") + subtype: str = Field(description="Observation subtype: structural, emergent, learned") description: str = Field(description="Brief description") summary: str | None = Field(default=None, description="Full summary (when looked up in detail)") @@ -168,23 +168,23 @@ class ChunkInfo(BaseModel): truncated: bool = Field(default=False, description="Whether the chunk was truncated due to token limits") -class MentalModelResult(BaseModel): - """A mental model result from recall.""" +class ObservationResult(BaseModel): + """An observation result from recall (consolidated knowledge synthesized from facts).""" - id: str = Field(description="Unique mental model ID") - text: str = Field(description="The mental model text") - proof_count: int = Field(description="Number of facts supporting this mental model") + id: str = Field(description="Unique observation ID") + text: str = Field(description="The observation text") + proof_count: int = Field(description="Number of facts supporting this observation") relevance: float = Field(default=0.0, description="Relevance score to the query") tags: list[str] | None = Field(default=None, description="Tags for visibility scoping") source_memory_ids: list[str] = Field( - default_factory=list, description="IDs of facts that contribute to this mental model" + default_factory=list, description="IDs of facts that contribute to this observation" ) -class ReflectionResult(BaseModel): - """A reflection result from recall.""" +class MentalModelResult(BaseModel): + """A mental model result from recall (stored reflect response).""" - id: str = Field(description="Unique reflection ID") + id: str = Field(description="Unique mental model ID") name: str = Field(description="Human-readable name") content: str = Field(description="The synthesized content") relevance: float = Field(default=0.0, description="Relevance score to the query") diff --git a/hindsight-api/hindsight_api/main.py b/hindsight-api/hindsight_api/main.py index f9a07ae9..cf121d2a 100644 --- a/hindsight-api/hindsight_api/main.py +++ b/hindsight-api/hindsight_api/main.py @@ -216,7 +216,7 @@ def main(): retain_extract_causal_links=config.retain_extract_causal_links, retain_extraction_mode=config.retain_extraction_mode, retain_observations_async=config.retain_observations_async, - enable_mental_models=config.enable_mental_models, + enable_observations=config.enable_observations, consolidation_similarity_threshold=config.consolidation_similarity_threshold, consolidation_batch_size=config.consolidation_batch_size, skip_llm_verification=config.skip_llm_verification, diff --git a/hindsight-api/tests/test_consolidation.py b/hindsight-api/tests/test_consolidation.py index 0535b0d4..b7cbf846 100644 --- a/hindsight-api/tests/test_consolidation.py +++ b/hindsight-api/tests/test_consolidation.py @@ -14,35 +14,35 @@ from hindsight_api.engine.memory_engine import MemoryEngine from hindsight_api.engine.reflect.tools import ( tool_recall, tool_search_mental_models, - tool_search_reflections, + tool_search_observations, ) @pytest.fixture(autouse=True) -def enable_mental_models(): - """Enable mental models for all tests in this module.""" +def enable_observations(): + """Enable observations for all tests in this module.""" from hindsight_api.config import get_config config = get_config() - original_value = config.enable_mental_models - config.enable_mental_models = True + original_value = config.enable_observations + config.enable_observations = True yield - config.enable_mental_models = original_value + config.enable_observations = original_value class TestConsolidationIntegration: """Integration tests for consolidation with real database. - These tests verify that consolidation creates mental models correctly. + These tests verify that consolidation creates observations correctly. Since we use SyncTaskBackend in tests, consolidation runs synchronously after retain completes. """ @pytest.mark.asyncio - async def test_consolidation_creates_mental_model_after_retain( + async def test_consolidation_creates_observation_after_retain( self, memory: MemoryEngine, request_context ): - """Test that consolidation creates a mental model after retain.""" + """Test that consolidation creates an observation after retain.""" bank_id = f"test-consolidation-{uuid.uuid4().hex[:8]}" # Create the bank @@ -55,23 +55,23 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Verify mental model exists in memory_units + # Verify observation exists in memory_units # (consolidation already ran as part of retain via SyncTaskBackend) async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, proof_count, fact_type FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - # Mental model may or may not be created depending on LLM relevance judgment + # Observation may or may not be created depending on LLM relevance judgment # The important thing is no errors occurred - if mental_models: - mm = mental_models[0] - assert mm["proof_count"] >= 1 - assert mm["fact_type"] == "mental_model" + if observations: + obs = observations[0] + assert obs["proof_count"] >= 1 + assert obs["fact_type"] == "observation" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @@ -100,25 +100,25 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental models after both retains + # Check observations after both retains async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, proof_count FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY proof_count DESC """, bank_id, ) - # Should have at least one mental model - # If the LLM determined both memories support the same model, + # Should have at least one observation + # If the LLM determined both memories support the same observation, # proof_count might be > 1 - if mental_models: + if observations: # Verify structure is correct - assert all(mm["text"] for mm in mental_models) - assert all(mm["proof_count"] >= 1 for mm in mental_models) + assert all(obs["text"] for obs in observations) + assert all(obs["proof_count"] >= 1 for obs in observations) # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @@ -194,7 +194,7 @@ class TestConsolidationIntegration: @pytest.mark.asyncio async def test_consolidation_copies_entity_links(self, memory: MemoryEngine, request_context): - """Test that mental models inherit entity links from source memories.""" + """Test that observations inherit entity links from source memories.""" bank_id = f"test-consolidation-entities-{uuid.uuid4().hex[:8]}" # Create the bank @@ -207,19 +207,19 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental model and its entity links + # Check observation and its entity links async with memory._pool.acquire() as conn: - mental_model = await conn.fetchrow( + observation = await conn.fetchrow( """ SELECT id FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' LIMIT 1 """, bank_id, ) - if mental_model: + if observation: # Check if entity links were copied entity_links = await conn.fetch( """ @@ -227,9 +227,9 @@ class TestConsolidationIntegration: FROM unit_entities WHERE unit_id = $1 """, - mental_model["id"], + observation["id"], ) - # Mental model should have inherited entity links from source memory + # Observation should have inherited entity links from source memory # (may be empty if no entities were extracted, which is fine) assert entity_links is not None @@ -237,10 +237,10 @@ class TestConsolidationIntegration: await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_consolidation_mental_models_included_in_recall( + async def test_consolidation_observations_included_in_recall( self, memory: MemoryEngine, request_context ): - """Test that mental models created by consolidation are returned in recall.""" + """Test that observations created by consolidation are returned in recall.""" bank_id = f"test-consolidation-recall-{uuid.uuid4().hex[:8]}" # Create the bank @@ -253,15 +253,15 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Recall with mental models included + # Recall with observations included recall_result = await memory.recall_async( bank_id=bank_id, query="What does Sarah do?", - fact_type=["world", "experience", "mental_model"], + fact_type=["world", "experience", "observation"], request_context=request_context, ) - # Mental models come back as regular results with fact_type='mental_model' + # Observations come back as regular results with fact_type='observation' assert hasattr(recall_result, "results") # Cleanup @@ -269,7 +269,7 @@ class TestConsolidationIntegration: @pytest.mark.asyncio async def test_consolidation_creates_memory_links(self, memory: MemoryEngine, request_context): - """Test that mental models get bidirectional links to their source memories.""" + """Test that observations get bidirectional links to their source memories.""" bank_id = f"test-consolidation-links-{uuid.uuid4().hex[:8]}" # Create the bank @@ -282,20 +282,20 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check memory_links between mental model and source memory + # Check memory_links between observation and source memory async with memory._pool.acquire() as conn: - mental_model = await conn.fetchrow( + observation = await conn.fetchrow( """ SELECT id, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' LIMIT 1 """, bank_id, ) - if mental_model and mental_model["source_memory_ids"]: - source_memory_id = mental_model["source_memory_ids"][0] + if observation and observation["source_memory_ids"]: + source_memory_id = observation["source_memory_ids"][0] # Check that bidirectional links exist link_from_memory = await conn.fetchrow( @@ -304,20 +304,20 @@ class TestConsolidationIntegration: WHERE from_unit_id = $1 AND to_unit_id = $2 """, source_memory_id, - mental_model["id"], + observation["id"], ) link_to_memory = await conn.fetchrow( """ SELECT * FROM memory_links WHERE from_unit_id = $1 AND to_unit_id = $2 """, - mental_model["id"], + observation["id"], source_memory_id, ) # Both directions should have links - assert link_from_memory is not None, "Expected link from source memory to mental model" - assert link_to_memory is not None, "Expected link from mental model to source memory" + assert link_from_memory is not None, "Expected link from source memory to observation" + assert link_to_memory is not None, "Expected link from observation to source memory" assert link_from_memory["link_type"] == "semantic" assert link_to_memory["link_type"] == "semantic" @@ -330,7 +330,7 @@ class TestConsolidationIntegration: ): """Test that consolidation only merges truly redundant facts. - Mental models should be fine-grained (almost 1:1 with memories). + Observations should be fine-grained (almost 1:1 with memories). Only merge when facts are truly redundant (saying the same thing differently) or when one directly updates another (e.g., location change). @@ -338,7 +338,7 @@ class TestConsolidationIntegration: - "Nicolò lives in Italy" - "Nicolò moved to the US recently" (updates the living location) - The second fact should UPDATE the first, not create a separate model. + The second fact should UPDATE the first, not create a separate observation. But unrelated facts like "Nicolò works at Vectorize" should stay separate. """ bank_id = f"test-consolidation-merge-{uuid.uuid4().hex[:8]}" @@ -360,12 +360,12 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental models - should have 2 separate models + # Check observations - should have 2 separate observations async with memory._pool.acquire() as conn: - mm_before = await conn.fetch( + obs_before = await conn.fetch( """ SELECT id, text FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) @@ -377,13 +377,13 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental models after consolidation + # Check observations after consolidation async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, proof_count, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY created_at """, bank_id, @@ -391,13 +391,13 @@ class TestConsolidationIntegration: # Key assertions: # 1. Consolidation ran without errors - # 2. Mental models exist - assert len(mental_models) >= 1, "Expected at least one mental model" + # 2. Observations exist + assert len(observations) >= 1, "Expected at least one observation" # The work-related fact should remain separate from location facts # (LLM behavior varies, so we check structure rather than exact count) - for mm in mental_models: - assert mm["text"], "Mental model should have text" + for obs in observations: + assert obs["text"], "Observation should have text" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @@ -408,7 +408,7 @@ class TestConsolidationIntegration: ): """Test that consolidation NEVER merges facts about different people. - Each person's facts should stay in separate mental models. + Each person's facts should stay in separate observations. """ bank_id = f"test-consolidation-people-{uuid.uuid4().hex[:8]}" @@ -432,33 +432,33 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental models - should have separate models for each person + # Check observations - should have separate observations for each person async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - # Should have multiple mental models (one per person/fact) + # Should have multiple observations (one per person/fact) # Not everything merged into one - assert len(mental_models) >= 2, ( - f"Expected multiple mental models for different people, got {len(mental_models)}" + assert len(observations) >= 2, ( + f"Expected multiple observations for different people, got {len(observations)}" ) - # No single mental model should mention multiple different people - # (This is a structural check - each model should be focused) - for mm in mental_models: - text = mm["text"].lower() + # No single observation should mention multiple different people + # (This is a structural check - each observation should be focused) + for obs in observations: + text = obs["text"].lower() people_mentioned = sum([ 1 for name in ["john", "mary", "bob"] if name in text ]) assert people_mentioned <= 1, ( - f"Mental model should not merge different people: {mm['text']}" + f"Observation should not merge different people: {obs['text']}" ) # Cleanup @@ -471,7 +471,7 @@ class TestConsolidationIntegration: """Test that contradictions about the same topic are merged with history. When facts contradict each other (same person, same topic, opposite info), - they should be merged into ONE mental model that captures the change. + they should be merged into ONE observation that captures the change. Example: - "Nicolò loves pizza" @@ -490,16 +490,16 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check we have one mental model + # Check we have one observation async with memory._pool.acquire() as conn: - mm_before = await conn.fetch( + obs_before = await conn.fetch( """ SELECT id, text FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - count_before = len(mm_before) + count_before = len(obs_before) # Add contradicting fact (same person, same topic, opposite sentiment) await memory.retain_async( @@ -508,36 +508,36 @@ class TestConsolidationIntegration: request_context=request_context, ) - # Check mental models after consolidation + # Check observations after consolidation async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, source_memory_ids, history FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - # Key assertion: Should NOT have more mental models than before - # The contradiction should be merged, not create a new model - assert len(mental_models) <= count_before, ( - f"Contradiction should merge, not create new model. " - f"Before: {count_before}, After: {len(mental_models)}. " - f"Models: {[mm['text'] for mm in mental_models]}" + # Key assertion: Should NOT have more observations than before + # The contradiction should be merged, not create a new observation + assert len(observations) <= count_before, ( + f"Contradiction should merge, not create new observation. " + f"Before: {count_before}, After: {len(observations)}. " + f"Observations: {[obs['text'] for obs in observations]}" ) - # The merged model should capture both sentiments or the change - if mental_models: - merged_text = mental_models[0]["text"].lower() + # The merged observation should capture both sentiments or the change + if observations: + merged_text = observations[0]["text"].lower() # Should mention the change or both states has_history = ( ("used to" in merged_text or "now" in merged_text or "but" in merged_text) or ("love" in merged_text and "hate" in merged_text) - or (len(mental_models[0]["source_memory_ids"] or []) > 1) + or (len(observations[0]["source_memory_ids"] or []) > 1) ) assert has_history, ( - f"Merged model should capture the change. Got: {mental_models[0]['text']}" + f"Merged observation should capture the change. Got: {observations[0]['text']}" ) # Cleanup @@ -551,7 +551,7 @@ class TestConsolidationDisabled: async def test_consolidation_returns_disabled_status( self, memory: MemoryEngine, request_context ): - """Test that consolidation returns disabled status when enable_mental_models is False.""" + """Test that consolidation returns disabled status when enable_observations is False.""" from unittest.mock import patch bank_id = f"test-consolidation-disabled-{uuid.uuid4().hex[:8]}" @@ -559,9 +559,9 @@ class TestConsolidationDisabled: # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Disable mental models via config + # Disable observations via config with patch("hindsight_api.config.get_config") as mock_config: - mock_config.return_value.enable_mental_models = False + mock_config.return_value.enable_observations = False result = await run_consolidation_job( memory_engine=memory, @@ -576,20 +576,20 @@ class TestConsolidationDisabled: await memory.delete_bank(bank_id, request_context=request_context) -class TestRecallMentalModelFactType: - """Test recall with mental_model as a fact type.""" +class TestRecallObservationFactType: + """Test recall with observation as a fact type.""" @pytest.mark.asyncio - async def test_recall_with_mental_model_fact_type( + async def test_recall_with_observation_fact_type( self, memory: MemoryEngine, request_context ): - """Test that mental_model can be used as a fact type in recall. + """Test that observation can be used as a fact type in recall. - When mental_model is in the types list, the recall should: - 1. Return mental models in the results field with fact_type='mental_model' + When observation is in the types list, the recall should: + 1. Return observations in the results field with fact_type='observation' 2. Not raise validation errors for None context fields """ - bank_id = f"test-recall-mm-type-{uuid.uuid4().hex[:8]}" + bank_id = f"test-recall-obs-type-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) @@ -601,32 +601,32 @@ class TestRecallMentalModelFactType: request_context=request_context, ) - # Recall with mental_model in types + # Recall with observation in types recall_result = await memory.recall_async( bank_id=bank_id, query="What does Alex do?", - fact_type=["mental_model"], + fact_type=["observation"], request_context=request_context, ) - # Mental models come back as regular results with fact_type='mental_model' + # Observations come back as regular results with fact_type='observation' assert recall_result is not None assert recall_result.results is not None - # Check that results include mental models + # Check that results include observations if recall_result.results: - for mm in recall_result.results: - assert mm.id is not None - assert mm.text is not None - assert mm.fact_type == "mental_model" + for obs in recall_result.results: + assert obs.id is not None + assert obs.text is not None + assert obs.fact_type == "observation" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_recall_with_mixed_fact_types_including_mental_model( + async def test_recall_with_mixed_fact_types_including_observation( self, memory: MemoryEngine, request_context ): - """Test recall with mental_model alongside world and experience types.""" + """Test recall with observation alongside world and experience types.""" bank_id = f"test-recall-mixed-types-{uuid.uuid4().hex[:8]}" # Create the bank @@ -639,11 +639,11 @@ class TestRecallMentalModelFactType: request_context=request_context, ) - # Recall with all types including mental_model + # Recall with all types including observation recall_result = await memory.recall_async( bank_id=bank_id, query="What does Jordan do?", - fact_type=["world", "experience", "mental_model"], + fact_type=["world", "experience", "observation"], enable_trace=True, request_context=request_context, ) @@ -652,38 +652,38 @@ class TestRecallMentalModelFactType: assert recall_result is not None # Should have results from world/experience facts assert recall_result.results is not None - # Mental models come back as regular results with fact_type='mental_model' - # when mental_model is included in fact_type parameter + # Observations come back as regular results with fact_type='observation' + # when observation is included in fact_type parameter # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_recall_mental_model_only_with_trace( + async def test_recall_observation_only_with_trace( self, memory: MemoryEngine, request_context ): - """Test that recall with only mental_model type and trace enabled works. + """Test that recall with only observation type and trace enabled works. - This specifically tests the tracer handling of mental models with None context. + This specifically tests the tracer handling of observations with None context. """ - bank_id = f"test-recall-mm-trace-{uuid.uuid4().hex[:8]}" + bank_id = f"test-recall-obs-trace-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Retain memory - consolidation creates mental model + # Retain memory - consolidation creates observation await memory.retain_async( bank_id=bank_id, content="Chris works as a product manager at a startup focused on AI applications.", request_context=request_context, ) - # Recall with mental_model only and trace enabled + # Recall with observation only and trace enabled # This tests the fix for the None context validation error recall_result = await memory.recall_async( bank_id=bank_id, query="Where does Chris work?", - fact_type=["mental_model"], + fact_type=["observation"], enable_trace=True, request_context=request_context, ) @@ -691,7 +691,7 @@ class TestRecallMentalModelFactType: # Should complete without validation errors assert recall_result is not None # Trace should be populated - assert recall_result.trace is not None or recall_result.mental_models is not None + assert recall_result.trace is not None or recall_result.observations is not None # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @@ -701,8 +701,8 @@ class TestConsolidationTagRouting: """Test tag routing during consolidation. Tag routing rules: - - Same scope (tags match): update existing mental model - - Fact scoped, model global (untagged): update global (it absorbs all) + - Same scope (tags match): update existing observation + - Fact scoped, observation global (untagged): update global (it absorbs all) - Different scopes (non-overlapping tags): create untagged cross-scope insight - No match: create with fact's tags """ @@ -724,17 +724,17 @@ class TestConsolidationTagRouting: ) @pytest.mark.asyncio - async def test_same_scope_updates_model( + async def test_same_scope_updates_observation( self, memory: MemoryEngine, request_context ): - """Test that a tagged fact updates a mental model with the same tags. + """Test that a tagged fact updates an observation with the same tags. Given: - Memory with tags=['alice']: "Alice likes coffee" - New memory with tags=['alice']: "Alice prefers espresso" Expected: - - Mental model with tags=['alice'] is updated to reflect both facts + - Observation with tags=['alice'] is updated to reflect both facts """ bank_id = f"test-tag-same-scope-{uuid.uuid4().hex[:8]}" @@ -746,19 +746,19 @@ class TestConsolidationTagRouting: memory, bank_id, "Alice likes coffee.", ["alice"], request_context ) - # Check mental model has correct tags + # Check observation has correct tags async with memory._pool.acquire() as conn: - mm_before = await conn.fetch( + obs_before = await conn.fetch( """ SELECT id, text, tags FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - count_before = len(mm_before) - if mm_before: - assert "alice" in (mm_before[0]["tags"] or []), ( - f"Expected mental model to have 'alice' tag, got: {mm_before[0]['tags']}" + count_before = len(obs_before) + if obs_before: + assert "alice" in (obs_before[0]["tags"] or []), ( + f"Expected observation to have 'alice' tag, got: {obs_before[0]['tags']}" ) # Retain related memory with same tags @@ -766,44 +766,44 @@ class TestConsolidationTagRouting: memory, bank_id, "Alice prefers espresso over regular coffee.", ["alice"], request_context ) - # Check mental models - should NOT have increased (same scope update) + # Check observations - should NOT have increased (same scope update) async with memory._pool.acquire() as conn: - mm_after = await conn.fetch( + obs_after = await conn.fetch( """ SELECT id, text, tags, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - # Count of mental models should stay same or decrease (merge) - assert len(mm_after) <= count_before + 1, ( - f"Same scope fact should update existing model, not create new. " - f"Before: {count_before}, After: {len(mm_after)}" + # Count of observations should stay same or decrease (merge) + assert len(obs_after) <= count_before + 1, ( + f"Same scope fact should update existing observation, not create new. " + f"Before: {count_before}, After: {len(obs_after)}" ) - # The model(s) should still have alice tag - for mm in mm_after: - if "coffee" in mm["text"].lower() or "espresso" in mm["text"].lower(): - assert "alice" in (mm["tags"] or []), ( - f"Updated model should keep 'alice' tag: {mm['text']}" + # The observation(s) should still have alice tag + for obs in obs_after: + if "coffee" in obs["text"].lower() or "espresso" in obs["text"].lower(): + assert "alice" in (obs["tags"] or []), ( + f"Updated observation should keep 'alice' tag: {obs['text']}" ) # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_scoped_fact_updates_global_model( + async def test_scoped_fact_updates_global_observation( self, memory: MemoryEngine, request_context ): - """Test that a scoped fact can update an untagged (global) mental model. + """Test that a scoped fact can update an untagged (global) observation. Given: - Untagged memory: "Pizza is a popular food" - New memory with tags=['history']: "Pizza originated in Naples" Expected: - - The global mental model is updated (global absorbs all scopes) + - The global observation is updated (global absorbs all scopes) """ bank_id = f"test-tag-global-absorb-{uuid.uuid4().hex[:8]}" @@ -817,20 +817,20 @@ class TestConsolidationTagRouting: request_context=request_context, ) - # Check untagged mental model exists + # Check untagged observation exists async with memory._pool.acquire() as conn: - mm_before = await conn.fetch( + obs_before = await conn.fetch( """ SELECT id, text, tags FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - count_before = len(mm_before) + count_before = len(obs_before) # Should be untagged or have empty tags - if mm_before: - assert not mm_before[0]["tags"] or len(mm_before[0]["tags"]) == 0, ( - f"Expected untagged model, got: {mm_before[0]['tags']}" + if obs_before: + assert not obs_before[0]["tags"] or len(obs_before[0]["tags"]) == 0, ( + f"Expected untagged observation, got: {obs_before[0]['tags']}" ) # Retain scoped memory that relates to the global topic @@ -838,28 +838,28 @@ class TestConsolidationTagRouting: memory, bank_id, "Pizza originated in Naples.", ["history"], request_context ) - # Check - global model should be updated OR new scoped model created + # Check - global observation should be updated OR new scoped observation created async with memory._pool.acquire() as conn: - mm_after = await conn.fetch( + obs_after = await conn.fetch( """ SELECT id, text, tags, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY created_at """, bank_id, ) - # At least one model should exist - assert len(mm_after) >= 1, "Expected at least one mental model" + # At least one observation should exist + assert len(obs_after) >= 1, "Expected at least one observation" - # Check that global model was updated (source_memory_ids increased) - # OR new model was created with appropriate tags - global_models = [m for m in mm_after if not m["tags"] or len(m["tags"]) == 0] - scoped_models = [m for m in mm_after if m["tags"] and len(m["tags"]) > 0] + # Check that global observation was updated (source_memory_ids increased) + # OR new observation was created with appropriate tags + global_observations = [o for o in obs_after if not o["tags"] or len(o["tags"]) == 0] + scoped_observations = [o for o in obs_after if o["tags"] and len(o["tags"]) > 0] # Either global was updated or scoped was created - assert len(global_models) >= 1 or len(scoped_models) >= 1, ( - "Expected either global model update or scoped model creation" + assert len(global_observations) >= 1 or len(scoped_observations) >= 1, ( + "Expected either global observation update or scoped observation creation" ) # Cleanup @@ -876,7 +876,7 @@ class TestConsolidationTagRouting: - Memory with tags=['bob']: "Bob tried the Thai restaurant Alice mentioned" Expected: - - A new untagged mental model capturing the cross-scope insight + - A new untagged observation capturing the cross-scope insight """ bank_id = f"test-tag-cross-scope-{uuid.uuid4().hex[:8]}" @@ -890,16 +890,16 @@ class TestConsolidationTagRouting: ["alice"], request_context ) - # Check Alice's mental model exists with correct tags + # Check Alice's observation exists with correct tags async with memory._pool.acquire() as conn: - mm_alice = await conn.fetch( + obs_alice = await conn.fetch( """ SELECT id, text, tags FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - count_before = len(mm_alice) + count_before = len(obs_alice) # Retain Bob's memory that relates to Alice's topic (cross-scope) await self._retain_with_tags( @@ -908,32 +908,32 @@ class TestConsolidationTagRouting: ["bob"], request_context ) - # Check mental models + # Check observations async with memory._pool.acquire() as conn: - mm_after = await conn.fetch( + obs_after = await conn.fetch( """ SELECT id, text, tags, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY created_at """, bank_id, ) - # Should have multiple models (alice's, bob's, potentially global) - assert len(mm_after) >= 2, ( - f"Expected at least 2 mental models for different scopes, got {len(mm_after)}" + # Should have multiple observations (alice's, bob's, potentially global) + assert len(obs_after) >= 2, ( + f"Expected at least 2 observations for different scopes, got {len(obs_after)}" ) - # Check we have models with different tags (alice, bob, or untagged) - tag_sets = [frozenset(m["tags"] or []) for m in mm_after] + # Check we have observations with different tags (alice, bob, or untagged) + tag_sets = [frozenset(o["tags"] or []) for o in obs_after] - # Should NOT merge alice and bob into same model - models_with_both = [ - m for m in mm_after - if m["tags"] and "alice" in m["tags"] and "bob" in m["tags"] + # Should NOT merge alice and bob into same observation + observations_with_both = [ + o for o in obs_after + if o["tags"] and "alice" in o["tags"] and "bob" in o["tags"] ] - assert len(models_with_both) == 0, ( - "Should not merge different scopes into one model with both tags" + assert len(observations_with_both) == 0, ( + "Should not merge different scopes into one observation with both tags" ) # Cleanup @@ -943,62 +943,62 @@ class TestConsolidationTagRouting: async def test_no_match_creates_with_fact_tags( self, memory: MemoryEngine, request_context ): - """Test that a new fact with no matching models creates a model with fact's tags. + """Test that a new fact with no matching observations creates an observation with fact's tags. Given: - Empty bank - Memory with tags=['project_x']: "Project X uses Python" Expected: - - Mental model created with tags=['project_x'] + - Observation created with tags=['project_x'] """ bank_id = f"test-tag-new-scoped-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Retain tagged memory (no existing mental models) + # Retain tagged memory (no existing observations) await self._retain_with_tags( memory, bank_id, "Project X uses Python for its backend services.", ["project_x"], request_context ) - # Check mental model was created with correct tags + # Check observation was created with correct tags async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, tags FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - assert len(mental_models) >= 1, "Expected mental model to be created" + assert len(observations) >= 1, "Expected observation to be created" - # The model should have the fact's tags - mm = mental_models[0] - assert mm["tags"] is not None, "Mental model should have tags" - assert "project_x" in mm["tags"], ( - f"Mental model should have 'project_x' tag, got: {mm['tags']}" + # The observation should have the fact's tags + obs = observations[0] + assert obs["tags"] is not None, "Observation should have tags" + assert "project_x" in obs["tags"], ( + f"Observation should have 'project_x' tag, got: {obs['tags']}" ) # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_untagged_fact_can_update_scoped_model( + async def test_untagged_fact_can_update_scoped_observation( self, memory: MemoryEngine, request_context ): - """Test that an untagged fact can update a scoped mental model. + """Test that an untagged fact can update a scoped observation. Given: - Memory with tags=['alice']: "Alice works on machine learning" - Untagged memory: "Machine learning involves neural networks" Expected: - - The scoped model may be updated with the global insight - - OR a global model is created + - The scoped observation may be updated with the global insight + - OR a global observation is created """ bank_id = f"test-tag-untagged-update-{uuid.uuid4().hex[:8]}" @@ -1019,24 +1019,24 @@ class TestConsolidationTagRouting: request_context=request_context, ) - # Check mental models + # Check observations async with memory._pool.acquire() as conn: - mental_models = await conn.fetch( + observations = await conn.fetch( """ SELECT id, text, tags, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY created_at """, bank_id, ) - # Should have at least one model - assert len(mental_models) >= 1, "Expected at least one mental model" + # Should have at least one observation + assert len(observations) >= 1, "Expected at least one observation" - # Either alice's model was updated OR a global model was created + # Either alice's observation was updated OR a global observation was created # This is valid LLM behavior - just verify no errors and structure is correct - for mm in mental_models: - assert mm["text"], "Mental model should have text" + for obs in observations: + assert obs["text"], "Observation should have text" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @@ -1045,9 +1045,9 @@ class TestConsolidationTagRouting: async def test_tag_filtering_in_recall( self, memory: MemoryEngine, request_context ): - """Test that mental models respect tag filtering during recall. + """Test that observations respect tag filtering during recall. - Mental models should be filtered by tags just like memories. + Observations should be filtered by tags just like memories. """ bank_id = f"test-tag-recall-filter-{uuid.uuid4().hex[:8]}" @@ -1072,19 +1072,19 @@ class TestConsolidationTagRouting: query="What does everyone do for work?", tags=["alice"], tags_match="any_strict", # Only alice's data - fact_type=["world", "experience", "mental_model"], + fact_type=["world", "experience", "observation"], request_context=request_context, ) # Results should only include alice-tagged content - # Mental models are now regular results with fact_type='mental_model' - mental_models = [r for r in recall_result.results if r.fact_type == "mental_model"] - for mm in mental_models: - # Mental model should be alice-scoped or global (untagged) + # Observations are now regular results with fact_type='observation' + observations = [r for r in recall_result.results if r.fact_type == "observation"] + for obs in observations: + # Observation should be alice-scoped or global (untagged) # Not bob-scoped - mm_tags = mm.tags or [] - assert "bob" not in mm_tags, ( - f"Recall with tags=['alice'] should not return bob's models: {mm.text}" + obs_tags = obs.tags or [] + assert "bob" not in obs_tags, ( + f"Recall with tags=['alice'] should not return bob's observations: {obs.text}" ) # Cleanup @@ -1097,43 +1097,43 @@ class TestConsolidationTagRouting: """Test that one fact can trigger multiple consolidation actions. Given: - - Global model: "Coffee is a popular beverage" - - Alice's model: "Alice drinks coffee every morning" + - Global observation: "Coffee is a popular beverage" + - Alice's observation: "Alice drinks coffee every morning" - New fact with tags=['alice']: "Alice switched to decaf coffee" Expected: - - Update Alice's scoped model (same scope) - - Potentially update global model too (global absorbs all) + - Update Alice's scoped observation (same scope) + - Potentially update global observation too (global absorbs all) """ bank_id = f"test-tag-multi-action-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Create global model + # Create global observation await memory.retain_async( bank_id=bank_id, content="Coffee is a popular beverage worldwide.", request_context=request_context, ) - # Create alice's scoped model + # Create alice's scoped observation await self._retain_with_tags( memory, bank_id, "Alice drinks coffee every morning.", ["alice"], request_context ) - # Check models before + # Check observations before async with memory._pool.acquire() as conn: - mm_before = await conn.fetch( + obs_before = await conn.fetch( """ SELECT id, text, tags, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - count_before = len(mm_before) + count_before = len(obs_before) # Add fact that could relate to both await self._retain_with_tags( @@ -1142,24 +1142,24 @@ class TestConsolidationTagRouting: ["alice"], request_context ) - # Check models after + # Check observations after async with memory._pool.acquire() as conn: - mm_after = await conn.fetch( + obs_after = await conn.fetch( """ SELECT id, text, tags, source_memory_ids, proof_count FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' ORDER BY created_at """, bank_id, ) # Should have processed without errors - assert len(mm_after) >= 1, "Expected at least one mental model" + assert len(obs_after) >= 1, "Expected at least one observation" # Check that consolidation worked (either updates or maintains structure) # The key is no errors and proper tag handling - for mm in mm_after: - assert mm["text"], "Mental model should have text" + for obs in obs_after: + assert obs["text"], "Observation should have text" # Tags should be consistent (not mixing alice and bob, etc.) # Cleanup @@ -1169,9 +1169,9 @@ class TestConsolidationTagRouting: async def test_consolidation_inherits_dates_from_source_memory( self, memory: MemoryEngine, request_context ): - """Test that mental models inherit occurred_start and event_date from source memories. + """Test that observations inherit occurred_start and event_date from source memories. - When a mental model is created, it should inherit the temporal information + When an observation is created, it should inherit the temporal information from the source memory that triggered its creation, not use the current time. """ from datetime import datetime, timezone @@ -1213,61 +1213,61 @@ class TestConsolidationTagRouting: assert result["status"] == "completed" assert result["memories_processed"] >= 1 - # Check that mental model inherited the date from source memory + # Check that observation inherited the date from source memory async with memory._pool.acquire() as conn: - mental_model = await conn.fetchrow( + observation = await conn.fetchrow( """ SELECT id, text, occurred_start, event_date, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' LIMIT 1 """, bank_id, ) - if mental_model: - # Mental model should have inherited the date from the source memory - mm_occurred = mental_model["occurred_start"] - mm_event_date = mental_model["event_date"] + if observation: + # Observation should have inherited the date from the source memory + obs_occurred = observation["occurred_start"] + obs_event_date = observation["event_date"] # Dates should match the source memory's date (2023-06-15), not today - assert mm_occurred is not None, "Mental model should have occurred_start" - assert mm_event_date is not None, "Mental model should have event_date" + assert obs_occurred is not None, "Observation should have occurred_start" + assert obs_event_date is not None, "Observation should have event_date" # The date should be from 2023, not today - assert mm_occurred.year == 2023, ( - f"Expected occurred_start year 2023, got {mm_occurred.year}. " - "Mental model should inherit date from source memory." + assert obs_occurred.year == 2023, ( + f"Expected occurred_start year 2023, got {obs_occurred.year}. " + "Observation should inherit date from source memory." ) - assert mm_occurred.month == 6, f"Expected month 6, got {mm_occurred.month}" - assert mm_occurred.day == 15, f"Expected day 15, got {mm_occurred.day}" + assert obs_occurred.month == 6, f"Expected month 6, got {obs_occurred.month}" + assert obs_occurred.day == 15, f"Expected day 15, got {obs_occurred.day}" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) -class TestMentalModelDrillDown: - """Test that reflect agent can drill down from mental models to source memories.""" +class TestObservationDrillDown: + """Test that reflect agent can drill down from observations to source memories.""" @pytest.mark.asyncio - async def test_search_mental_models_returns_source_memory_ids( + async def test_search_observations_returns_source_memory_ids( self, memory: MemoryEngine, request_context ): - """Test that search_mental_models returns source_memory_ids for drill-down. + """Test that search_observations returns source_memory_ids for drill-down. This verifies the agent can: - 1. Find a mental model + 1. Find an observation 2. Access its source_memory_ids 3. Use those IDs to expand/recall for more details """ - from hindsight_api.engine.reflect.tools import tool_search_mental_models, tool_expand + from hindsight_api.engine.reflect.tools import tool_expand, tool_search_observations - bank_id = f"test-mm-drilldown-{uuid.uuid4().hex[:8]}" + bank_id = f"test-obs-drilldown-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Store memories with specific details that get summarized in mental model + # Store memories with specific details that get summarized in observation await memory.retain_async( bank_id=bank_id, content="Sarah works at TechCorp as a senior software engineer since March 2020.", @@ -1279,32 +1279,32 @@ class TestMentalModelDrillDown: request_context=request_context, ) - # Search for mental models - result = await tool_search_mental_models( + # Search for observations + result = await tool_search_observations( memory_engine=memory, bank_id=bank_id, query="Sarah TechCorp", request_context=request_context, ) - assert result["count"] > 0, "Expected at least one mental model" + assert result["count"] > 0, "Expected at least one observation" # Verify source_memory_ids and proof_count are present - mm = result["mental_models"][0] - assert "source_memory_ids" in mm, "Mental model should have source_memory_ids" - assert "proof_count" in mm, "Mental model should have proof_count" - assert mm["proof_count"] >= 1, "proof_count should be at least 1" + obs = result["observations"][0] + assert "source_memory_ids" in obs, "Observation should have source_memory_ids" + assert "proof_count" in obs, "Observation should have proof_count" + assert obs["proof_count"] >= 1, "proof_count should be at least 1" # If source_memory_ids exist, verify they can be used with expand - if mm["source_memory_ids"]: - assert len(mm["source_memory_ids"]) >= 1, "Should have at least one source memory" + if obs["source_memory_ids"]: + assert len(obs["source_memory_ids"]) >= 1, "Should have at least one source memory" # Use expand tool to get source memory details async with memory._pool.acquire() as conn: expand_result = await tool_expand( conn=conn, bank_id=bank_id, - memory_ids=mm["source_memory_ids"][:2], # Take first 2 + memory_ids=obs["source_memory_ids"][:2], # Take first 2 depth="chunk", ) @@ -1313,7 +1313,7 @@ class TestMentalModelDrillDown: # Verify we get the original detailed information all_text = " ".join(r["memory"]["text"] for r in expand_result["results"] if "memory" in r) - # The expanded memories should contain details not necessarily in the mental model + # The expanded memories should contain details not necessarily in the observation assert "Sarah" in all_text or "TechCorp" in all_text, ( f"Expanded memories should contain source details. Got: {all_text}" ) @@ -1322,11 +1322,11 @@ class TestMentalModelDrillDown: await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_mental_model_source_ids_match_contributing_memories( + async def test_observation_source_ids_match_contributing_memories( self, memory: MemoryEngine, request_context ): - """Test that source_memory_ids actually point to the memories that built the mental model.""" - bank_id = f"test-mm-source-ids-{uuid.uuid4().hex[:8]}" + """Test that source_memory_ids actually point to the memories that built the observation.""" + bank_id = f"test-obs-source-ids-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) @@ -1343,20 +1343,20 @@ class TestMentalModelDrillDown: request_context=request_context, ) - # Get the mental model with source_memory_ids + # Get the observation with source_memory_ids async with memory._pool.acquire() as conn: - mm_rows = await conn.fetch( + obs_rows = await conn.fetch( """ SELECT id, text, proof_count, source_memory_ids FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - if mm_rows: - mm = mm_rows[0] - source_ids = mm["source_memory_ids"] or [] + if obs_rows: + obs = obs_rows[0] + source_ids = obs["source_memory_ids"] or [] # Verify source_memory_ids point to actual memories if source_ids: @@ -1388,51 +1388,51 @@ class TestHierarchicalRetrieval: """Test the reflect agent's hierarchical retrieval tools. The hierarchy is: - 1. search_reflections - User-curated summaries (highest quality) - 2. search_mental_models - Auto-consolidated knowledge + 1. search_mental_models - User-curated summaries (highest quality, formerly reflections) + 2. search_observations - Auto-consolidated knowledge (formerly mental_models) 3. recall - Raw facts as ground truth - When a reflection matches the query, it should be used first. + When a mental model matches the query, it should be used first. """ @pytest.mark.asyncio - async def test_reflection_takes_priority_over_mental_model( + async def test_mental_model_takes_priority_over_observation( self, memory: MemoryEngine, request_context ): - """Test that reflections are found and would be used before mental models. + """Test that mental models are found and would be used before observations. Given: - A memory about "John's favorite color is blue" - - A mental model created from that memory (via consolidation) - - A reflection manually created about John + - An observation created from that memory (via consolidation) + - A mental model manually created about John - When searching, the reflection should be found first. + When searching, the mental model should be found first. """ bank_id = f"test-hierarchy-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Retain a memory - consolidation creates a mental model + # Retain a memory - consolidation creates an observation await memory.retain_async( bank_id=bank_id, content="John's favorite color is blue and he likes painting.", request_context=request_context, ) - # Verify mental model was created + # Verify observation was created async with memory._pool.acquire() as conn: - mm_count = await conn.fetchval( + obs_count = await conn.fetchval( """ SELECT COUNT(*) FROM memory_units - WHERE bank_id = $1 AND fact_type = 'mental_model' + WHERE bank_id = $1 AND fact_type = 'observation' """, bank_id, ) - assert mm_count >= 1, "Consolidation should have created a mental model" + assert obs_count >= 1, "Consolidation should have created an observation" - # Create a reflection about John (higher quality, user-curated) - reflection = await memory.create_reflection( + # Create a mental model about John (higher quality, user-curated) + mental_model = await memory.create_mental_model( bank_id=bank_id, name="John's Preferences", source_query="What are John's preferences?", @@ -1440,12 +1440,12 @@ class TestHierarchicalRetrieval: tags=[], request_context=request_context, ) - assert reflection["id"] is not None + assert mental_model["id"] is not None - # Search reflections - should find our reflection + # Search mental models - should find our mental model async with memory._pool.acquire() as conn: query_embedding = memory.embeddings.encode(["What does John like?"])[0] - reflection_result = await tool_search_reflections( + mental_model_result = await tool_search_mental_models( conn=conn, bank_id=bank_id, query="What does John like?", @@ -1453,62 +1453,62 @@ class TestHierarchicalRetrieval: max_results=5, ) - # Reflection should be found - assert reflection_result["count"] >= 1, "Reflection should be found" - found_reflection = reflection_result["reflections"][0] - assert "John" in found_reflection["content"] or "blue" in found_reflection["content"] + # Mental model should be found + assert mental_model_result["count"] >= 1, "Mental model should be found" + found_mental_model = mental_model_result["mental_models"][0] + assert "John" in found_mental_model["content"] or "blue" in found_mental_model["content"] - # Search mental models - should also find something - mm_result = await tool_search_mental_models( + # Search observations - should also find something + obs_result = await tool_search_observations( memory_engine=memory, bank_id=bank_id, query="What does John like?", request_context=request_context, max_tokens=5000, ) - assert mm_result["count"] >= 1, "Mental model should also be found" + assert obs_result["count"] >= 1, "Observation should also be found" - # Verify the reflection has higher quality content (more detail) - reflection_content = found_reflection["content"] - mm_content = mm_result["mental_models"][0]["text"] + # Verify the mental model has higher quality content (more detail) + mental_model_content = found_mental_model["content"] + obs_content = obs_result["observations"][0]["text"] - # The reflection should contain the richer, user-curated content - assert "watercolors" in reflection_content or "10 years" in reflection_content, ( - f"Reflection should have the rich user-curated content. Got: {reflection_content}" + # The mental model should contain the richer, user-curated content + assert "watercolors" in mental_model_content or "10 years" in mental_model_content, ( + f"Mental model should have the rich user-curated content. Got: {mental_model_content}" ) # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_fallback_to_mental_model_when_no_reflection( + async def test_fallback_to_observation_when_no_mental_model( self, memory: MemoryEngine, request_context ): - """Test that mental models are used when no reflection matches. + """Test that observations are used when no mental model matches. Given: - A memory about "Sarah works at Google" - - A mental model created from that memory - - NO reflection about Sarah + - An observation created from that memory + - NO mental model about Sarah - When searching, mental models should provide the information. + When searching, observations should provide the information. """ bank_id = f"test-hierarchy-fallback-{uuid.uuid4().hex[:8]}" # Create the bank await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Retain a memory - consolidation creates a mental model + # Retain a memory - consolidation creates an observation await memory.retain_async( bank_id=bank_id, content="Sarah works at Google as a software engineer.", request_context=request_context, ) - # Search reflections - should find nothing + # Search mental models - should find nothing async with memory._pool.acquire() as conn: query_embedding = memory.embeddings.encode(["Where does Sarah work?"])[0] - reflection_result = await tool_search_reflections( + mental_model_result = await tool_search_mental_models( conn=conn, bank_id=bank_id, query="Where does Sarah work?", @@ -1516,11 +1516,11 @@ class TestHierarchicalRetrieval: max_results=5, ) - # No reflections exist - assert reflection_result["count"] == 0, "No reflections should exist" + # No mental models exist + assert mental_model_result["count"] == 0, "No mental models should exist" - # Search mental models - should find the consolidated knowledge - mm_result = await tool_search_mental_models( + # Search observations - should find the consolidated knowledge + obs_result = await tool_search_observations( memory_engine=memory, bank_id=bank_id, query="Where does Sarah work?", @@ -1528,11 +1528,11 @@ class TestHierarchicalRetrieval: max_tokens=5000, ) - # Mental model should be found - assert mm_result["count"] >= 1, "Mental model should be found when no reflection exists" - mm_text = mm_result["mental_models"][0]["text"].lower() - assert "sarah" in mm_text or "google" in mm_text, ( - f"Mental model should contain info about Sarah. Got: {mm_text}" + # Observation should be found + assert obs_result["count"] >= 1, "Observation should be found when no mental model exists" + obs_text = obs_result["observations"][0]["text"].lower() + assert "sarah" in obs_text or "google" in obs_text, ( + f"Observation should contain info about Sarah. Got: {obs_text}" ) # Cleanup @@ -1579,7 +1579,10 @@ class TestHierarchicalRetrieval: # Check that we get the actual numbers from the original memories all_memory_text = " ".join([m["text"] for m in recall_result["memories"]]) - assert "$1.5M" in all_memory_text or "$2.1M" in all_memory_text, ( + # Accept both abbreviated ($1.5M) and full form ($1.5 million) as LLM extraction can vary + has_q3_data = "$1.5M" in all_memory_text or "$1.5 million" in all_memory_text + has_q4_data = "$2.1M" in all_memory_text or "$2.1 million" in all_memory_text + assert has_q3_data or has_q4_data, ( f"Recall should return raw facts with specific data. Got: {all_memory_text}" ) diff --git a/hindsight-api/tests/test_llm_tools.py b/hindsight-api/tests/test_llm_tools.py index 400c52a4..2468ef22 100644 --- a/hindsight-api/tests/test_llm_tools.py +++ b/hindsight-api/tests/test_llm_tools.py @@ -241,8 +241,8 @@ class TestReflectToolSchemas: tools = get_reflect_tools() tool_names = [t["function"]["name"] for t in tools] - assert "search_reflections" in tool_names assert "search_mental_models" in tool_names + assert "search_observations" in tool_names assert "recall" in tool_names assert "expand" in tool_names assert "done" in tool_names @@ -273,8 +273,8 @@ class TestReflectToolSchemas: assert "answer" in params assert "memory_ids" in params + assert "observation_ids" in params assert "mental_model_ids" in params - assert "reflection_ids" in params class TestLLMToolCallResult: diff --git a/hindsight-api/tests/test_reflect_agent.py b/hindsight-api/tests/test_reflect_agent.py index 76749da4..84cffd03 100644 --- a/hindsight-api/tests/test_reflect_agent.py +++ b/hindsight-api/tests/test_reflect_agent.py @@ -68,15 +68,15 @@ class TestToolNameNormalization: """Standard tool names should pass through unchanged.""" assert _normalize_tool_name("done") == "done" assert _normalize_tool_name("recall") == "recall" - assert _normalize_tool_name("search_reflections") == "search_reflections" assert _normalize_tool_name("search_mental_models") == "search_mental_models" + assert _normalize_tool_name("search_observations") == "search_observations" assert _normalize_tool_name("expand") == "expand" def test_normalize_functions_prefix(self): """Tool names with 'functions.' prefix should be normalized.""" assert _normalize_tool_name("functions.done") == "done" assert _normalize_tool_name("functions.recall") == "recall" - assert _normalize_tool_name("functions.search_reflections") == "search_reflections" + assert _normalize_tool_name("functions.search_mental_models") == "search_mental_models" def test_normalize_call_equals_prefix(self): """Tool names with 'call=' prefix should be normalized.""" @@ -87,7 +87,7 @@ class TestToolNameNormalization: """Tool names with 'call=functions.' prefix should be normalized.""" assert _normalize_tool_name("call=functions.done") == "done" assert _normalize_tool_name("call=functions.recall") == "recall" - assert _normalize_tool_name("call=functions.search_mental_models") == "search_mental_models" + assert _normalize_tool_name("call=functions.search_observations") == "search_observations" def test_is_done_tool(self): """Test _is_done_tool helper.""" @@ -123,8 +123,8 @@ class TestReflectAgentMocked: def mock_functions(self): """Create mock search/recall functions.""" return { - "search_reflections_fn": AsyncMock(return_value={"reflections": []}), "search_mental_models_fn": AsyncMock(return_value={"mental_models": []}), + "search_observations_fn": AsyncMock(return_value={"observations": []}), "recall_fn": AsyncMock(return_value={"memories": [{"id": "mem-1", "content": "test memory"}]}), "expand_fn": AsyncMock(return_value={"memories": []}), } diff --git a/hindsight-api/tests/test_reflections.py b/hindsight-api/tests/test_reflections.py index 25437e06..feb497da 100644 --- a/hindsight-api/tests/test_reflections.py +++ b/hindsight-api/tests/test_reflections.py @@ -1,4 +1,4 @@ -"""Tests for reflections, mental models, and learnings functionality.""" +"""Tests for mental models (formerly reflections), observations, and learnings functionality.""" import uuid @@ -21,22 +21,22 @@ async def api_client(memory): @pytest.fixture def test_bank_id(): """Provide a unique bank ID for this test run.""" - return f"test_reflections_{uuid.uuid4().hex[:8]}" + return f"test_mental_models_{uuid.uuid4().hex[:8]}" -class TestReflectionsCRUD: - """Test reflections CRUD operations via memory engine.""" +class TestMentalModelsCRUD: + """Test mental models CRUD operations via memory engine.""" @pytest.mark.asyncio - async def test_create_and_get_reflection(self, memory: MemoryEngine, request_context): - """Test creating and retrieving a reflection.""" - bank_id = f"test-reflection-{uuid.uuid4().hex[:8]}" + async def test_create_and_get_mental_model(self, memory: MemoryEngine, request_context): + """Test creating and retrieving a mental model.""" + bank_id = f"test-mental-model-{uuid.uuid4().hex[:8]}" # Create the bank first await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Create a reflection - reflection = await memory.create_reflection( + # Create a mental model + mental_model = await memory.create_mental_model( bank_id=bank_id, name="Team Preferences", source_query="What are the team's communication preferences?", @@ -45,45 +45,45 @@ class TestReflectionsCRUD: request_context=request_context, ) - assert reflection["name"] == "Team Preferences" - assert reflection["source_query"] == "What are the team's communication preferences?" - assert reflection["content"] == "The team prefers async communication via Slack" - assert reflection["tags"] == ["team"] - assert "id" in reflection + assert mental_model["name"] == "Team Preferences" + assert mental_model["source_query"] == "What are the team's communication preferences?" + assert mental_model["content"] == "The team prefers async communication via Slack" + assert mental_model["tags"] == ["team"] + assert "id" in mental_model - # Get the reflection - fetched = await memory.get_reflection( + # Get the mental model + fetched = await memory.get_mental_model( bank_id=bank_id, - reflection_id=reflection["id"], + mental_model_id=mental_model["id"], request_context=request_context, ) - assert fetched["id"] == reflection["id"] + assert fetched["id"] == mental_model["id"] assert fetched["name"] == "Team Preferences" # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_list_reflections(self, memory: MemoryEngine, request_context): - """Test listing reflections with filters.""" - bank_id = f"test-reflection-list-{uuid.uuid4().hex[:8]}" + async def test_list_mental_models(self, memory: MemoryEngine, request_context): + """Test listing mental models with filters.""" + bank_id = f"test-mental-model-list-{uuid.uuid4().hex[:8]}" # Create the bank first await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Create multiple reflections - await memory.create_reflection( + # Create multiple mental models + await memory.create_mental_model( bank_id=bank_id, - name="Reflection 1", + name="Mental Model 1", source_query="Query 1", content="Content 1", tags=["tag1"], request_context=request_context, ) - await memory.create_reflection( + await memory.create_mental_model( bank_id=bank_id, - name="Reflection 2", + name="Mental Model 2", source_query="Query 2", content="Content 2", tags=["tag2"], @@ -91,33 +91,33 @@ class TestReflectionsCRUD: ) # List all - all_reflections = await memory.list_reflections( + all_mental_models = await memory.list_mental_models( bank_id=bank_id, request_context=request_context, ) - assert len(all_reflections) == 2 + assert len(all_mental_models) == 2 # List with tag filter - tag1_reflections = await memory.list_reflections( + tag1_mental_models = await memory.list_mental_models( bank_id=bank_id, tags=["tag1"], request_context=request_context, ) - assert len(tag1_reflections) == 1 + assert len(tag1_mental_models) == 1 # Cleanup await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_update_reflection(self, memory: MemoryEngine, request_context): - """Test updating a reflection.""" - bank_id = f"test-reflection-update-{uuid.uuid4().hex[:8]}" + async def test_update_mental_model(self, memory: MemoryEngine, request_context): + """Test updating a mental model.""" + bank_id = f"test-mental-model-update-{uuid.uuid4().hex[:8]}" # Create the bank first await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Create a reflection - reflection = await memory.create_reflection( + # Create a mental model + mental_model = await memory.create_mental_model( bank_id=bank_id, name="Original Name", source_query="Original Query", @@ -125,10 +125,10 @@ class TestReflectionsCRUD: request_context=request_context, ) - # Update the reflection - updated = await memory.update_reflection( + # Update the mental model + updated = await memory.update_mental_model( bank_id=bank_id, - reflection_id=reflection["id"], + mental_model_id=mental_model["id"], name="Updated Name", content="Updated Content", request_context=request_context, @@ -141,15 +141,15 @@ class TestReflectionsCRUD: await memory.delete_bank(bank_id, request_context=request_context) @pytest.mark.asyncio - async def test_delete_reflection(self, memory: MemoryEngine, request_context): - """Test deleting a reflection.""" - bank_id = f"test-reflection-delete-{uuid.uuid4().hex[:8]}" + async def test_delete_mental_model(self, memory: MemoryEngine, request_context): + """Test deleting a mental model.""" + bank_id = f"test-mental-model-delete-{uuid.uuid4().hex[:8]}" # Create the bank first await memory.get_bank_profile(bank_id=bank_id, request_context=request_context) - # Create a reflection - reflection = await memory.create_reflection( + # Create a mental model + mental_model = await memory.create_mental_model( bank_id=bank_id, name="To Delete", source_query="Query", @@ -157,17 +157,17 @@ class TestReflectionsCRUD: request_context=request_context, ) - # Delete the reflection - await memory.delete_reflection( + # Delete the mental model + await memory.delete_mental_model( bank_id=bank_id, - reflection_id=reflection["id"], + mental_model_id=mental_model["id"], request_context=request_context, ) # Verify deletion - should return None - fetched = await memory.get_reflection( + fetched = await memory.get_mental_model( bank_id=bank_id, - reflection_id=reflection["id"], + mental_model_id=mental_model["id"], request_context=request_context, ) assert fetched is None @@ -176,45 +176,45 @@ class TestReflectionsCRUD: await memory.delete_bank(bank_id, request_context=request_context) -class TestMentalModelsAPI: - """Test mental models API endpoints. +class TestObservationsAPI: + """Test observations API endpoints. - NOTE: Mental models are now stored in memory_units with fact_type='mental_model' - and accessed via recall with fact_type=["mental_model"]. The old /mental-models + NOTE: Observations are now stored in memory_units with fact_type='observation' + and accessed via recall with fact_type=["observation"]. The old /observations endpoint was removed. These tests are skipped. """ - @pytest.mark.skip(reason="Mental models endpoint removed - use recall with fact_type=['mental_model']") + @pytest.mark.skip(reason="Observations endpoint removed - use recall with fact_type=['observation']") @pytest.mark.asyncio - async def test_list_mental_models_empty(self, api_client, test_bank_id): - """Test listing mental models when none exist.""" + async def test_list_observations_empty(self, api_client, test_bank_id): + """Test listing observations when none exist.""" pass - @pytest.mark.skip(reason="Mental models endpoint removed - use recall with fact_type=['mental_model']") + @pytest.mark.skip(reason="Observations endpoint removed - use recall with fact_type=['observation']") @pytest.mark.asyncio - async def test_get_mental_model_not_found(self, api_client, test_bank_id): - """Test getting a non-existent mental model.""" + async def test_get_observation_not_found(self, api_client, test_bank_id): + """Test getting a non-existent observation.""" pass -class TestReflectionsAPI: - """Test reflections API endpoints.""" +class TestMentalModelsAPI: + """Test mental models API endpoints.""" @pytest.mark.asyncio - async def test_reflections_api_crud(self, api_client, test_bank_id): + async def test_mental_models_api_crud(self, api_client, test_bank_id): """Test full CRUD cycle through API.""" import asyncio # Create bank first via profile endpoint await api_client.get(f"/v1/default/banks/{test_bank_id}/profile") - # Create a reflection (async operation) + # Create a mental model (async operation) response = await api_client.post( - f"/v1/default/banks/{test_bank_id}/reflections", + f"/v1/default/banks/{test_bank_id}/mental-models", json={ - "name": "API Test Reflection", + "name": "API Test Mental Model", "source_query": "What is the API test about?", - "content": "This is an API test reflection", + "content": "This is an API test mental model", "tags": ["api-test"], }, ) @@ -232,44 +232,72 @@ class TestReflectionsAPI: break await asyncio.sleep(1) - # List reflections to get the created reflection - response = await api_client.get(f"/v1/default/banks/{test_bank_id}/reflections") + # List mental models to get the created mental model + response = await api_client.get(f"/v1/default/banks/{test_bank_id}/mental-models") assert response.status_code == 200 - reflections = response.json()["items"] - assert len(reflections) >= 1 + mental_models = response.json()["items"] + assert len(mental_models) >= 1 - # Find our reflection - reflection = next((r for r in reflections if r["name"] == "API Test Reflection"), None) - assert reflection is not None, f"Reflection not found. Items: {reflections}" - reflection_id = reflection["id"] + # Find our mental model + mental_model = next((m for m in mental_models if m["name"] == "API Test Mental Model"), None) + assert mental_model is not None, f"Mental model not found. Items: {mental_models}" + mental_model_id = mental_model["id"] - # Get the reflection - response = await api_client.get(f"/v1/default/banks/{test_bank_id}/reflections/{reflection_id}") + # Get the mental model + response = await api_client.get(f"/v1/default/banks/{test_bank_id}/mental-models/{mental_model_id}") assert response.status_code == 200 - assert response.json()["name"] == "API Test Reflection" + assert response.json()["name"] == "API Test Mental Model" - # Update the reflection + # Update the mental model response = await api_client.patch( - f"/v1/default/banks/{test_bank_id}/reflections/{reflection_id}", - json={"name": "Updated API Test Reflection"}, + f"/v1/default/banks/{test_bank_id}/mental-models/{mental_model_id}", + json={"name": "Updated API Test Mental Model"}, ) assert response.status_code == 200 - assert response.json()["name"] == "Updated API Test Reflection" + assert response.json()["name"] == "Updated API Test Mental Model" - # Delete the reflection - response = await api_client.delete(f"/v1/default/banks/{test_bank_id}/reflections/{reflection_id}") + # Delete the mental model + response = await api_client.delete(f"/v1/default/banks/{test_bank_id}/mental-models/{mental_model_id}") assert response.status_code == 200 # Verify deletion - response = await api_client.get(f"/v1/default/banks/{test_bank_id}/reflections/{reflection_id}") + response = await api_client.get(f"/v1/default/banks/{test_bank_id}/mental-models/{mental_model_id}") assert response.status_code == 404 # Cleanup await api_client.delete(f"/v1/default/banks/{test_bank_id}") -class TestRecallWithMentalModelsAndReflections: - """Test recall integration with mental models and reflections.""" +class TestRecallWithObservationsAndMentalModels: + """Test recall integration with observations and mental models.""" + + @pytest.mark.asyncio + async def test_recall_includes_observations(self, api_client, test_bank_id): + """Test that recall can include observations in the response.""" + # Create bank first via profile endpoint + await api_client.get(f"/v1/default/banks/{test_bank_id}/profile") + + # Note: Observations are auto-created via consolidation, not manually + # This test just verifies the include parameter works + + # Recall with observations included + response = await api_client.post( + f"/v1/default/banks/{test_bank_id}/memories/recall", + json={ + "query": "What is machine learning?", + "include": { + "observations": {"max_results": 5}, + }, + }, + ) + assert response.status_code == 200 + result = response.json() + + # Should have observations field in response (may be empty) + assert "observations" in result or result.get("observations") is None + + # Cleanup + await api_client.delete(f"/v1/default/banks/{test_bank_id}") @pytest.mark.asyncio async def test_recall_includes_mental_models(self, api_client, test_bank_id): @@ -277,37 +305,9 @@ class TestRecallWithMentalModelsAndReflections: # Create bank first via profile endpoint await api_client.get(f"/v1/default/banks/{test_bank_id}/profile") - # Note: Mental models are auto-created via consolidation, not manually - # This test just verifies the include parameter works - - # Recall with mental models included + # Create a mental model first response = await api_client.post( - f"/v1/default/banks/{test_bank_id}/memories/recall", - json={ - "query": "What is machine learning?", - "include": { - "mental_models": {"max_results": 5}, - }, - }, - ) - assert response.status_code == 200 - result = response.json() - - # Should have mental_models field in response (may be empty) - assert "mental_models" in result or result.get("mental_models") is None - - # Cleanup - await api_client.delete(f"/v1/default/banks/{test_bank_id}") - - @pytest.mark.asyncio - async def test_recall_includes_reflections(self, api_client, test_bank_id): - """Test that recall can include reflections in the response.""" - # Create bank first via profile endpoint - await api_client.get(f"/v1/default/banks/{test_bank_id}/profile") - - # Create a reflection first - response = await api_client.post( - f"/v1/default/banks/{test_bank_id}/reflections", + f"/v1/default/banks/{test_bank_id}/mental-models", json={ "name": "AI Overview", "source_query": "What is AI?", @@ -317,32 +317,32 @@ class TestRecallWithMentalModelsAndReflections: ) assert response.status_code == 200 - # Recall with reflections included + # Recall with mental models included response = await api_client.post( f"/v1/default/banks/{test_bank_id}/memories/recall", json={ "query": "What is artificial intelligence?", "include": { - "reflections": {"max_results": 5}, + "mental_models": {"max_results": 5}, }, }, ) assert response.status_code == 200 result = response.json() - # Should have reflections in response (may be empty if embedding not generated yet) - assert "reflections" in result or result.get("reflections") is None + # Should have mental_models in response (may be empty if embedding not generated yet) + assert "mental_models" in result or result.get("mental_models") is None # Cleanup await api_client.delete(f"/v1/default/banks/{test_bank_id}") @pytest.mark.asyncio - async def test_recall_without_mental_models_by_default(self, api_client, test_bank_id): - """Test that recall does not include mental models by default.""" + async def test_recall_without_observations_by_default(self, api_client, test_bank_id): + """Test that recall does not include observations by default.""" # Create bank first via profile endpoint await api_client.get(f"/v1/default/banks/{test_bank_id}/profile") - # Recall without specifying mental models + # Recall without specifying observations response = await api_client.post( f"/v1/default/banks/{test_bank_id}/memories/recall", json={ @@ -352,8 +352,8 @@ class TestRecallWithMentalModelsAndReflections: assert response.status_code == 200 result = response.json() - # Mental models should not be in response - assert result.get("mental_models") is None + # Observations should not be in response + assert result.get("observations") is None # Cleanup await api_client.delete(f"/v1/default/banks/{test_bank_id}") diff --git a/hindsight-api/tests/test_sql_schema_safety.py b/hindsight-api/tests/test_sql_schema_safety.py index 70f78681..60350abf 100644 --- a/hindsight-api/tests/test_sql_schema_safety.py +++ b/hindsight-api/tests/test_sql_schema_safety.py @@ -22,7 +22,7 @@ TABLES = [ "chunks", "async_operations", "directives", - "reflections", + "mental_models", ] # Files to scan for SQL queries diff --git a/hindsight-cli/src/api.rs b/hindsight-cli/src/api.rs index 2b496ecf..d4299d62 100644 --- a/hindsight-cli/src/api.rs +++ b/hindsight-cli/src/api.rs @@ -437,57 +437,57 @@ impl ApiClient { }) } - // --- Reflection Methods --- + // --- Mental Model Methods --- - pub fn list_reflections(&self, bank_id: &str, _verbose: bool) -> Result { + pub fn list_mental_models(&self, bank_id: &str, _verbose: bool) -> Result { self.runtime.block_on(async { - let response = self.client.list_reflections(bank_id, None, None, None, None, None).await?; + let response = self.client.list_mental_models(bank_id, None, None, None, None, None).await?; Ok(response.into_inner()) }) } - pub fn get_reflection(&self, bank_id: &str, reflection_id: &str, _verbose: bool) -> Result { + pub fn get_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result { self.runtime.block_on(async { - let response = self.client.get_reflection(bank_id, reflection_id, None).await?; + let response = self.client.get_mental_model(bank_id, mental_model_id, None).await?; Ok(response.into_inner()) }) } - pub fn create_reflection( + pub fn create_mental_model( &self, bank_id: &str, - request: &types::CreateReflectionRequest, + request: &types::CreateMentalModelRequest, _verbose: bool, - ) -> Result { + ) -> Result { self.runtime.block_on(async { - let response = self.client.create_reflection(bank_id, None, request).await?; + let response = self.client.create_mental_model(bank_id, None, request).await?; Ok(response.into_inner()) }) } - pub fn update_reflection( + pub fn update_mental_model( &self, bank_id: &str, - reflection_id: &str, - request: &types::UpdateReflectionRequest, + mental_model_id: &str, + request: &types::UpdateMentalModelRequest, _verbose: bool, - ) -> Result { + ) -> Result { self.runtime.block_on(async { - let response = self.client.update_reflection(bank_id, reflection_id, None, request).await?; + let response = self.client.update_mental_model(bank_id, mental_model_id, None, request).await?; Ok(response.into_inner()) }) } - pub fn delete_reflection(&self, bank_id: &str, reflection_id: &str, _verbose: bool) -> Result { + pub fn delete_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result { self.runtime.block_on(async { - let response = self.client.delete_reflection(bank_id, reflection_id, None).await?; + let response = self.client.delete_mental_model(bank_id, mental_model_id, None).await?; Ok(response.into_inner()) }) } - pub fn refresh_reflection(&self, bank_id: &str, reflection_id: &str, _verbose: bool) -> Result { + pub fn refresh_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result { self.runtime.block_on(async { - let response = self.client.refresh_reflection(bank_id, reflection_id, None).await?; + let response = self.client.refresh_mental_model(bank_id, mental_model_id, None).await?; Ok(response.into_inner()) }) } diff --git a/hindsight-cli/src/commands/reflection.rs b/hindsight-cli/src/commands/mental_model.rs similarity index 61% rename from hindsight-cli/src/commands/reflection.rs rename to hindsight-cli/src/commands/mental_model.rs index 199c5bea..483fde3b 100644 --- a/hindsight-cli/src/commands/reflection.rs +++ b/hindsight-cli/src/commands/mental_model.rs @@ -1,4 +1,4 @@ -//! Reflection commands for managing user-curated summaries. +//! Mental model commands for managing user-curated summaries. use anyhow::Result; @@ -8,7 +8,7 @@ use crate::ui; use hindsight_client::types; -/// List reflections for a bank +/// List mental models for a bank pub fn list( client: &ApiClient, bank_id: &str, @@ -16,12 +16,12 @@ pub fn list( output_format: OutputFormat, ) -> Result<()> { let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Fetching reflections...")) + Some(ui::create_spinner("Fetching mental models...")) } else { None }; - let response = client.list_reflections(bank_id, verbose); + let response = client.list_mental_models(bank_id, verbose); if let Some(mut sp) = spinner { sp.finish(); @@ -30,21 +30,21 @@ pub fn list( match response { Ok(result) => { if output_format == OutputFormat::Pretty { - ui::print_section_header(&format!("Reflections: {}", bank_id)); + ui::print_section_header(&format!("Mental Models: {}", bank_id)); if result.items.is_empty() { - println!(" {}", ui::dim("No reflections found.")); + println!(" {}", ui::dim("No mental models found.")); } else { - for reflection in &result.items { + for mental_model in &result.items { println!( " {} {}", - ui::gradient_start(&reflection.id), - reflection.name + ui::gradient_start(&mental_model.id), + mental_model.name ); // Show content preview - let preview: String = reflection.content.chars().take(80).collect(); - let ellipsis = if reflection.content.len() > 80 { "..." } else { "" }; + let preview: String = mental_model.content.chars().take(80).collect(); + let ellipsis = if mental_model.content.len() > 80 { "..." } else { "" }; println!(" {}{}", ui::dim(&preview), ellipsis); println!(); @@ -59,32 +59,32 @@ pub fn list( } } -/// Get a specific reflection +/// Get a specific mental model pub fn get( client: &ApiClient, bank_id: &str, - reflection_id: &str, + mental_model_id: &str, verbose: bool, output_format: OutputFormat, ) -> Result<()> { let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Fetching reflection...")) + Some(ui::create_spinner("Fetching mental model...")) } else { None }; - let response = client.get_reflection(bank_id, reflection_id, verbose); + let response = client.get_mental_model(bank_id, mental_model_id, verbose); if let Some(mut sp) = spinner { sp.finish(); } match response { - Ok(reflection) => { + Ok(mental_model) => { if output_format == OutputFormat::Pretty { - print_reflection_detail(&reflection); + print_mental_model_detail(&mental_model); } else { - output::print_output(&reflection, output_format)?; + output::print_output(&mental_model, output_format)?; } Ok(()) } @@ -92,7 +92,7 @@ pub fn get( } } -/// Create a new reflection +/// Create a new mental model pub fn create( client: &ApiClient, bank_id: &str, @@ -102,19 +102,19 @@ pub fn create( output_format: OutputFormat, ) -> Result<()> { let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Creating reflection...")) + Some(ui::create_spinner("Creating mental model...")) } else { None }; - let request = types::CreateReflectionRequest { + let request = types::CreateMentalModelRequest { name: name.to_string(), source_query: source_query.to_string(), max_tokens: 2048, tags: vec![], }; - let response = client.create_reflection(bank_id, &request, verbose); + let response = client.create_mental_model(bank_id, &request, verbose); if let Some(mut sp) = spinner { sp.finish(); @@ -123,7 +123,7 @@ pub fn create( match response { Ok(result) => { if output_format == OutputFormat::Pretty { - ui::print_success(&format!("Reflection created, operation_id: {}", result.operation_id)); + ui::print_success(&format!("Mental model created, operation_id: {}", result.operation_id)); } else { output::print_output(&result, output_format)?; } @@ -133,11 +133,11 @@ pub fn create( } } -/// Update a reflection +/// Update a mental model pub fn update( client: &ApiClient, bank_id: &str, - reflection_id: &str, + mental_model_id: &str, name: Option, verbose: bool, output_format: OutputFormat, @@ -147,27 +147,27 @@ pub fn update( } let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Updating reflection...")) + Some(ui::create_spinner("Updating mental model...")) } else { None }; - let request = types::UpdateReflectionRequest { name }; + let request = types::UpdateMentalModelRequest { name }; - let response = client.update_reflection(bank_id, reflection_id, &request, verbose); + let response = client.update_mental_model(bank_id, mental_model_id, &request, verbose); if let Some(mut sp) = spinner { sp.finish(); } match response { - Ok(reflection) => { + Ok(mental_model) => { if output_format == OutputFormat::Pretty { - ui::print_success(&format!("Reflection '{}' updated successfully", reflection_id)); + ui::print_success(&format!("Mental model '{}' updated successfully", mental_model_id)); println!(); - print_reflection_detail(&reflection); + print_mental_model_detail(&mental_model); } else { - output::print_output(&reflection, output_format)?; + output::print_output(&mental_model, output_format)?; } Ok(()) } @@ -175,11 +175,11 @@ pub fn update( } } -/// Delete a reflection +/// Delete a mental model pub fn delete( client: &ApiClient, bank_id: &str, - reflection_id: &str, + mental_model_id: &str, yes: bool, verbose: bool, output_format: OutputFormat, @@ -187,8 +187,8 @@ pub fn delete( // Confirmation prompt unless -y flag is used if !yes && output_format == OutputFormat::Pretty { let message = format!( - "Are you sure you want to delete reflection '{}'? This cannot be undone.", - reflection_id + "Are you sure you want to delete mental model '{}'? This cannot be undone.", + mental_model_id ); let confirmed = ui::prompt_confirmation(&message)?; @@ -200,12 +200,12 @@ pub fn delete( } let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Deleting reflection...")) + Some(ui::create_spinner("Deleting mental model...")) } else { None }; - let response = client.delete_reflection(bank_id, reflection_id, verbose); + let response = client.delete_mental_model(bank_id, mental_model_id, verbose); if let Some(mut sp) = spinner { sp.finish(); @@ -214,7 +214,7 @@ pub fn delete( match response { Ok(_) => { if output_format == OutputFormat::Pretty { - ui::print_success(&format!("Reflection '{}' deleted successfully", reflection_id)); + ui::print_success(&format!("Mental model '{}' deleted successfully", mental_model_id)); } else { println!("{{\"success\": true}}"); } @@ -224,21 +224,21 @@ pub fn delete( } } -/// Refresh a reflection +/// Refresh a mental model pub fn refresh( client: &ApiClient, bank_id: &str, - reflection_id: &str, + mental_model_id: &str, verbose: bool, output_format: OutputFormat, ) -> Result<()> { let spinner = if output_format == OutputFormat::Pretty { - Some(ui::create_spinner("Submitting reflection refresh...")) + Some(ui::create_spinner("Submitting mental model refresh...")) } else { None }; - let response = client.refresh_reflection(bank_id, reflection_id, verbose); + let response = client.refresh_mental_model(bank_id, mental_model_id, verbose); if let Some(mut sp) = spinner { sp.finish(); @@ -248,7 +248,7 @@ pub fn refresh( Ok(operation) => { if output_format == OutputFormat::Pretty { ui::print_success(&format!( - "Reflection refresh submitted. Operation ID: {}", + "Mental model refresh submitted. Operation ID: {}", operation.operation_id )); println!(" {} {}", ui::dim("Status:"), operation.status); @@ -263,16 +263,16 @@ pub fn refresh( } } -// Helper function to print reflection details -fn print_reflection_detail(reflection: &types::ReflectionResponse) { - ui::print_section_header(&reflection.name); +// Helper function to print mental model details +fn print_mental_model_detail(mental_model: &types::MentalModelResponse) { + ui::print_section_header(&mental_model.name); - println!(" {} {}", ui::dim("ID:"), ui::gradient_start(&reflection.id)); - println!(" {} {}", ui::dim("Source Query:"), &reflection.source_query); + println!(" {} {}", ui::dim("ID:"), ui::gradient_start(&mental_model.id)); + println!(" {} {}", ui::dim("Source Query:"), &mental_model.source_query); println!(); println!("{}", ui::gradient_text("─── Content ───")); println!(); - println!("{}", &reflection.content); + println!("{}", &mental_model.content); println!(); } diff --git a/hindsight-cli/src/commands/mod.rs b/hindsight-cli/src/commands/mod.rs index a7a63064..ee057f1c 100644 --- a/hindsight-cli/src/commands/mod.rs +++ b/hindsight-cli/src/commands/mod.rs @@ -7,5 +7,5 @@ pub mod explore; pub mod health; pub mod memory; pub mod operation; -pub mod reflection; +pub mod mental_model; pub mod tag; diff --git a/hindsight-cli/src/main.rs b/hindsight-cli/src/main.rs index 38d2b010..01fa771d 100644 --- a/hindsight-cli/src/main.rs +++ b/hindsight-cli/src/main.rs @@ -95,9 +95,9 @@ enum Commands { #[command(subcommand)] Operation(OperationCommands), - /// Manage reflections (user-curated summaries) + /// Manage mental models (user-curated summaries) #[command(subcommand)] - Reflection(ReflectionCommands), + MentalModel(MentalModelCommands), /// Manage directives (behavioral rules) #[command(subcommand)] @@ -539,67 +539,67 @@ enum ChunkCommands { } #[derive(Subcommand)] -enum ReflectionCommands { - /// List reflections for a bank +enum MentalModelCommands { + /// List mental models for a bank List { /// Bank ID bank_id: String, }, - /// Get a specific reflection + /// Get a specific mental model Get { /// Bank ID bank_id: String, - /// Reflection ID - reflection_id: String, + /// Mental model ID + mental_model_id: String, }, - /// Create a new reflection + /// Create a new mental model Create { /// Bank ID bank_id: String, - /// Reflection name + /// Mental model name name: String, - /// Source query to generate the reflection from + /// Source query to generate the mental model from source_query: String, }, - /// Update a reflection + /// Update a mental model Update { /// Bank ID bank_id: String, - /// Reflection ID - reflection_id: String, + /// Mental model ID + mental_model_id: String, /// New name #[arg(long)] name: Option, }, - /// Delete a reflection + /// Delete a mental model Delete { /// Bank ID bank_id: String, - /// Reflection ID - reflection_id: String, + /// Mental model ID + mental_model_id: String, /// Skip confirmation prompt #[arg(short = 'y', long)] yes: bool, }, - /// Refresh a reflection (re-run the source query) + /// Refresh a mental model (re-run the source query) Refresh { /// Bank ID bank_id: String, - /// Reflection ID - reflection_id: String, + /// Mental model ID + mental_model_id: String, }, } @@ -817,25 +817,25 @@ fn run() -> Result<()> { } }, - // Reflection commands - Commands::Reflection(ref_cmd) => match ref_cmd { - ReflectionCommands::List { bank_id } => { - commands::reflection::list(&client, &bank_id, verbose, output_format) + // Mental model commands + Commands::MentalModel(mm_cmd) => match mm_cmd { + MentalModelCommands::List { bank_id } => { + commands::mental_model::list(&client, &bank_id, verbose, output_format) } - ReflectionCommands::Get { bank_id, reflection_id } => { - commands::reflection::get(&client, &bank_id, &reflection_id, verbose, output_format) + MentalModelCommands::Get { bank_id, mental_model_id } => { + commands::mental_model::get(&client, &bank_id, &mental_model_id, verbose, output_format) } - ReflectionCommands::Create { bank_id, name, source_query } => { - commands::reflection::create(&client, &bank_id, &name, &source_query, verbose, output_format) + MentalModelCommands::Create { bank_id, name, source_query } => { + commands::mental_model::create(&client, &bank_id, &name, &source_query, verbose, output_format) } - ReflectionCommands::Update { bank_id, reflection_id, name } => { - commands::reflection::update(&client, &bank_id, &reflection_id, name, verbose, output_format) + MentalModelCommands::Update { bank_id, mental_model_id, name } => { + commands::mental_model::update(&client, &bank_id, &mental_model_id, name, verbose, output_format) } - ReflectionCommands::Delete { bank_id, reflection_id, yes } => { - commands::reflection::delete(&client, &bank_id, &reflection_id, yes, verbose, output_format) + MentalModelCommands::Delete { bank_id, mental_model_id, yes } => { + commands::mental_model::delete(&client, &bank_id, &mental_model_id, yes, verbose, output_format) } - ReflectionCommands::Refresh { bank_id, reflection_id } => { - commands::reflection::refresh(&client, &bank_id, &reflection_id, verbose, output_format) + MentalModelCommands::Refresh { bank_id, mental_model_id } => { + commands::mental_model::refresh(&client, &bank_id, &mental_model_id, verbose, output_format) } }, diff --git a/hindsight-clients/python/.openapi-generator/FILES b/hindsight-clients/python/.openapi-generator/FILES index afc687ce..163c16df 100644 --- a/hindsight-clients/python/.openapi-generator/FILES +++ b/hindsight-clients/python/.openapi-generator/FILES @@ -5,9 +5,9 @@ hindsight_client_api/api/directives_api.py hindsight_client_api/api/documents_api.py hindsight_client_api/api/entities_api.py hindsight_client_api/api/memory_api.py +hindsight_client_api/api/mental_models_api.py hindsight_client_api/api/monitoring_api.py hindsight_client_api/api/operations_api.py -hindsight_client_api/api/reflections_api.py hindsight_client_api/api_client.py hindsight_client_api/api_response.py hindsight_client_api/configuration.py @@ -28,8 +28,8 @@ hindsight_client_api/models/chunk_response.py hindsight_client_api/models/consolidation_response.py hindsight_client_api/models/create_bank_request.py hindsight_client_api/models/create_directive_request.py -hindsight_client_api/models/create_reflection_request.py -hindsight_client_api/models/create_reflection_response.py +hindsight_client_api/models/create_mental_model_request.py +hindsight_client_api/models/create_mental_model_response.py hindsight_client_api/models/delete_document_response.py hindsight_client_api/models/delete_response.py hindsight_client_api/models/directive_list_response.py @@ -51,6 +51,8 @@ hindsight_client_api/models/list_documents_response.py hindsight_client_api/models/list_memory_units_response.py hindsight_client_api/models/list_tags_response.py hindsight_client_api/models/memory_item.py +hindsight_client_api/models/mental_model_list_response.py +hindsight_client_api/models/mental_model_response.py hindsight_client_api/models/operation_response.py hindsight_client_api/models/operation_status_response.py hindsight_client_api/models/operations_list_response.py @@ -61,13 +63,10 @@ hindsight_client_api/models/reflect_based_on.py hindsight_client_api/models/reflect_fact.py hindsight_client_api/models/reflect_include_options.py hindsight_client_api/models/reflect_llm_call.py -hindsight_client_api/models/reflect_mental_model.py hindsight_client_api/models/reflect_request.py hindsight_client_api/models/reflect_response.py hindsight_client_api/models/reflect_tool_call.py hindsight_client_api/models/reflect_trace.py -hindsight_client_api/models/reflection_list_response.py -hindsight_client_api/models/reflection_response.py hindsight_client_api/models/retain_request.py hindsight_client_api/models/retain_response.py hindsight_client_api/models/tag_item.py @@ -75,7 +74,7 @@ hindsight_client_api/models/token_usage.py hindsight_client_api/models/tool_calls_include_options.py hindsight_client_api/models/update_directive_request.py hindsight_client_api/models/update_disposition_request.py -hindsight_client_api/models/update_reflection_request.py +hindsight_client_api/models/update_mental_model_request.py hindsight_client_api/models/validation_error.py hindsight_client_api/models/validation_error_loc_inner.py hindsight_client_api/models/version_response.py diff --git a/hindsight-clients/python/hindsight_client_api/__init__.py b/hindsight-clients/python/hindsight_client_api/__init__.py index 61ed2500..a2f3fcc8 100644 --- a/hindsight-clients/python/hindsight_client_api/__init__.py +++ b/hindsight-clients/python/hindsight_client_api/__init__.py @@ -22,9 +22,9 @@ from hindsight_client_api.api.directives_api import DirectivesApi from hindsight_client_api.api.documents_api import DocumentsApi from hindsight_client_api.api.entities_api import EntitiesApi from hindsight_client_api.api.memory_api import MemoryApi +from hindsight_client_api.api.mental_models_api import MentalModelsApi from hindsight_client_api.api.monitoring_api import MonitoringApi from hindsight_client_api.api.operations_api import OperationsApi -from hindsight_client_api.api.reflections_api import ReflectionsApi # import ApiClient from hindsight_client_api.api_response import ApiResponse @@ -53,8 +53,8 @@ from hindsight_client_api.models.chunk_response import ChunkResponse from hindsight_client_api.models.consolidation_response import ConsolidationResponse from hindsight_client_api.models.create_bank_request import CreateBankRequest from hindsight_client_api.models.create_directive_request import CreateDirectiveRequest -from hindsight_client_api.models.create_reflection_request import CreateReflectionRequest -from hindsight_client_api.models.create_reflection_response import CreateReflectionResponse +from hindsight_client_api.models.create_mental_model_request import CreateMentalModelRequest +from hindsight_client_api.models.create_mental_model_response import CreateMentalModelResponse from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse from hindsight_client_api.models.delete_response import DeleteResponse from hindsight_client_api.models.directive_list_response import DirectiveListResponse @@ -76,6 +76,8 @@ from hindsight_client_api.models.list_documents_response import ListDocumentsRes from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse from hindsight_client_api.models.list_tags_response import ListTagsResponse from hindsight_client_api.models.memory_item import MemoryItem +from hindsight_client_api.models.mental_model_list_response import MentalModelListResponse +from hindsight_client_api.models.mental_model_response import MentalModelResponse from hindsight_client_api.models.operation_response import OperationResponse from hindsight_client_api.models.operation_status_response import OperationStatusResponse from hindsight_client_api.models.operations_list_response import OperationsListResponse @@ -86,13 +88,10 @@ from hindsight_client_api.models.reflect_based_on import ReflectBasedOn from hindsight_client_api.models.reflect_fact import ReflectFact from hindsight_client_api.models.reflect_include_options import ReflectIncludeOptions from hindsight_client_api.models.reflect_llm_call import ReflectLLMCall -from hindsight_client_api.models.reflect_mental_model import ReflectMentalModel from hindsight_client_api.models.reflect_request import ReflectRequest from hindsight_client_api.models.reflect_response import ReflectResponse from hindsight_client_api.models.reflect_tool_call import ReflectToolCall from hindsight_client_api.models.reflect_trace import ReflectTrace -from hindsight_client_api.models.reflection_list_response import ReflectionListResponse -from hindsight_client_api.models.reflection_response import ReflectionResponse from hindsight_client_api.models.retain_request import RetainRequest from hindsight_client_api.models.retain_response import RetainResponse from hindsight_client_api.models.tag_item import TagItem @@ -100,7 +99,7 @@ from hindsight_client_api.models.token_usage import TokenUsage from hindsight_client_api.models.tool_calls_include_options import ToolCallsIncludeOptions from hindsight_client_api.models.update_directive_request import UpdateDirectiveRequest from hindsight_client_api.models.update_disposition_request import UpdateDispositionRequest -from hindsight_client_api.models.update_reflection_request import UpdateReflectionRequest +from hindsight_client_api.models.update_mental_model_request import UpdateMentalModelRequest from hindsight_client_api.models.validation_error import ValidationError from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner from hindsight_client_api.models.version_response import VersionResponse diff --git a/hindsight-clients/python/hindsight_client_api/api/__init__.py b/hindsight-clients/python/hindsight_client_api/api/__init__.py index b7029893..dda78a34 100644 --- a/hindsight-clients/python/hindsight_client_api/api/__init__.py +++ b/hindsight-clients/python/hindsight_client_api/api/__init__.py @@ -6,7 +6,7 @@ from hindsight_client_api.api.directives_api import DirectivesApi from hindsight_client_api.api.documents_api import DocumentsApi from hindsight_client_api.api.entities_api import EntitiesApi from hindsight_client_api.api.memory_api import MemoryApi +from hindsight_client_api.api.mental_models_api import MentalModelsApi from hindsight_client_api.api.monitoring_api import MonitoringApi from hindsight_client_api.api.operations_api import OperationsApi -from hindsight_client_api.api.reflections_api import ReflectionsApi diff --git a/hindsight-clients/python/hindsight_client_api/api/banks_api.py b/hindsight-clients/python/hindsight_client_api/api/banks_api.py index a0bf82b0..63ac9c0f 100644 --- a/hindsight-clients/python/hindsight_client_api/api/banks_api.py +++ b/hindsight-clients/python/hindsight_client_api/api/banks_api.py @@ -356,7 +356,7 @@ class BanksApi: @validate_call - async def clear_mental_models( + async def clear_observations( self, bank_id: StrictStr, authorization: Optional[StrictStr] = None, @@ -373,9 +373,9 @@ class BanksApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> DeleteResponse: - """Clear all mental models + """Clear all observations - Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge. + Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge. :param bank_id: (required) :type bank_id: str @@ -403,7 +403,7 @@ class BanksApi: :return: Returns the result object. """ # noqa: E501 - _param = self._clear_mental_models_serialize( + _param = self._clear_observations_serialize( bank_id=bank_id, authorization=authorization, _request_auth=_request_auth, @@ -428,7 +428,7 @@ class BanksApi: @validate_call - async def clear_mental_models_with_http_info( + async def clear_observations_with_http_info( self, bank_id: StrictStr, authorization: Optional[StrictStr] = None, @@ -445,9 +445,9 @@ class BanksApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> ApiResponse[DeleteResponse]: - """Clear all mental models + """Clear all observations - Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge. + Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge. :param bank_id: (required) :type bank_id: str @@ -475,7 +475,7 @@ class BanksApi: :return: Returns the result object. """ # noqa: E501 - _param = self._clear_mental_models_serialize( + _param = self._clear_observations_serialize( bank_id=bank_id, authorization=authorization, _request_auth=_request_auth, @@ -500,7 +500,7 @@ class BanksApi: @validate_call - async def clear_mental_models_without_preload_content( + async def clear_observations_without_preload_content( self, bank_id: StrictStr, authorization: Optional[StrictStr] = None, @@ -517,9 +517,9 @@ class BanksApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Clear all mental models + """Clear all observations - Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge. + Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge. :param bank_id: (required) :type bank_id: str @@ -547,7 +547,7 @@ class BanksApi: :return: Returns the result object. """ # noqa: E501 - _param = self._clear_mental_models_serialize( + _param = self._clear_observations_serialize( bank_id=bank_id, authorization=authorization, _request_auth=_request_auth, @@ -567,7 +567,7 @@ class BanksApi: return response_data.response - def _clear_mental_models_serialize( + def _clear_observations_serialize( self, bank_id, authorization, @@ -617,7 +617,7 @@ class BanksApi: return self.api_client.param_serialize( method='DELETE', - resource_path='/v1/default/banks/{bank_id}/mental-models', + resource_path='/v1/default/banks/{bank_id}/observations', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -2056,7 +2056,7 @@ class BanksApi: ) -> ConsolidationResponse: """Trigger consolidation - Run memory consolidation to create/update mental models from recent memories. + Run memory consolidation to create/update observations from recent memories. :param bank_id: (required) :type bank_id: str @@ -2128,7 +2128,7 @@ class BanksApi: ) -> ApiResponse[ConsolidationResponse]: """Trigger consolidation - Run memory consolidation to create/update mental models from recent memories. + Run memory consolidation to create/update observations from recent memories. :param bank_id: (required) :type bank_id: str @@ -2200,7 +2200,7 @@ class BanksApi: ) -> RESTResponseType: """Trigger consolidation - Run memory consolidation to create/update mental models from recent memories. + Run memory consolidation to create/update observations from recent memories. :param bank_id: (required) :type bank_id: str diff --git a/hindsight-clients/python/hindsight_client_api/api/reflections_api.py b/hindsight-clients/python/hindsight_client_api/api/mental_models_api.py similarity index 86% rename from hindsight-clients/python/hindsight_client_api/api/reflections_api.py rename to hindsight-clients/python/hindsight_client_api/api/mental_models_api.py index f9d47c8c..bf718eec 100644 --- a/hindsight-clients/python/hindsight_client_api/api/reflections_api.py +++ b/hindsight-clients/python/hindsight_client_api/api/mental_models_api.py @@ -20,18 +20,18 @@ from pydantic import Field, StrictStr, field_validator from typing import Any, List, Optional from typing_extensions import Annotated from hindsight_client_api.models.async_operation_submit_response import AsyncOperationSubmitResponse -from hindsight_client_api.models.create_reflection_request import CreateReflectionRequest -from hindsight_client_api.models.create_reflection_response import CreateReflectionResponse -from hindsight_client_api.models.reflection_list_response import ReflectionListResponse -from hindsight_client_api.models.reflection_response import ReflectionResponse -from hindsight_client_api.models.update_reflection_request import UpdateReflectionRequest +from hindsight_client_api.models.create_mental_model_request import CreateMentalModelRequest +from hindsight_client_api.models.create_mental_model_response import CreateMentalModelResponse +from hindsight_client_api.models.mental_model_list_response import MentalModelListResponse +from hindsight_client_api.models.mental_model_response import MentalModelResponse +from hindsight_client_api.models.update_mental_model_request import UpdateMentalModelRequest from hindsight_client_api.api_client import ApiClient, RequestSerialized from hindsight_client_api.api_response import ApiResponse from hindsight_client_api.rest import RESTResponseType -class ReflectionsApi: +class MentalModelsApi: """NOTE: This class is auto generated by OpenAPI Generator Ref: https://openapi-generator.tech @@ -45,10 +45,10 @@ class ReflectionsApi: @validate_call - async def create_reflection( + async def create_mental_model( self, bank_id: StrictStr, - create_reflection_request: CreateReflectionRequest, + create_mental_model_request: CreateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -62,15 +62,15 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> CreateReflectionResponse: - """Create reflection + ) -> CreateMentalModelResponse: + """Create mental model - Create a reflection by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. + Create a mental model by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. :param bank_id: (required) :type bank_id: str - :param create_reflection_request: (required) - :type create_reflection_request: CreateReflectionRequest + :param create_mental_model_request: (required) + :type create_mental_model_request: CreateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -95,9 +95,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._create_reflection_serialize( + _param = self._create_mental_model_serialize( bank_id=bank_id, - create_reflection_request=create_reflection_request, + create_mental_model_request=create_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -106,7 +106,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "CreateReflectionResponse", + '200': "CreateMentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -121,10 +121,10 @@ class ReflectionsApi: @validate_call - async def create_reflection_with_http_info( + async def create_mental_model_with_http_info( self, bank_id: StrictStr, - create_reflection_request: CreateReflectionRequest, + create_mental_model_request: CreateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -138,15 +138,15 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[CreateReflectionResponse]: - """Create reflection + ) -> ApiResponse[CreateMentalModelResponse]: + """Create mental model - Create a reflection by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. + Create a mental model by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. :param bank_id: (required) :type bank_id: str - :param create_reflection_request: (required) - :type create_reflection_request: CreateReflectionRequest + :param create_mental_model_request: (required) + :type create_mental_model_request: CreateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -171,9 +171,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._create_reflection_serialize( + _param = self._create_mental_model_serialize( bank_id=bank_id, - create_reflection_request=create_reflection_request, + create_mental_model_request=create_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -182,7 +182,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "CreateReflectionResponse", + '200': "CreateMentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -197,10 +197,10 @@ class ReflectionsApi: @validate_call - async def create_reflection_without_preload_content( + async def create_mental_model_without_preload_content( self, bank_id: StrictStr, - create_reflection_request: CreateReflectionRequest, + create_mental_model_request: CreateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -215,14 +215,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Create reflection + """Create mental model - Create a reflection by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. + Create a mental model by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. :param bank_id: (required) :type bank_id: str - :param create_reflection_request: (required) - :type create_reflection_request: CreateReflectionRequest + :param create_mental_model_request: (required) + :type create_mental_model_request: CreateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -247,9 +247,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._create_reflection_serialize( + _param = self._create_mental_model_serialize( bank_id=bank_id, - create_reflection_request=create_reflection_request, + create_mental_model_request=create_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -258,7 +258,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "CreateReflectionResponse", + '200': "CreateMentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -268,10 +268,10 @@ class ReflectionsApi: return response_data.response - def _create_reflection_serialize( + def _create_mental_model_serialize( self, bank_id, - create_reflection_request, + create_mental_model_request, authorization, _request_auth, _content_type, @@ -302,8 +302,8 @@ class ReflectionsApi: _header_params['authorization'] = authorization # process the form parameters # process the body parameter - if create_reflection_request is not None: - _body_params = create_reflection_request + if create_mental_model_request is not None: + _body_params = create_mental_model_request # set the HTTP header `Accept` @@ -334,7 +334,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='POST', - resource_path='/v1/default/banks/{bank_id}/reflections', + resource_path='/v1/default/banks/{bank_id}/mental-models', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -351,10 +351,10 @@ class ReflectionsApi: @validate_call - async def delete_reflection( + async def delete_mental_model( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -369,14 +369,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> object: - """Delete reflection + """Delete mental model - Delete a reflection. + Delete a mental model. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -401,9 +401,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._delete_reflection_serialize( + _param = self._delete_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -427,10 +427,10 @@ class ReflectionsApi: @validate_call - async def delete_reflection_with_http_info( + async def delete_mental_model_with_http_info( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -445,14 +445,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> ApiResponse[object]: - """Delete reflection + """Delete mental model - Delete a reflection. + Delete a mental model. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -477,9 +477,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._delete_reflection_serialize( + _param = self._delete_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -503,10 +503,10 @@ class ReflectionsApi: @validate_call - async def delete_reflection_without_preload_content( + async def delete_mental_model_without_preload_content( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -521,14 +521,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Delete reflection + """Delete mental model - Delete a reflection. + Delete a mental model. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -553,9 +553,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._delete_reflection_serialize( + _param = self._delete_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -574,10 +574,10 @@ class ReflectionsApi: return response_data.response - def _delete_reflection_serialize( + def _delete_mental_model_serialize( self, bank_id, - reflection_id, + mental_model_id, authorization, _request_auth, _content_type, @@ -602,8 +602,8 @@ class ReflectionsApi: # process the path parameters if bank_id is not None: _path_params['bank_id'] = bank_id - if reflection_id is not None: - _path_params['reflection_id'] = reflection_id + if mental_model_id is not None: + _path_params['mental_model_id'] = mental_model_id # process the query parameters # process the header parameters if authorization is not None: @@ -627,7 +627,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='DELETE', - resource_path='/v1/default/banks/{bank_id}/reflections/{reflection_id}', + resource_path='/v1/default/banks/{bank_id}/mental-models/{mental_model_id}', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -644,10 +644,10 @@ class ReflectionsApi: @validate_call - async def get_reflection( + async def get_mental_model( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -661,15 +661,15 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ReflectionResponse: - """Get reflection + ) -> MentalModelResponse: + """Get mental model - Get a specific reflection by ID. + Get a specific mental model by ID. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -694,9 +694,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._get_reflection_serialize( + _param = self._get_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -705,7 +705,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -720,10 +720,10 @@ class ReflectionsApi: @validate_call - async def get_reflection_with_http_info( + async def get_mental_model_with_http_info( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -737,15 +737,15 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[ReflectionResponse]: - """Get reflection + ) -> ApiResponse[MentalModelResponse]: + """Get mental model - Get a specific reflection by ID. + Get a specific mental model by ID. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -770,9 +770,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._get_reflection_serialize( + _param = self._get_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -781,7 +781,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -796,10 +796,10 @@ class ReflectionsApi: @validate_call - async def get_reflection_without_preload_content( + async def get_mental_model_without_preload_content( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -814,14 +814,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Get reflection + """Get mental model - Get a specific reflection by ID. + Get a specific mental model by ID. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -846,9 +846,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._get_reflection_serialize( + _param = self._get_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -857,7 +857,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -867,10 +867,10 @@ class ReflectionsApi: return response_data.response - def _get_reflection_serialize( + def _get_mental_model_serialize( self, bank_id, - reflection_id, + mental_model_id, authorization, _request_auth, _content_type, @@ -895,8 +895,8 @@ class ReflectionsApi: # process the path parameters if bank_id is not None: _path_params['bank_id'] = bank_id - if reflection_id is not None: - _path_params['reflection_id'] = reflection_id + if mental_model_id is not None: + _path_params['mental_model_id'] = mental_model_id # process the query parameters # process the header parameters if authorization is not None: @@ -920,7 +920,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='GET', - resource_path='/v1/default/banks/{bank_id}/reflections/{reflection_id}', + resource_path='/v1/default/banks/{bank_id}/mental-models/{mental_model_id}', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -937,7 +937,7 @@ class ReflectionsApi: @validate_call - async def list_reflections( + async def list_mental_models( self, bank_id: StrictStr, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, @@ -957,8 +957,8 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ReflectionListResponse: - """List reflections + ) -> MentalModelListResponse: + """List mental models List user-curated living documents that stay current. @@ -996,7 +996,7 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._list_reflections_serialize( + _param = self._list_mental_models_serialize( bank_id=bank_id, tags=tags, tags_match=tags_match, @@ -1010,7 +1010,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionListResponse", + '200': "MentalModelListResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1025,7 +1025,7 @@ class ReflectionsApi: @validate_call - async def list_reflections_with_http_info( + async def list_mental_models_with_http_info( self, bank_id: StrictStr, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, @@ -1045,8 +1045,8 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[ReflectionListResponse]: - """List reflections + ) -> ApiResponse[MentalModelListResponse]: + """List mental models List user-curated living documents that stay current. @@ -1084,7 +1084,7 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._list_reflections_serialize( + _param = self._list_mental_models_serialize( bank_id=bank_id, tags=tags, tags_match=tags_match, @@ -1098,7 +1098,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionListResponse", + '200': "MentalModelListResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1113,7 +1113,7 @@ class ReflectionsApi: @validate_call - async def list_reflections_without_preload_content( + async def list_mental_models_without_preload_content( self, bank_id: StrictStr, tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None, @@ -1134,7 +1134,7 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """List reflections + """List mental models List user-curated living documents that stay current. @@ -1172,7 +1172,7 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._list_reflections_serialize( + _param = self._list_mental_models_serialize( bank_id=bank_id, tags=tags, tags_match=tags_match, @@ -1186,7 +1186,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionListResponse", + '200': "MentalModelListResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1196,7 +1196,7 @@ class ReflectionsApi: return response_data.response - def _list_reflections_serialize( + def _list_mental_models_serialize( self, bank_id, tags, @@ -1267,7 +1267,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='GET', - resource_path='/v1/default/banks/{bank_id}/reflections', + resource_path='/v1/default/banks/{bank_id}/mental-models', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -1284,10 +1284,10 @@ class ReflectionsApi: @validate_call - async def refresh_reflection( + async def refresh_mental_model( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1302,14 +1302,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> AsyncOperationSubmitResponse: - """Refresh reflection + """Refresh mental model Submit an async task to re-run the source query through reflect and update the content. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1334,9 +1334,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._refresh_reflection_serialize( + _param = self._refresh_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1360,10 +1360,10 @@ class ReflectionsApi: @validate_call - async def refresh_reflection_with_http_info( + async def refresh_mental_model_with_http_info( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1378,14 +1378,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> ApiResponse[AsyncOperationSubmitResponse]: - """Refresh reflection + """Refresh mental model Submit an async task to re-run the source query through reflect and update the content. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1410,9 +1410,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._refresh_reflection_serialize( + _param = self._refresh_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1436,10 +1436,10 @@ class ReflectionsApi: @validate_call - async def refresh_reflection_without_preload_content( + async def refresh_mental_model_without_preload_content( self, bank_id: StrictStr, - reflection_id: StrictStr, + mental_model_id: StrictStr, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1454,14 +1454,14 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Refresh reflection + """Refresh mental model Submit an async task to re-run the source query through reflect and update the content. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str + :param mental_model_id: (required) + :type mental_model_id: str :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1486,9 +1486,9 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._refresh_reflection_serialize( + _param = self._refresh_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, + mental_model_id=mental_model_id, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1507,10 +1507,10 @@ class ReflectionsApi: return response_data.response - def _refresh_reflection_serialize( + def _refresh_mental_model_serialize( self, bank_id, - reflection_id, + mental_model_id, authorization, _request_auth, _content_type, @@ -1535,8 +1535,8 @@ class ReflectionsApi: # process the path parameters if bank_id is not None: _path_params['bank_id'] = bank_id - if reflection_id is not None: - _path_params['reflection_id'] = reflection_id + if mental_model_id is not None: + _path_params['mental_model_id'] = mental_model_id # process the query parameters # process the header parameters if authorization is not None: @@ -1560,7 +1560,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='POST', - resource_path='/v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh', + resource_path='/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh', path_params=_path_params, query_params=_query_params, header_params=_header_params, @@ -1577,11 +1577,11 @@ class ReflectionsApi: @validate_call - async def update_reflection( + async def update_mental_model( self, bank_id: StrictStr, - reflection_id: StrictStr, - update_reflection_request: UpdateReflectionRequest, + mental_model_id: StrictStr, + update_mental_model_request: UpdateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1595,17 +1595,17 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ReflectionResponse: - """Update reflection + ) -> MentalModelResponse: + """Update mental model - Update a reflection's name. + Update a mental model's name. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str - :param update_reflection_request: (required) - :type update_reflection_request: UpdateReflectionRequest + :param mental_model_id: (required) + :type mental_model_id: str + :param update_mental_model_request: (required) + :type update_mental_model_request: UpdateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1630,10 +1630,10 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._update_reflection_serialize( + _param = self._update_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, - update_reflection_request=update_reflection_request, + mental_model_id=mental_model_id, + update_mental_model_request=update_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1642,7 +1642,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1657,11 +1657,11 @@ class ReflectionsApi: @validate_call - async def update_reflection_with_http_info( + async def update_mental_model_with_http_info( self, bank_id: StrictStr, - reflection_id: StrictStr, - update_reflection_request: UpdateReflectionRequest, + mental_model_id: StrictStr, + update_mental_model_request: UpdateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1675,17 +1675,17 @@ class ReflectionsApi: _content_type: Optional[StrictStr] = None, _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, - ) -> ApiResponse[ReflectionResponse]: - """Update reflection + ) -> ApiResponse[MentalModelResponse]: + """Update mental model - Update a reflection's name. + Update a mental model's name. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str - :param update_reflection_request: (required) - :type update_reflection_request: UpdateReflectionRequest + :param mental_model_id: (required) + :type mental_model_id: str + :param update_mental_model_request: (required) + :type update_mental_model_request: UpdateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1710,10 +1710,10 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._update_reflection_serialize( + _param = self._update_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, - update_reflection_request=update_reflection_request, + mental_model_id=mental_model_id, + update_mental_model_request=update_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1722,7 +1722,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1737,11 +1737,11 @@ class ReflectionsApi: @validate_call - async def update_reflection_without_preload_content( + async def update_mental_model_without_preload_content( self, bank_id: StrictStr, - reflection_id: StrictStr, - update_reflection_request: UpdateReflectionRequest, + mental_model_id: StrictStr, + update_mental_model_request: UpdateMentalModelRequest, authorization: Optional[StrictStr] = None, _request_timeout: Union[ None, @@ -1756,16 +1756,16 @@ class ReflectionsApi: _headers: Optional[Dict[StrictStr, Any]] = None, _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, ) -> RESTResponseType: - """Update reflection + """Update mental model - Update a reflection's name. + Update a mental model's name. :param bank_id: (required) :type bank_id: str - :param reflection_id: (required) - :type reflection_id: str - :param update_reflection_request: (required) - :type update_reflection_request: UpdateReflectionRequest + :param mental_model_id: (required) + :type mental_model_id: str + :param update_mental_model_request: (required) + :type update_mental_model_request: UpdateMentalModelRequest :param authorization: :type authorization: str :param _request_timeout: timeout setting for this request. If one @@ -1790,10 +1790,10 @@ class ReflectionsApi: :return: Returns the result object. """ # noqa: E501 - _param = self._update_reflection_serialize( + _param = self._update_mental_model_serialize( bank_id=bank_id, - reflection_id=reflection_id, - update_reflection_request=update_reflection_request, + mental_model_id=mental_model_id, + update_mental_model_request=update_mental_model_request, authorization=authorization, _request_auth=_request_auth, _content_type=_content_type, @@ -1802,7 +1802,7 @@ class ReflectionsApi: ) _response_types_map: Dict[str, Optional[str]] = { - '200': "ReflectionResponse", + '200': "MentalModelResponse", '422': "HTTPValidationError", } response_data = await self.api_client.call_api( @@ -1812,11 +1812,11 @@ class ReflectionsApi: return response_data.response - def _update_reflection_serialize( + def _update_mental_model_serialize( self, bank_id, - reflection_id, - update_reflection_request, + mental_model_id, + update_mental_model_request, authorization, _request_auth, _content_type, @@ -1841,16 +1841,16 @@ class ReflectionsApi: # process the path parameters if bank_id is not None: _path_params['bank_id'] = bank_id - if reflection_id is not None: - _path_params['reflection_id'] = reflection_id + if mental_model_id is not None: + _path_params['mental_model_id'] = mental_model_id # process the query parameters # process the header parameters if authorization is not None: _header_params['authorization'] = authorization # process the form parameters # process the body parameter - if update_reflection_request is not None: - _body_params = update_reflection_request + if update_mental_model_request is not None: + _body_params = update_mental_model_request # set the HTTP header `Accept` @@ -1881,7 +1881,7 @@ class ReflectionsApi: return self.api_client.param_serialize( method='PATCH', - resource_path='/v1/default/banks/{bank_id}/reflections/{reflection_id}', + resource_path='/v1/default/banks/{bank_id}/mental-models/{mental_model_id}', path_params=_path_params, query_params=_query_params, header_params=_header_params, diff --git a/hindsight-clients/python/hindsight_client_api/models/__init__.py b/hindsight-clients/python/hindsight_client_api/models/__init__.py index 2a897d1a..56440ea1 100644 --- a/hindsight-clients/python/hindsight_client_api/models/__init__.py +++ b/hindsight-clients/python/hindsight_client_api/models/__init__.py @@ -29,8 +29,8 @@ from hindsight_client_api.models.chunk_response import ChunkResponse from hindsight_client_api.models.consolidation_response import ConsolidationResponse from hindsight_client_api.models.create_bank_request import CreateBankRequest from hindsight_client_api.models.create_directive_request import CreateDirectiveRequest -from hindsight_client_api.models.create_reflection_request import CreateReflectionRequest -from hindsight_client_api.models.create_reflection_response import CreateReflectionResponse +from hindsight_client_api.models.create_mental_model_request import CreateMentalModelRequest +from hindsight_client_api.models.create_mental_model_response import CreateMentalModelResponse from hindsight_client_api.models.delete_document_response import DeleteDocumentResponse from hindsight_client_api.models.delete_response import DeleteResponse from hindsight_client_api.models.directive_list_response import DirectiveListResponse @@ -52,6 +52,8 @@ from hindsight_client_api.models.list_documents_response import ListDocumentsRes from hindsight_client_api.models.list_memory_units_response import ListMemoryUnitsResponse from hindsight_client_api.models.list_tags_response import ListTagsResponse from hindsight_client_api.models.memory_item import MemoryItem +from hindsight_client_api.models.mental_model_list_response import MentalModelListResponse +from hindsight_client_api.models.mental_model_response import MentalModelResponse from hindsight_client_api.models.operation_response import OperationResponse from hindsight_client_api.models.operation_status_response import OperationStatusResponse from hindsight_client_api.models.operations_list_response import OperationsListResponse @@ -62,13 +64,10 @@ from hindsight_client_api.models.reflect_based_on import ReflectBasedOn from hindsight_client_api.models.reflect_fact import ReflectFact from hindsight_client_api.models.reflect_include_options import ReflectIncludeOptions from hindsight_client_api.models.reflect_llm_call import ReflectLLMCall -from hindsight_client_api.models.reflect_mental_model import ReflectMentalModel from hindsight_client_api.models.reflect_request import ReflectRequest from hindsight_client_api.models.reflect_response import ReflectResponse from hindsight_client_api.models.reflect_tool_call import ReflectToolCall from hindsight_client_api.models.reflect_trace import ReflectTrace -from hindsight_client_api.models.reflection_list_response import ReflectionListResponse -from hindsight_client_api.models.reflection_response import ReflectionResponse from hindsight_client_api.models.retain_request import RetainRequest from hindsight_client_api.models.retain_response import RetainResponse from hindsight_client_api.models.tag_item import TagItem @@ -76,7 +75,7 @@ from hindsight_client_api.models.token_usage import TokenUsage from hindsight_client_api.models.tool_calls_include_options import ToolCallsIncludeOptions from hindsight_client_api.models.update_directive_request import UpdateDirectiveRequest from hindsight_client_api.models.update_disposition_request import UpdateDispositionRequest -from hindsight_client_api.models.update_reflection_request import UpdateReflectionRequest +from hindsight_client_api.models.update_mental_model_request import UpdateMentalModelRequest from hindsight_client_api.models.validation_error import ValidationError from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner from hindsight_client_api.models.version_response import VersionResponse diff --git a/hindsight-clients/python/hindsight_client_api/models/bank_stats_response.py b/hindsight-clients/python/hindsight_client_api/models/bank_stats_response.py index bb0243bb..f5de9b81 100644 --- a/hindsight-clients/python/hindsight_client_api/models/bank_stats_response.py +++ b/hindsight-clients/python/hindsight_client_api/models/bank_stats_response.py @@ -37,9 +37,9 @@ class BankStatsResponse(BaseModel): pending_operations: StrictInt failed_operations: StrictInt last_consolidated_at: Optional[StrictStr] = None - pending_consolidation: Optional[StrictInt] = Field(default=0, description="Number of memories not yet processed into mental models") - total_mental_models: Optional[StrictInt] = Field(default=0, description="Total number of mental models") - __properties: ClassVar[List[str]] = ["bank_id", "total_nodes", "total_links", "total_documents", "nodes_by_fact_type", "links_by_link_type", "links_by_fact_type", "links_breakdown", "pending_operations", "failed_operations", "last_consolidated_at", "pending_consolidation", "total_mental_models"] + pending_consolidation: Optional[StrictInt] = Field(default=0, description="Number of memories not yet processed into observations") + total_observations: Optional[StrictInt] = Field(default=0, description="Total number of observations") + __properties: ClassVar[List[str]] = ["bank_id", "total_nodes", "total_links", "total_documents", "nodes_by_fact_type", "links_by_link_type", "links_by_fact_type", "links_breakdown", "pending_operations", "failed_operations", "last_consolidated_at", "pending_consolidation", "total_observations"] model_config = ConfigDict( populate_by_name=True, @@ -109,7 +109,7 @@ class BankStatsResponse(BaseModel): "failed_operations": obj.get("failed_operations"), "last_consolidated_at": obj.get("last_consolidated_at"), "pending_consolidation": obj.get("pending_consolidation") if obj.get("pending_consolidation") is not None else 0, - "total_mental_models": obj.get("total_mental_models") if obj.get("total_mental_models") is not None else 0 + "total_observations": obj.get("total_observations") if obj.get("total_observations") is not None else 0 }) return _obj diff --git a/hindsight-clients/python/hindsight_client_api/models/create_reflection_request.py b/hindsight-clients/python/hindsight_client_api/models/create_mental_model_request.py similarity index 91% rename from hindsight-clients/python/hindsight_client_api/models/create_reflection_request.py rename to hindsight-clients/python/hindsight_client_api/models/create_mental_model_request.py index 70817ab7..72885176 100644 --- a/hindsight-clients/python/hindsight_client_api/models/create_reflection_request.py +++ b/hindsight-clients/python/hindsight_client_api/models/create_mental_model_request.py @@ -23,11 +23,11 @@ from typing_extensions import Annotated from typing import Optional, Set from typing_extensions import Self -class CreateReflectionRequest(BaseModel): +class CreateMentalModelRequest(BaseModel): """ - Request model for creating a reflection. + Request model for creating a mental model. """ # noqa: E501 - name: StrictStr = Field(description="Human-readable name for the reflection") + name: StrictStr = Field(description="Human-readable name for the mental model") source_query: StrictStr = Field(description="The query to run to generate content") tags: Optional[List[StrictStr]] = Field(default=None, description="Tags for scoped visibility") max_tokens: Optional[Annotated[int, Field(le=8192, strict=True, ge=256)]] = Field(default=2048, description="Maximum tokens for generated content") @@ -51,7 +51,7 @@ class CreateReflectionRequest(BaseModel): @classmethod def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of CreateReflectionRequest from a JSON string""" + """Create an instance of CreateMentalModelRequest from a JSON string""" return cls.from_dict(json.loads(json_str)) def to_dict(self) -> Dict[str, Any]: @@ -76,7 +76,7 @@ class CreateReflectionRequest(BaseModel): @classmethod def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of CreateReflectionRequest from a dict""" + """Create an instance of CreateMentalModelRequest from a dict""" if obj is None: return None diff --git a/hindsight-clients/python/hindsight_client_api/models/create_reflection_response.py b/hindsight-clients/python/hindsight_client_api/models/create_mental_model_response.py similarity index 90% rename from hindsight-clients/python/hindsight_client_api/models/create_reflection_response.py rename to hindsight-clients/python/hindsight_client_api/models/create_mental_model_response.py index a86a621b..febbb933 100644 --- a/hindsight-clients/python/hindsight_client_api/models/create_reflection_response.py +++ b/hindsight-clients/python/hindsight_client_api/models/create_mental_model_response.py @@ -22,9 +22,9 @@ from typing import Any, ClassVar, Dict, List from typing import Optional, Set from typing_extensions import Self -class CreateReflectionResponse(BaseModel): +class CreateMentalModelResponse(BaseModel): """ - Response model for reflection creation. + Response model for mental model creation. """ # noqa: E501 operation_id: StrictStr = Field(description="Operation ID to track progress") __properties: ClassVar[List[str]] = ["operation_id"] @@ -47,7 +47,7 @@ class CreateReflectionResponse(BaseModel): @classmethod def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of CreateReflectionResponse from a JSON string""" + """Create an instance of CreateMentalModelResponse from a JSON string""" return cls.from_dict(json.loads(json_str)) def to_dict(self) -> Dict[str, Any]: @@ -72,7 +72,7 @@ class CreateReflectionResponse(BaseModel): @classmethod def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of CreateReflectionResponse from a dict""" + """Create an instance of CreateMentalModelResponse from a dict""" if obj is None: return None diff --git a/hindsight-clients/python/hindsight_client_api/models/features_info.py b/hindsight-clients/python/hindsight_client_api/models/features_info.py index c98d722f..62bf344d 100644 --- a/hindsight-clients/python/hindsight_client_api/models/features_info.py +++ b/hindsight-clients/python/hindsight_client_api/models/features_info.py @@ -26,10 +26,10 @@ class FeaturesInfo(BaseModel): """ Feature flags indicating which capabilities are enabled. """ # noqa: E501 - mental_models: StrictBool = Field(description="Whether mental models (auto-consolidation) are enabled") + observations: StrictBool = Field(description="Whether observations (auto-consolidation) are enabled") mcp: StrictBool = Field(description="Whether MCP (Model Context Protocol) server is enabled") worker: StrictBool = Field(description="Whether the background worker is enabled") - __properties: ClassVar[List[str]] = ["mental_models", "mcp", "worker"] + __properties: ClassVar[List[str]] = ["observations", "mcp", "worker"] model_config = ConfigDict( populate_by_name=True, @@ -82,7 +82,7 @@ class FeaturesInfo(BaseModel): return cls.model_validate(obj) _obj = cls.model_validate({ - "mental_models": obj.get("mental_models"), + "observations": obj.get("observations"), "mcp": obj.get("mcp"), "worker": obj.get("worker") }) diff --git a/hindsight-clients/python/hindsight_client_api/models/reflection_list_response.py b/hindsight-clients/python/hindsight_client_api/models/mental_model_list_response.py similarity index 83% rename from hindsight-clients/python/hindsight_client_api/models/reflection_list_response.py rename to hindsight-clients/python/hindsight_client_api/models/mental_model_list_response.py index bd65796c..af4c7161 100644 --- a/hindsight-clients/python/hindsight_client_api/models/reflection_list_response.py +++ b/hindsight-clients/python/hindsight_client_api/models/mental_model_list_response.py @@ -19,15 +19,15 @@ import json from pydantic import BaseModel, ConfigDict from typing import Any, ClassVar, Dict, List -from hindsight_client_api.models.reflection_response import ReflectionResponse +from hindsight_client_api.models.mental_model_response import MentalModelResponse from typing import Optional, Set from typing_extensions import Self -class ReflectionListResponse(BaseModel): +class MentalModelListResponse(BaseModel): """ - Response model for listing reflections. + Response model for listing mental models. """ # noqa: E501 - items: List[ReflectionResponse] + items: List[MentalModelResponse] __properties: ClassVar[List[str]] = ["items"] model_config = ConfigDict( @@ -48,7 +48,7 @@ class ReflectionListResponse(BaseModel): @classmethod def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of ReflectionListResponse from a JSON string""" + """Create an instance of MentalModelListResponse from a JSON string""" return cls.from_dict(json.loads(json_str)) def to_dict(self) -> Dict[str, Any]: @@ -80,7 +80,7 @@ class ReflectionListResponse(BaseModel): @classmethod def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of ReflectionListResponse from a dict""" + """Create an instance of MentalModelListResponse from a dict""" if obj is None: return None @@ -88,7 +88,7 @@ class ReflectionListResponse(BaseModel): return cls.model_validate(obj) _obj = cls.model_validate({ - "items": [ReflectionResponse.from_dict(_item) for _item in obj["items"]] if obj.get("items") is not None else None + "items": [MentalModelResponse.from_dict(_item) for _item in obj["items"]] if obj.get("items") is not None else None }) return _obj diff --git a/hindsight-clients/python/hindsight_client_api/models/reflection_response.py b/hindsight-clients/python/hindsight_client_api/models/mental_model_response.py similarity index 93% rename from hindsight-clients/python/hindsight_client_api/models/reflection_response.py rename to hindsight-clients/python/hindsight_client_api/models/mental_model_response.py index 4632c7c5..656f727f 100644 --- a/hindsight-clients/python/hindsight_client_api/models/reflection_response.py +++ b/hindsight-clients/python/hindsight_client_api/models/mental_model_response.py @@ -22,9 +22,9 @@ from typing import Any, ClassVar, Dict, List, Optional from typing import Optional, Set from typing_extensions import Self -class ReflectionResponse(BaseModel): +class MentalModelResponse(BaseModel): """ - Response model for a reflection. + Response model for a mental model (stored reflect response). """ # noqa: E501 id: StrictStr bank_id: StrictStr @@ -55,7 +55,7 @@ class ReflectionResponse(BaseModel): @classmethod def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of ReflectionResponse from a JSON string""" + """Create an instance of MentalModelResponse from a JSON string""" return cls.from_dict(json.loads(json_str)) def to_dict(self) -> Dict[str, Any]: @@ -95,7 +95,7 @@ class ReflectionResponse(BaseModel): @classmethod def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of ReflectionResponse from a dict""" + """Create an instance of MentalModelResponse from a dict""" if obj is None: return None diff --git a/hindsight-clients/python/hindsight_client_api/models/reflect_mental_model.py b/hindsight-clients/python/hindsight_client_api/models/reflect_mental_model.py deleted file mode 100644 index 076a3556..00000000 --- a/hindsight-clients/python/hindsight_client_api/models/reflect_mental_model.py +++ /dev/null @@ -1,100 +0,0 @@ -# coding: utf-8 - -""" - Hindsight HTTP API - - HTTP API for Hindsight - - The version of the OpenAPI document: 0.1.0 - Generated by OpenAPI Generator (https://openapi-generator.tech) - - Do not edit the class manually. -""" # noqa: E501 - - -from __future__ import annotations -import pprint -import re # noqa: F401 -import json - -from pydantic import BaseModel, ConfigDict, Field, StrictStr -from typing import Any, ClassVar, Dict, List, Optional -from typing import Optional, Set -from typing_extensions import Self - -class ReflectMentalModel(BaseModel): - """ - A mental model accessed during reflect. - """ # noqa: E501 - id: StrictStr = Field(description="Mental model ID") - name: StrictStr = Field(description="Mental model name") - type: StrictStr = Field(description="Mental model type: entity, concept, event") - subtype: StrictStr = Field(description="Mental model subtype: structural, emergent, learned, directive") - observations: Optional[List[StrictStr]] = None - __properties: ClassVar[List[str]] = ["id", "name", "type", "subtype", "observations"] - - model_config = ConfigDict( - populate_by_name=True, - validate_assignment=True, - protected_namespaces=(), - ) - - - def to_str(self) -> str: - """Returns the string representation of the model using alias""" - return pprint.pformat(self.model_dump(by_alias=True)) - - def to_json(self) -> str: - """Returns the JSON representation of the model using alias""" - # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead - return json.dumps(self.to_dict()) - - @classmethod - def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of ReflectMentalModel from a JSON string""" - return cls.from_dict(json.loads(json_str)) - - def to_dict(self) -> Dict[str, Any]: - """Return the dictionary representation of the model using alias. - - This has the following differences from calling pydantic's - `self.model_dump(by_alias=True)`: - - * `None` is only added to the output dict for nullable fields that - were set at model initialization. Other fields with value `None` - are ignored. - """ - excluded_fields: Set[str] = set([ - ]) - - _dict = self.model_dump( - by_alias=True, - exclude=excluded_fields, - exclude_none=True, - ) - # set to None if observations (nullable) is None - # and model_fields_set contains the field - if self.observations is None and "observations" in self.model_fields_set: - _dict['observations'] = None - - return _dict - - @classmethod - def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of ReflectMentalModel from a dict""" - if obj is None: - return None - - if not isinstance(obj, dict): - return cls.model_validate(obj) - - _obj = cls.model_validate({ - "id": obj.get("id"), - "name": obj.get("name"), - "type": obj.get("type"), - "subtype": obj.get("subtype"), - "observations": obj.get("observations") - }) - return _obj - - diff --git a/hindsight-clients/python/hindsight_client_api/models/reflect_trace.py b/hindsight-clients/python/hindsight_client_api/models/reflect_trace.py index d738a013..591f0750 100644 --- a/hindsight-clients/python/hindsight_client_api/models/reflect_trace.py +++ b/hindsight-clients/python/hindsight_client_api/models/reflect_trace.py @@ -20,7 +20,6 @@ import json from pydantic import BaseModel, ConfigDict, Field from typing import Any, ClassVar, Dict, List, Optional from hindsight_client_api.models.reflect_llm_call import ReflectLLMCall -from hindsight_client_api.models.reflect_mental_model import ReflectMentalModel from hindsight_client_api.models.reflect_tool_call import ReflectToolCall from typing import Optional, Set from typing_extensions import Self @@ -31,8 +30,7 @@ class ReflectTrace(BaseModel): """ # noqa: E501 tool_calls: Optional[List[ReflectToolCall]] = Field(default=None, description="Tool calls made during reflection") llm_calls: Optional[List[ReflectLLMCall]] = Field(default=None, description="LLM calls made during reflection") - mental_models: Optional[List[ReflectMentalModel]] = Field(default=None, description="Mental models used during reflection (includes directives with subtype='directive')") - __properties: ClassVar[List[str]] = ["tool_calls", "llm_calls", "mental_models"] + __properties: ClassVar[List[str]] = ["tool_calls", "llm_calls"] model_config = ConfigDict( populate_by_name=True, @@ -87,13 +85,6 @@ class ReflectTrace(BaseModel): if _item_llm_calls: _items.append(_item_llm_calls.to_dict()) _dict['llm_calls'] = _items - # override the default output from pydantic by calling `to_dict()` of each item in mental_models (list) - _items = [] - if self.mental_models: - for _item_mental_models in self.mental_models: - if _item_mental_models: - _items.append(_item_mental_models.to_dict()) - _dict['mental_models'] = _items return _dict @classmethod @@ -107,8 +98,7 @@ class ReflectTrace(BaseModel): _obj = cls.model_validate({ "tool_calls": [ReflectToolCall.from_dict(_item) for _item in obj["tool_calls"]] if obj.get("tool_calls") is not None else None, - "llm_calls": [ReflectLLMCall.from_dict(_item) for _item in obj["llm_calls"]] if obj.get("llm_calls") is not None else None, - "mental_models": [ReflectMentalModel.from_dict(_item) for _item in obj["mental_models"]] if obj.get("mental_models") is not None else None + "llm_calls": [ReflectLLMCall.from_dict(_item) for _item in obj["llm_calls"]] if obj.get("llm_calls") is not None else None }) return _obj diff --git a/hindsight-clients/python/hindsight_client_api/models/update_reflection_request.py b/hindsight-clients/python/hindsight_client_api/models/update_mental_model_request.py similarity index 90% rename from hindsight-clients/python/hindsight_client_api/models/update_reflection_request.py rename to hindsight-clients/python/hindsight_client_api/models/update_mental_model_request.py index 9017521b..0aca2417 100644 --- a/hindsight-clients/python/hindsight_client_api/models/update_reflection_request.py +++ b/hindsight-clients/python/hindsight_client_api/models/update_mental_model_request.py @@ -22,9 +22,9 @@ from typing import Any, ClassVar, Dict, List, Optional from typing import Optional, Set from typing_extensions import Self -class UpdateReflectionRequest(BaseModel): +class UpdateMentalModelRequest(BaseModel): """ - Request model for updating a reflection. + Request model for updating a mental model. """ # noqa: E501 name: Optional[StrictStr] = None __properties: ClassVar[List[str]] = ["name"] @@ -47,7 +47,7 @@ class UpdateReflectionRequest(BaseModel): @classmethod def from_json(cls, json_str: str) -> Optional[Self]: - """Create an instance of UpdateReflectionRequest from a JSON string""" + """Create an instance of UpdateMentalModelRequest from a JSON string""" return cls.from_dict(json.loads(json_str)) def to_dict(self) -> Dict[str, Any]: @@ -77,7 +77,7 @@ class UpdateReflectionRequest(BaseModel): @classmethod def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: - """Create an instance of UpdateReflectionRequest from a dict""" + """Create an instance of UpdateMentalModelRequest from a dict""" if obj is None: return None diff --git a/hindsight-clients/typescript/generated/sdk.gen.ts b/hindsight-clients/typescript/generated/sdk.gen.ts index 3236dac6..242c6682 100644 --- a/hindsight-clients/typescript/generated/sdk.gen.ts +++ b/hindsight-clients/typescript/generated/sdk.gen.ts @@ -12,18 +12,18 @@ import type { ClearBankMemoriesData, ClearBankMemoriesErrors, ClearBankMemoriesResponses, - ClearMentalModelsData, - ClearMentalModelsErrors, - ClearMentalModelsResponses, + ClearObservationsData, + ClearObservationsErrors, + ClearObservationsResponses, CreateDirectiveData, CreateDirectiveErrors, CreateDirectiveResponses, + CreateMentalModelData, + CreateMentalModelErrors, + CreateMentalModelResponses, CreateOrUpdateBankData, CreateOrUpdateBankErrors, CreateOrUpdateBankResponses, - CreateReflectionData, - CreateReflectionErrors, - CreateReflectionResponses, DeleteBankData, DeleteBankErrors, DeleteBankResponses, @@ -33,9 +33,9 @@ import type { DeleteDocumentData, DeleteDocumentErrors, DeleteDocumentResponses, - DeleteReflectionData, - DeleteReflectionErrors, - DeleteReflectionResponses, + DeleteMentalModelData, + DeleteMentalModelErrors, + DeleteMentalModelResponses, GetAgentStatsData, GetAgentStatsErrors, GetAgentStatsResponses, @@ -60,12 +60,12 @@ import type { GetMemoryData, GetMemoryErrors, GetMemoryResponses, + GetMentalModelData, + GetMentalModelErrors, + GetMentalModelResponses, GetOperationStatusData, GetOperationStatusErrors, GetOperationStatusResponses, - GetReflectionData, - GetReflectionErrors, - GetReflectionResponses, GetVersionData, GetVersionResponses, HealthEndpointHealthGetData, @@ -85,12 +85,12 @@ import type { ListMemoriesData, ListMemoriesErrors, ListMemoriesResponses, + ListMentalModelsData, + ListMentalModelsErrors, + ListMentalModelsResponses, ListOperationsData, ListOperationsErrors, ListOperationsResponses, - ListReflectionsData, - ListReflectionsErrors, - ListReflectionsResponses, ListTagsData, ListTagsErrors, ListTagsResponses, @@ -102,9 +102,9 @@ import type { ReflectData, ReflectErrors, ReflectResponses, - RefreshReflectionData, - RefreshReflectionErrors, - RefreshReflectionResponses, + RefreshMentalModelData, + RefreshMentalModelErrors, + RefreshMentalModelResponses, RegenerateEntityObservationsData, RegenerateEntityObservationsErrors, RegenerateEntityObservationsResponses, @@ -123,9 +123,9 @@ import type { UpdateDirectiveData, UpdateDirectiveErrors, UpdateDirectiveResponses, - UpdateReflectionData, - UpdateReflectionErrors, - UpdateReflectionResponses, + UpdateMentalModelData, + UpdateMentalModelErrors, + UpdateMentalModelResponses, } from "./types.gen"; export type Options< @@ -363,33 +363,33 @@ export const regenerateEntityObservations = < }); /** - * List reflections + * List mental models * * List user-curated living documents that stay current. */ -export const listReflections = ( - options: Options, +export const listMentalModels = ( + options: Options, ) => (options.client ?? client).get< - ListReflectionsResponses, - ListReflectionsErrors, + ListMentalModelsResponses, + ListMentalModelsErrors, ThrowOnError - >({ url: "/v1/default/banks/{bank_id}/reflections", ...options }); + >({ url: "/v1/default/banks/{bank_id}/mental-models", ...options }); /** - * Create reflection + * Create mental model * - * Create a reflection by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. + * Create a mental model by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status. */ -export const createReflection = ( - options: Options, +export const createMentalModel = ( + options: Options, ) => (options.client ?? client).post< - CreateReflectionResponses, - CreateReflectionErrors, + CreateMentalModelResponses, + CreateMentalModelErrors, ThrowOnError >({ - url: "/v1/default/banks/{bank_id}/reflections", + url: "/v1/default/banks/{bank_id}/mental-models", ...options, headers: { "Content-Type": "application/json", @@ -398,53 +398,53 @@ export const createReflection = ( }); /** - * Delete reflection + * Delete mental model * - * Delete a reflection. + * Delete a mental model. */ -export const deleteReflection = ( - options: Options, +export const deleteMentalModel = ( + options: Options, ) => (options.client ?? client).delete< - DeleteReflectionResponses, - DeleteReflectionErrors, + DeleteMentalModelResponses, + DeleteMentalModelErrors, ThrowOnError >({ - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}", + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", ...options, }); /** - * Get reflection + * Get mental model * - * Get a specific reflection by ID. + * Get a specific mental model by ID. */ -export const getReflection = ( - options: Options, +export const getMentalModel = ( + options: Options, ) => (options.client ?? client).get< - GetReflectionResponses, - GetReflectionErrors, + GetMentalModelResponses, + GetMentalModelErrors, ThrowOnError >({ - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}", + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", ...options, }); /** - * Update reflection + * Update mental model * - * Update a reflection's name. + * Update a mental model's name. */ -export const updateReflection = ( - options: Options, +export const updateMentalModel = ( + options: Options, ) => (options.client ?? client).patch< - UpdateReflectionResponses, - UpdateReflectionErrors, + UpdateMentalModelResponses, + UpdateMentalModelErrors, ThrowOnError >({ - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}", + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}", ...options, headers: { "Content-Type": "application/json", @@ -453,19 +453,19 @@ export const updateReflection = ( }); /** - * Refresh reflection + * Refresh mental model * * Submit an async task to re-run the source query through reflect and update the content. */ -export const refreshReflection = ( - options: Options, +export const refreshMentalModel = ( + options: Options, ) => (options.client ?? client).post< - RefreshReflectionResponses, - RefreshReflectionErrors, + RefreshMentalModelResponses, + RefreshMentalModelErrors, ThrowOnError >({ - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh", + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh", ...options, }); @@ -799,23 +799,23 @@ export const createOrUpdateBank = ( }); /** - * Clear all mental models + * Clear all observations * - * Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge. + * Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge. */ -export const clearMentalModels = ( - options: Options, +export const clearObservations = ( + options: Options, ) => (options.client ?? client).delete< - ClearMentalModelsResponses, - ClearMentalModelsErrors, + ClearObservationsResponses, + ClearObservationsErrors, ThrowOnError - >({ url: "/v1/default/banks/{bank_id}/mental-models", ...options }); + >({ url: "/v1/default/banks/{bank_id}/observations", ...options }); /** * Trigger consolidation * - * Run memory consolidation to create/update mental models from recent memories. + * Run memory consolidation to create/update observations from recent memories. */ export const triggerConsolidation = ( options: Options, diff --git a/hindsight-clients/typescript/generated/types.gen.ts b/hindsight-clients/typescript/generated/types.gen.ts index a730f1d1..4cce498f 100644 --- a/hindsight-clients/typescript/generated/types.gen.ts +++ b/hindsight-clients/typescript/generated/types.gen.ts @@ -194,15 +194,15 @@ export type BankStatsResponse = { /** * Pending Consolidation * - * Number of memories not yet processed into mental models + * Number of memories not yet processed into observations */ pending_consolidation?: number; /** - * Total Mental Models + * Total Observations * - * Total number of mental models + * Total number of observations */ - total_mental_models?: number; + total_observations?: number; }; /** @@ -388,15 +388,15 @@ export type CreateDirectiveRequest = { }; /** - * CreateReflectionRequest + * CreateMentalModelRequest * - * Request model for creating a reflection. + * Request model for creating a mental model. */ -export type CreateReflectionRequest = { +export type CreateMentalModelRequest = { /** * Name * - * Human-readable name for the reflection + * Human-readable name for the mental model */ name: string; /** @@ -420,11 +420,11 @@ export type CreateReflectionRequest = { }; /** - * CreateReflectionResponse + * CreateMentalModelResponse * - * Response model for reflection creation. + * Response model for mental model creation. */ -export type CreateReflectionResponse = { +export type CreateMentalModelResponse = { /** * Operation Id * @@ -783,11 +783,11 @@ export type FactsIncludeOptions = { */ export type FeaturesInfo = { /** - * Mental Models + * Observations * - * Whether mental models (auto-consolidation) are enabled + * Whether observations (auto-consolidation) are enabled */ - mental_models: boolean; + observations: boolean; /** * Mcp * @@ -982,6 +982,66 @@ export type MemoryItem = { tags?: Array | null; }; +/** + * MentalModelListResponse + * + * Response model for listing mental models. + */ +export type MentalModelListResponse = { + /** + * Items + */ + items: Array; +}; + +/** + * MentalModelResponse + * + * Response model for a mental model (stored reflect response). + */ +export type MentalModelResponse = { + /** + * Id + */ + id: string; + /** + * Bank Id + */ + bank_id: string; + /** + * Name + */ + name: string; + /** + * Source Query + */ + source_query: string; + /** + * Content + */ + content: string; + /** + * Tags + */ + tags?: Array; + /** + * Last Refreshed At + */ + last_refreshed_at?: string | null; + /** + * Created At + */ + created_at?: string | null; + /** + * Reflect Response + * + * Full reflect API response payload including based_on facts and observations + */ + reflect_response?: { + [key: string]: unknown; + } | null; +}; + /** * OperationResponse * @@ -1095,7 +1155,7 @@ export type RecallRequest = { /** * Types * - * List of fact types to recall: 'world', 'experience', 'mental_model'. Defaults to world and experience if not specified. Note: 'opinion' is accepted but ignored (opinions are excluded from recall). + * List of fact types to recall: 'world', 'experience', 'observation'. Defaults to world and experience if not specified. Note: 'opinion' is accepted but ignored (opinions are excluded from recall). */ types?: Array | null; budget?: Budget; @@ -1305,44 +1365,6 @@ export type ReflectLlmCall = { duration_ms: number; }; -/** - * ReflectMentalModel - * - * A mental model accessed during reflect. - */ -export type ReflectMentalModel = { - /** - * Id - * - * Mental model ID - */ - id: string; - /** - * Name - * - * Mental model name - */ - name: string; - /** - * Type - * - * Mental model type: entity, concept, event - */ - type: string; - /** - * Subtype - * - * Mental model subtype: structural, emergent, learned, directive - */ - subtype: string; - /** - * Observations - * - * Observations for directive mental models (subtype='directive') - */ - observations?: Array | null; -}; - /** * ReflectRequest * @@ -1486,72 +1508,6 @@ export type ReflectTrace = { * LLM calls made during reflection */ llm_calls?: Array; - /** - * Mental Models - * - * Mental models used during reflection (includes directives with subtype='directive') - */ - mental_models?: Array; -}; - -/** - * ReflectionListResponse - * - * Response model for listing reflections. - */ -export type ReflectionListResponse = { - /** - * Items - */ - items: Array; -}; - -/** - * ReflectionResponse - * - * Response model for a reflection. - */ -export type ReflectionResponse = { - /** - * Id - */ - id: string; - /** - * Bank Id - */ - bank_id: string; - /** - * Name - */ - name: string; - /** - * Source Query - */ - source_query: string; - /** - * Content - */ - content: string; - /** - * Tags - */ - tags?: Array; - /** - * Last Refreshed At - */ - last_refreshed_at?: string | null; - /** - * Created At - */ - created_at?: string | null; - /** - * Reflect Response - * - * Full reflect API response payload including based_on facts and mental_models - */ - reflect_response?: { - [key: string]: unknown; - } | null; }; /** @@ -1725,15 +1681,15 @@ export type UpdateDispositionRequest = { }; /** - * UpdateReflectionRequest + * UpdateMentalModelRequest * - * Request model for updating a reflection. + * Request model for updating a mental model. */ -export type UpdateReflectionRequest = { +export type UpdateMentalModelRequest = { /** * Name * - * New name for the reflection + * New name for the mental model */ name?: string | null; }; @@ -2229,7 +2185,7 @@ export type RegenerateEntityObservationsResponses = { export type RegenerateEntityObservationsResponse = RegenerateEntityObservationsResponses[keyof RegenerateEntityObservationsResponses]; -export type ListReflectionsData = { +export type ListMentalModelsData = { body?: never; headers?: { /** @@ -2265,31 +2221,31 @@ export type ListReflectionsData = { */ offset?: number; }; - url: "/v1/default/banks/{bank_id}/reflections"; + url: "/v1/default/banks/{bank_id}/mental-models"; }; -export type ListReflectionsErrors = { +export type ListMentalModelsErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type ListReflectionsError = - ListReflectionsErrors[keyof ListReflectionsErrors]; +export type ListMentalModelsError = + ListMentalModelsErrors[keyof ListMentalModelsErrors]; -export type ListReflectionsResponses = { +export type ListMentalModelsResponses = { /** * Successful Response */ - 200: ReflectionListResponse; + 200: MentalModelListResponse; }; -export type ListReflectionsResponse = - ListReflectionsResponses[keyof ListReflectionsResponses]; +export type ListMentalModelsResponse = + ListMentalModelsResponses[keyof ListMentalModelsResponses]; -export type CreateReflectionData = { - body: CreateReflectionRequest; +export type CreateMentalModelData = { + body: CreateMentalModelRequest; headers?: { /** * Authorization @@ -2303,30 +2259,30 @@ export type CreateReflectionData = { bank_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/reflections"; + url: "/v1/default/banks/{bank_id}/mental-models"; }; -export type CreateReflectionErrors = { +export type CreateMentalModelErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type CreateReflectionError = - CreateReflectionErrors[keyof CreateReflectionErrors]; +export type CreateMentalModelError = + CreateMentalModelErrors[keyof CreateMentalModelErrors]; -export type CreateReflectionResponses = { +export type CreateMentalModelResponses = { /** * Successful Response */ - 200: CreateReflectionResponse; + 200: CreateMentalModelResponse; }; -export type CreateReflectionResponse2 = - CreateReflectionResponses[keyof CreateReflectionResponses]; +export type CreateMentalModelResponse2 = + CreateMentalModelResponses[keyof CreateMentalModelResponses]; -export type DeleteReflectionData = { +export type DeleteMentalModelData = { body?: never; headers?: { /** @@ -2340,32 +2296,32 @@ export type DeleteReflectionData = { */ bank_id: string; /** - * Reflection Id + * Mental Model Id */ - reflection_id: string; + mental_model_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}"; + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}"; }; -export type DeleteReflectionErrors = { +export type DeleteMentalModelErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type DeleteReflectionError = - DeleteReflectionErrors[keyof DeleteReflectionErrors]; +export type DeleteMentalModelError = + DeleteMentalModelErrors[keyof DeleteMentalModelErrors]; -export type DeleteReflectionResponses = { +export type DeleteMentalModelResponses = { /** * Successful Response */ 200: unknown; }; -export type GetReflectionData = { +export type GetMentalModelData = { body?: never; headers?: { /** @@ -2379,35 +2335,36 @@ export type GetReflectionData = { */ bank_id: string; /** - * Reflection Id + * Mental Model Id */ - reflection_id: string; + mental_model_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}"; + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}"; }; -export type GetReflectionErrors = { +export type GetMentalModelErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type GetReflectionError = GetReflectionErrors[keyof GetReflectionErrors]; +export type GetMentalModelError = + GetMentalModelErrors[keyof GetMentalModelErrors]; -export type GetReflectionResponses = { +export type GetMentalModelResponses = { /** * Successful Response */ - 200: ReflectionResponse; + 200: MentalModelResponse; }; -export type GetReflectionResponse = - GetReflectionResponses[keyof GetReflectionResponses]; +export type GetMentalModelResponse = + GetMentalModelResponses[keyof GetMentalModelResponses]; -export type UpdateReflectionData = { - body: UpdateReflectionRequest; +export type UpdateMentalModelData = { + body: UpdateMentalModelRequest; headers?: { /** * Authorization @@ -2420,35 +2377,35 @@ export type UpdateReflectionData = { */ bank_id: string; /** - * Reflection Id + * Mental Model Id */ - reflection_id: string; + mental_model_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}"; + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}"; }; -export type UpdateReflectionErrors = { +export type UpdateMentalModelErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type UpdateReflectionError = - UpdateReflectionErrors[keyof UpdateReflectionErrors]; +export type UpdateMentalModelError = + UpdateMentalModelErrors[keyof UpdateMentalModelErrors]; -export type UpdateReflectionResponses = { +export type UpdateMentalModelResponses = { /** * Successful Response */ - 200: ReflectionResponse; + 200: MentalModelResponse; }; -export type UpdateReflectionResponse = - UpdateReflectionResponses[keyof UpdateReflectionResponses]; +export type UpdateMentalModelResponse = + UpdateMentalModelResponses[keyof UpdateMentalModelResponses]; -export type RefreshReflectionData = { +export type RefreshMentalModelData = { body?: never; headers?: { /** @@ -2462,33 +2419,33 @@ export type RefreshReflectionData = { */ bank_id: string; /** - * Reflection Id + * Mental Model Id */ - reflection_id: string; + mental_model_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh"; + url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh"; }; -export type RefreshReflectionErrors = { +export type RefreshMentalModelErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type RefreshReflectionError = - RefreshReflectionErrors[keyof RefreshReflectionErrors]; +export type RefreshMentalModelError = + RefreshMentalModelErrors[keyof RefreshMentalModelErrors]; -export type RefreshReflectionResponses = { +export type RefreshMentalModelResponses = { /** * Successful Response */ 200: AsyncOperationSubmitResponse; }; -export type RefreshReflectionResponse = - RefreshReflectionResponses[keyof RefreshReflectionResponses]; +export type RefreshMentalModelResponse = + RefreshMentalModelResponses[keyof RefreshMentalModelResponses]; export type ListDirectivesData = { body?: never; @@ -3304,7 +3261,7 @@ export type CreateOrUpdateBankResponses = { export type CreateOrUpdateBankResponse = CreateOrUpdateBankResponses[keyof CreateOrUpdateBankResponses]; -export type ClearMentalModelsData = { +export type ClearObservationsData = { body?: never; headers?: { /** @@ -3319,28 +3276,28 @@ export type ClearMentalModelsData = { bank_id: string; }; query?: never; - url: "/v1/default/banks/{bank_id}/mental-models"; + url: "/v1/default/banks/{bank_id}/observations"; }; -export type ClearMentalModelsErrors = { +export type ClearObservationsErrors = { /** * Validation Error */ 422: HttpValidationError; }; -export type ClearMentalModelsError = - ClearMentalModelsErrors[keyof ClearMentalModelsErrors]; +export type ClearObservationsError = + ClearObservationsErrors[keyof ClearObservationsErrors]; -export type ClearMentalModelsResponses = { +export type ClearObservationsResponses = { /** * Successful Response */ 200: DeleteResponse; }; -export type ClearMentalModelsResponse = - ClearMentalModelsResponses[keyof ClearMentalModelsResponses]; +export type ClearObservationsResponse = + ClearObservationsResponses[keyof ClearObservationsResponses]; export type TriggerConsolidationData = { body?: never; diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/refresh/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/refresh/route.ts similarity index 50% rename from hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/refresh/route.ts rename to hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/refresh/route.ts index 2a6a2d43..4ea6148c 100644 --- a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/refresh/route.ts +++ b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/refresh/route.ts @@ -4,28 +4,28 @@ const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://loca export async function POST( request: Request, - { params }: { params: Promise<{ bankId: string; reflectionId: string }> } + { params }: { params: Promise<{ bankId: string; mentalModelId: string }> } ) { try { - const { bankId, reflectionId } = await params; + const { bankId, mentalModelId } = await params; - if (!bankId || !reflectionId) { + if (!bankId || !mentalModelId) { return NextResponse.json( - { error: "bank_id and reflection_id are required" }, + { error: "bank_id and mental_model_id are required" }, { status: 400 } ); } const response = await fetch( - `${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections/${reflectionId}/refresh`, + `${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models/${mentalModelId}/refresh`, { method: "POST" } ); if (!response.ok) { const errorText = await response.text(); - console.error("API error refreshing reflection:", errorText); + console.error("API error refreshing mental model:", errorText); return NextResponse.json( - { error: errorText || "Failed to refresh reflection" }, + { error: errorText || "Failed to refresh mental model" }, { status: response.status } ); } @@ -33,7 +33,7 @@ export async function POST( const data = await response.json(); return NextResponse.json(data, { status: 200 }); } catch (error) { - console.error("Error refreshing reflection:", error); - return NextResponse.json({ error: "Failed to refresh reflection" }, { status: 500 }); + console.error("Error refreshing mental model:", error); + return NextResponse.json({ error: "Failed to refresh mental model" }, { status: 500 }); } } diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/route.ts new file mode 100644 index 00000000..8c48f0e5 --- /dev/null +++ b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[mentalModelId]/route.ts @@ -0,0 +1,116 @@ +import { NextResponse } from "next/server"; + +const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ bankId: string; mentalModelId: string }> } +) { + try { + const { bankId, mentalModelId } = await params; + + if (!bankId || !mentalModelId) { + return NextResponse.json( + { error: "bank_id and mental_model_id are required" }, + { status: 400 } + ); + } + + const response = await fetch( + `${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models/${mentalModelId}`, + { method: "GET" } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("API error getting mental model:", errorText); + return NextResponse.json( + { error: "Failed to get mental model" }, + { status: response.status } + ); + } + + const data = await response.json(); + return NextResponse.json(data, { status: 200 }); + } catch (error) { + console.error("Error getting mental model:", error); + return NextResponse.json({ error: "Failed to get mental model" }, { status: 500 }); + } +} + +export async function PATCH( + request: Request, + { params }: { params: Promise<{ bankId: string; mentalModelId: string }> } +) { + try { + const { bankId, mentalModelId } = await params; + + if (!bankId || !mentalModelId) { + return NextResponse.json( + { error: "bank_id and mental_model_id are required" }, + { status: 400 } + ); + } + + const body = await request.json(); + + const response = await fetch( + `${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models/${mentalModelId}`, + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("API error updating mental model:", errorText); + return NextResponse.json( + { error: errorText || "Failed to update mental model" }, + { status: response.status } + ); + } + + const data = await response.json(); + return NextResponse.json(data, { status: 200 }); + } catch (error) { + console.error("Error updating mental model:", error); + return NextResponse.json({ error: "Failed to update mental model" }, { status: 500 }); + } +} + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ bankId: string; mentalModelId: string }> } +) { + try { + const { bankId, mentalModelId } = await params; + + if (!bankId || !mentalModelId) { + return NextResponse.json( + { error: "bank_id and mental_model_id are required" }, + { status: 400 } + ); + } + + const response = await fetch( + `${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models/${mentalModelId}`, + { method: "DELETE" } + ); + + if (!response.ok) { + const errorText = await response.text(); + console.error("API error deleting mental model:", errorText); + return NextResponse.json( + { error: errorText || "Failed to delete mental model" }, + { status: response.status } + ); + } + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error) { + console.error("Error deleting mental model:", error); + return NextResponse.json({ error: "Failed to delete mental model" }, { status: 500 }); + } +} diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/route.ts index d98b6c78..57702d00 100644 --- a/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/route.ts +++ b/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/route.ts @@ -1,54 +1,47 @@ import { NextResponse } from "next/server"; -import { sdk, lowLevelClient } from "@/lib/hindsight-client"; + +const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888"; export async function GET(request: Request, { params }: { params: Promise<{ bankId: string }> }) { try { const { bankId } = await params; + const { searchParams } = new URL(request.url); + const tags = searchParams.getAll("tags"); + const tagsMatch = searchParams.get("tags_match"); if (!bankId) { return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); } - // Note: tags filtering is not supported by the list_memories API endpoint - const response = await sdk.listMemories({ - client: lowLevelClient, - path: { bank_id: bankId }, - query: { - type: "mental_model", - limit: 1000, - }, - }); - - if (response.error) { - console.error("API error listing mental models:", response.error); - return NextResponse.json({ error: "Failed to list mental models" }, { status: 500 }); + const queryParams = new URLSearchParams(); + if (tags.length > 0) { + tags.forEach((t) => queryParams.append("tags", t)); + } + if (tagsMatch) { + queryParams.append("tags_match", tagsMatch); } - // Transform list memories response to mental models format - const items = (response.data?.items || []).map((item) => ({ - id: item.id, - bank_id: bankId, - text: item.text, - proof_count: 1, - history: [], - tags: item.tags || [], - source_memory_ids: [], - source_memories: [], - created_at: item.date, - updated_at: item.date, - })); + const url = `${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models${queryParams.toString() ? `?${queryParams}` : ""}`; + const response = await fetch(url, { method: "GET" }); - return NextResponse.json({ items }, { status: 200 }); + if (!response.ok) { + const errorText = await response.text(); + console.error("API error listing mental models:", errorText); + return NextResponse.json( + { error: "Failed to list mental models" }, + { status: response.status } + ); + } + + const data = await response.json(); + return NextResponse.json(data, { status: 200 }); } catch (error) { console.error("Error listing mental models:", error); return NextResponse.json({ error: "Failed to list mental models" }, { status: 500 }); } } -export async function DELETE( - request: Request, - { params }: { params: Promise<{ bankId: string }> } -) { +export async function POST(request: Request, { params }: { params: Promise<{ bankId: string }> }) { try { const { bankId } = await params; @@ -56,19 +49,28 @@ export async function DELETE( return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); } - const response = await sdk.clearMentalModels({ - client: lowLevelClient, - path: { bank_id: bankId }, + const body = await request.json(); + + const response = await fetch(`${DATAPLANE_URL}/v1/default/banks/${bankId}/mental-models`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), }); - if (response.error) { - console.error("API error clearing mental models:", response.error); - return NextResponse.json({ error: "Failed to clear mental models" }, { status: 500 }); + if (!response.ok) { + const errorText = await response.text(); + console.error("API error creating mental model:", errorText); + return NextResponse.json( + { error: errorText || "Failed to create mental model" }, + { status: response.status } + ); } - return NextResponse.json(response.data, { status: 200 }); + const data = await response.json(); + // Returns operation_id - content is generated in background + return NextResponse.json(data, { status: 202 }); } catch (error) { - console.error("Error clearing mental models:", error); - return NextResponse.json({ error: "Failed to clear mental models" }, { status: 500 }); + console.error("Error creating mental model:", error); + return NextResponse.json({ error: "Failed to create mental model" }, { status: 500 }); } } diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[modelId]/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/observations/[modelId]/route.ts similarity index 100% rename from hindsight-control-plane/src/app/api/banks/[bankId]/mental-models/[modelId]/route.ts rename to hindsight-control-plane/src/app/api/banks/[bankId]/observations/[modelId]/route.ts diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/observations/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/observations/route.ts new file mode 100644 index 00000000..225d54b2 --- /dev/null +++ b/hindsight-control-plane/src/app/api/banks/[bankId]/observations/route.ts @@ -0,0 +1,74 @@ +import { NextResponse } from "next/server"; +import { sdk, lowLevelClient } from "@/lib/hindsight-client"; + +export async function GET(request: Request, { params }: { params: Promise<{ bankId: string }> }) { + try { + const { bankId } = await params; + + if (!bankId) { + return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); + } + + // Note: tags filtering is not supported by the list_memories API endpoint + const response = await sdk.listMemories({ + client: lowLevelClient, + path: { bank_id: bankId }, + query: { + type: "observation", + limit: 1000, + }, + }); + + if (response.error) { + console.error("API error listing observations:", response.error); + return NextResponse.json({ error: "Failed to list observations" }, { status: 500 }); + } + + // Transform list memories response to observations format + const items = (response.data?.items || []).map((item) => ({ + id: item.id, + bank_id: bankId, + text: item.text, + proof_count: 1, + history: [], + tags: item.tags || [], + source_memory_ids: [], + source_memories: [], + created_at: item.date, + updated_at: item.date, + })); + + return NextResponse.json({ items }, { status: 200 }); + } catch (error) { + console.error("Error listing observations:", error); + return NextResponse.json({ error: "Failed to list observations" }, { status: 500 }); + } +} + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ bankId: string }> } +) { + try { + const { bankId } = await params; + + if (!bankId) { + return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); + } + + const response = await sdk.clearObservations({ + client: lowLevelClient, + path: { bank_id: bankId }, + }); + + if (response.error) { + console.error("API error clearing observations:", response.error); + return NextResponse.json({ error: "Failed to clear observations" }, { status: 500 }); + } + + return NextResponse.json(response.data, { status: 200 }); + } catch (error) { + console.error("Error clearing observations:", error); + return NextResponse.json({ error: "Failed to clear observations" }, { status: 500 }); + } +} diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/route.ts deleted file mode 100644 index e146788a..00000000 --- a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/[reflectionId]/route.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { NextResponse } from "next/server"; - -const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888"; - -export async function GET( - request: Request, - { params }: { params: Promise<{ bankId: string; reflectionId: string }> } -) { - try { - const { bankId, reflectionId } = await params; - - if (!bankId || !reflectionId) { - return NextResponse.json( - { error: "bank_id and reflection_id are required" }, - { status: 400 } - ); - } - - const response = await fetch( - `${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections/${reflectionId}`, - { method: "GET" } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error("API error getting reflection:", errorText); - return NextResponse.json({ error: "Failed to get reflection" }, { status: response.status }); - } - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); - } catch (error) { - console.error("Error getting reflection:", error); - return NextResponse.json({ error: "Failed to get reflection" }, { status: 500 }); - } -} - -export async function PATCH( - request: Request, - { params }: { params: Promise<{ bankId: string; reflectionId: string }> } -) { - try { - const { bankId, reflectionId } = await params; - - if (!bankId || !reflectionId) { - return NextResponse.json( - { error: "bank_id and reflection_id are required" }, - { status: 400 } - ); - } - - const body = await request.json(); - - const response = await fetch( - `${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections/${reflectionId}`, - { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error("API error updating reflection:", errorText); - return NextResponse.json( - { error: errorText || "Failed to update reflection" }, - { status: response.status } - ); - } - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); - } catch (error) { - console.error("Error updating reflection:", error); - return NextResponse.json({ error: "Failed to update reflection" }, { status: 500 }); - } -} - -export async function DELETE( - request: Request, - { params }: { params: Promise<{ bankId: string; reflectionId: string }> } -) { - try { - const { bankId, reflectionId } = await params; - - if (!bankId || !reflectionId) { - return NextResponse.json( - { error: "bank_id and reflection_id are required" }, - { status: 400 } - ); - } - - const response = await fetch( - `${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections/${reflectionId}`, - { method: "DELETE" } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error("API error deleting reflection:", errorText); - return NextResponse.json( - { error: errorText || "Failed to delete reflection" }, - { status: response.status } - ); - } - - return NextResponse.json({ success: true }, { status: 200 }); - } catch (error) { - console.error("Error deleting reflection:", error); - return NextResponse.json({ error: "Failed to delete reflection" }, { status: 500 }); - } -} diff --git a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/route.ts b/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/route.ts deleted file mode 100644 index 89869976..00000000 --- a/hindsight-control-plane/src/app/api/banks/[bankId]/reflections/route.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { NextResponse } from "next/server"; - -const DATAPLANE_URL = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888"; - -export async function GET(request: Request, { params }: { params: Promise<{ bankId: string }> }) { - try { - const { bankId } = await params; - const { searchParams } = new URL(request.url); - const tags = searchParams.getAll("tags"); - const tagsMatch = searchParams.get("tags_match"); - - if (!bankId) { - return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); - } - - const queryParams = new URLSearchParams(); - if (tags.length > 0) { - tags.forEach((t) => queryParams.append("tags", t)); - } - if (tagsMatch) { - queryParams.append("tags_match", tagsMatch); - } - - const url = `${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections${queryParams.toString() ? `?${queryParams}` : ""}`; - const response = await fetch(url, { method: "GET" }); - - if (!response.ok) { - const errorText = await response.text(); - console.error("API error listing reflections:", errorText); - return NextResponse.json( - { error: "Failed to list reflections" }, - { status: response.status } - ); - } - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); - } catch (error) { - console.error("Error listing reflections:", error); - return NextResponse.json({ error: "Failed to list reflections" }, { status: 500 }); - } -} - -export async function POST(request: Request, { params }: { params: Promise<{ bankId: string }> }) { - try { - const { bankId } = await params; - - if (!bankId) { - return NextResponse.json({ error: "bank_id is required" }, { status: 400 }); - } - - const body = await request.json(); - - const response = await fetch(`${DATAPLANE_URL}/v1/default/banks/${bankId}/reflections`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error("API error creating reflection:", errorText); - return NextResponse.json( - { error: errorText || "Failed to create reflection" }, - { status: response.status } - ); - } - - const data = await response.json(); - // Returns operation_id - content is generated in background - return NextResponse.json(data, { status: 202 }); - } catch (error) { - console.error("Error creating reflection:", error); - return NextResponse.json({ error: "Failed to create reflection" }, { status: 500 }); - } -} diff --git a/hindsight-control-plane/src/app/banks/[bankId]/page.tsx b/hindsight-control-plane/src/app/banks/[bankId]/page.tsx index e936e761..960b8ef9 100644 --- a/hindsight-control-plane/src/app/banks/[bankId]/page.tsx +++ b/hindsight-control-plane/src/app/banks/[bankId]/page.tsx @@ -9,11 +9,11 @@ import { EntitiesView } from "@/components/entities-view"; import { ThinkView } from "@/components/think-view"; import { SearchDebugView } from "@/components/search-debug-view"; import { BankProfileView } from "@/components/bank-profile-view"; -import { ReflectionsView } from "@/components/reflections-view"; +import { MentalModelsView } from "@/components/mental-models-view"; import { useFeatures } from "@/lib/features-context"; type NavItem = "recall" | "reflect" | "data" | "documents" | "entities" | "profile"; -type DataSubTab = "world" | "experience" | "models" | "reflections"; +type DataSubTab = "world" | "experience" | "observations" | "mental-models"; export default function BankPage() { const params = useParams(); @@ -24,7 +24,7 @@ export default function BankPage() { const bankId = params.bankId as string; const view = (searchParams.get("view") || "profile") as NavItem; const subTab = (searchParams.get("subTab") || "world") as DataSubTab; - const mentalModelsEnabled = features?.mental_models ?? false; + const observationsEnabled = features?.observations ?? false; const handleTabChange = (tab: NavItem) => { router.push(`/banks/${bankId}?view=${tab}`); @@ -115,33 +115,33 @@ export default function BankPage() { )} + - @@ -151,9 +151,9 @@ export default function BankPage() {
{subTab === "world" && } {subTab === "experience" && } - {subTab === "models" && - (mentalModelsEnabled ? ( - + {subTab === "observations" && + (observationsEnabled ? ( + ) : (
@@ -174,18 +174,18 @@ export default function BankPage() {

- Mental Models Not Enabled + Observations Not Enabled

- Mental models consolidation is disabled on this server. Set{" "} + Observations consolidation is disabled on this server. Set{" "} - HINDSIGHT_API_ENABLE_MENTAL_MODELS=true + HINDSIGHT_API_ENABLE_OBSERVATIONS=true {" "} to enable.

))} - {subTab === "reflections" && } + {subTab === "mental-models" && }
)} diff --git a/hindsight-control-plane/src/components/bank-profile-view.tsx b/hindsight-control-plane/src/components/bank-profile-view.tsx index e21529e5..2934aa50 100644 --- a/hindsight-control-plane/src/components/bank-profile-view.tsx +++ b/hindsight-control-plane/src/components/bank-profile-view.tsx @@ -214,7 +214,7 @@ export function BankProfileView() { const router = useRouter(); const { currentBank, setCurrentBank, loadBanks } = useBank(); const { features } = useFeatures(); - const mentalModelsEnabled = features?.mental_models ?? false; + const observationsEnabled = features?.observations ?? false; const [profile, setProfile] = useState(null); const [stats, setStats] = useState(null); const [operations, setOperations] = useState([]); @@ -243,9 +243,9 @@ export function BankProfileView() { const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [isDeleting, setIsDeleting] = useState(false); - // Clear mental models state - const [showClearMentalModelsDialog, setShowClearMentalModelsDialog] = useState(false); - const [isClearingMentalModels, setIsClearingMentalModels] = useState(false); + // Clear observations state + const [showClearObservationsDialog, setShowClearObservationsDialog] = useState(false); + const [isClearingObservations, setIsClearingObservations] = useState(false); // Consolidation state const [isConsolidating, setIsConsolidating] = useState(false); @@ -372,20 +372,20 @@ export function BankProfileView() { } }; - const handleClearMentalModels = async () => { + const handleClearObservations = async () => { if (!currentBank) return; - setIsClearingMentalModels(true); + setIsClearingObservations(true); try { - const result = await client.clearMentalModels(currentBank); - setShowClearMentalModelsDialog(false); + const result = await client.clearObservations(currentBank); + setShowClearObservationsDialog(false); await loadData(); - alert(result.message || "Mental models cleared successfully"); + alert(result.message || "Observations cleared successfully"); } catch (error) { - console.error("Error clearing mental models:", error); - alert("Error clearing mental models: " + (error as Error).message); + console.error("Error clearing observations:", error); + alert("Error clearing observations: " + (error as Error).message); } finally { - setIsClearingMentalModels(false); + setIsClearingObservations(false); } }; @@ -537,8 +537,8 @@ export function BankProfileView() { {isConsolidating ? ( @@ -546,19 +546,19 @@ export function BankProfileView() { )} {isConsolidating ? "Consolidating..." : "Run Consolidation"} - {!mentalModelsEnabled && ( + {!observationsEnabled && ( Off )} setShowClearMentalModelsDialog(true)} - disabled={!mentalModelsEnabled} + onClick={() => setShowClearObservationsDialog(true)} + disabled={!observationsEnabled} className="text-amber-600 dark:text-amber-400 focus:text-amber-700 dark:focus:text-amber-300" - title={!mentalModelsEnabled ? "Mental models feature is not enabled" : undefined} + title={!observationsEnabled ? "Observations feature is not enabled" : undefined} > - Clear Mental Models - {!mentalModelsEnabled && ( + Clear Observations + {!observationsEnabled && ( Off )} @@ -664,26 +664,26 @@ export function BankProfileView() {

- Mental Models - {!mentalModelsEnabled && (Off)} + Observations + {!observationsEnabled && (Off)}

- {mentalModelsEnabled ? stats.total_mental_models || 0 : "—"} + {observationsEnabled ? stats.total_mental_models || 0 : "—"}

@@ -1024,35 +1024,35 @@ export function BankProfileView() { - {/* Clear Mental Models Confirmation Dialog */} - + {/* Clear Observations Confirmation Dialog */} + - Clear Mental Models + Clear Observations

- Are you sure you want to clear all mental models for{" "} + Are you sure you want to clear all observations for{" "} {currentBank}?

- This will delete all consolidated knowledge. Mental models will be regenerated the + This will delete all consolidated knowledge. Observations will be regenerated the next time consolidation runs.

{stats && stats.total_mental_models > 0 && ( -

This will delete {stats.total_mental_models} mental models.

+

This will delete {stats.total_mental_models} observations.

)}
- Cancel + Cancel - {isClearingMentalModels ? ( + {isClearingObservations ? ( <> Clearing... @@ -1060,7 +1060,7 @@ export function BankProfileView() { ) : ( <> - Clear Mental Models + Clear Observations )} diff --git a/hindsight-control-plane/src/components/data-view.tsx b/hindsight-control-plane/src/components/data-view.tsx index 0ae94d29..d57f74ce 100644 --- a/hindsight-control-plane/src/components/data-view.tsx +++ b/hindsight-control-plane/src/components/data-view.tsx @@ -36,7 +36,7 @@ import { Switch } from "@/components/ui/switch"; import { MemoryDetailPanel } from "./memory-detail-panel"; import { Graph2D, convertHindsightGraphData, GraphNode } from "./graph-2d"; -type FactType = "world" | "experience" | "mental_model"; +type FactType = "world" | "experience" | "observation"; type ViewMode = "graph" | "table" | "timeline"; interface DataViewProps { @@ -117,8 +117,8 @@ export function DataView({ factType }: DataViewProps) { }); setData(graphData); - // Fetch consolidation status for mental models - if (factType === "mental_model") { + // Fetch consolidation status for observations + if (factType === "observation") { const stats: any = await client.getBankStats(currentBank); setConsolidationStatus({ pending_consolidation: stats.pending_consolidation || 0, @@ -307,8 +307,8 @@ export function DataView({ factType }: DataViewProps) { )}
- {/* Consolidation status for mental models */} - {factType === "mental_model" && consolidationStatus && ( + {/* Consolidation status for observations */} + {factType === "observation" && consolidationStatus && (
- {factType === "mental_model" ? "Mental Model" : "Memory"} + {factType === "observation" ? "Observation" : "Memory"} - {factType === "mental_model" ? ( + {factType === "observation" ? ( <> Sources Created @@ -676,7 +676,7 @@ export function DataView({ factType }: DataViewProps) {
)} - {factType === "mental_model" ? ( + {factType === "observation" ? ( <> {row.proof_count || 1} diff --git a/hindsight-control-plane/src/components/memory-detail-panel.tsx b/hindsight-control-plane/src/components/memory-detail-panel.tsx index e8a5f5e5..86056078 100644 --- a/hindsight-control-plane/src/components/memory-detail-panel.tsx +++ b/hindsight-control-plane/src/components/memory-detail-panel.tsx @@ -58,8 +58,8 @@ export function MemoryDetailPanel({ // Use full memory data if available, otherwise fall back to the partial data passed in const displayMemory = fullMemory || memory; - const isMentalModel = - displayMemory?.fact_type === "mental_model" || displayMemory?.type === "mental_model"; + const isObservation = + displayMemory?.fact_type === "observation" || displayMemory?.type === "observation"; const copyToClipboard = async (text: string) => { try { @@ -127,8 +127,8 @@ export function MemoryDetailPanel({ - {/* Context (not shown for mental models) */} - {displayMemory.context && !isMentalModel && ( + {/* Context (not shown for observations) */} + {displayMemory.context && !isObservation && (
Context @@ -209,7 +209,7 @@ export function MemoryDetailPanel({
)} - {/* Source Memories (for mental models) */} + {/* Source Memories (for observations) */} {displayMemory.source_memories && displayMemory.source_memories.length > 0 && (
diff --git a/hindsight-control-plane/src/components/reflections-view.tsx b/hindsight-control-plane/src/components/mental-models-view.tsx similarity index 68% rename from hindsight-control-plane/src/components/reflections-view.tsx rename to hindsight-control-plane/src/components/mental-models-view.tsx index 8460c4c7..c12f26cd 100644 --- a/hindsight-control-plane/src/components/reflections-view.tsx +++ b/hindsight-control-plane/src/components/mental-models-view.tsx @@ -33,7 +33,6 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, Sparkles, @@ -60,7 +59,7 @@ interface ReflectResponse { based_on: Record; } -interface Reflection { +interface MentalModel { id: string; bank_id: string; name: string; @@ -72,30 +71,31 @@ interface Reflection { reflect_response?: ReflectResponse; } -export function ReflectionsView() { +export function MentalModelsView() { const { currentBank } = useBank(); - const [reflections, setReflections] = useState([]); + const [mentalModels, setMentalModels] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 100; - const [showCreateReflection, setShowCreateReflection] = useState(false); - const [selectedReflection, setSelectedReflection] = useState(null); + const [showCreateMentalModel, setShowCreateMentalModel] = useState(false); + const [selectedMentalModel, setSelectedMentalModel] = useState(null); const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string; } | null>(null); const [deleting, setDeleting] = useState(false); - // Filter reflections based on search query - const filteredReflections = reflections.filter((r) => { + // Filter mental models based on search query + const filteredMentalModels = mentalModels.filter((m) => { if (!searchQuery) return true; const query = searchQuery.toLowerCase(); return ( - r.name.toLowerCase().includes(query) || - r.source_query.toLowerCase().includes(query) || - r.content.toLowerCase().includes(query) + m.id.toLowerCase().includes(query) || + m.name.toLowerCase().includes(query) || + m.source_query.toLowerCase().includes(query) || + m.content.toLowerCase().includes(query) ); }); @@ -104,10 +104,10 @@ export function ReflectionsView() { setLoading(true); try { - const reflectionsData = await client.listReflections(currentBank); - setReflections(reflectionsData.items || []); + const mentalModelsData = await client.listMentalModels(currentBank); + setMentalModels(mentalModelsData.items || []); } catch (error) { - console.error("Error loading reflections:", error); + console.error("Error loading mental models:", error); } finally { setLoading(false); } @@ -118,12 +118,12 @@ export function ReflectionsView() { setDeleting(true); try { - await client.deleteReflection(currentBank, deleteTarget.id); - setReflections((prev) => prev.filter((r) => r.id !== deleteTarget.id)); - if (selectedReflection?.id === deleteTarget.id) setSelectedReflection(null); + await client.deleteMentalModel(currentBank, deleteTarget.id); + setMentalModels((prev) => prev.filter((m) => m.id !== deleteTarget.id)); + if (selectedMentalModel?.id === deleteTarget.id) setSelectedMentalModel(null); setDeleteTarget(null); } catch (error) { - console.error("Error deleting reflection:", error); + console.error("Error deleting mental model:", error); alert("Error deleting: " + (error as Error).message); } finally { setDeleting(false); @@ -139,7 +139,7 @@ export function ReflectionsView() { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") { - setSelectedReflection(null); + setSelectedMentalModel(null); } }; window.addEventListener("keydown", handleKeyDown); @@ -155,17 +155,17 @@ export function ReflectionsView() { return ( -

Select a memory bank to view reflections.

+

Select a memory bank to view mental models.

); } // Pagination calculations - const totalPages = Math.ceil(filteredReflections.length / itemsPerPage); + const totalPages = Math.ceil(filteredMentalModels.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; - const paginatedReflections = filteredReflections.slice(startIndex, endIndex); + const paginatedMentalModels = filteredMentalModels.slice(startIndex, endIndex); return (
@@ -182,7 +182,7 @@ export function ReflectionsView() { type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} - placeholder="Filter reflections by name, query, or content..." + placeholder="Filter mental models by name, query, or content..." className="max-w-md" />
@@ -190,30 +190,31 @@ export function ReflectionsView() {
{searchQuery - ? `${filteredReflections.length} of ${reflections.length} reflections` - : `${reflections.length} reflection${reflections.length !== 1 ? "s" : ""}`} + ? `${filteredMentalModels.length} of ${mentalModels.length} mental models` + : `${mentalModels.length} mental model${mentalModels.length !== 1 ? "s" : ""}`}
-
- {filteredReflections.length > 0 ? ( + {filteredMentalModels.length > 0 ? ( <>
- Name - Source Query - Last Refreshed + ID + Name + Source Query + Last Refreshed - {paginatedReflections.map((r) => { - const refreshedDate = new Date(r.last_refreshed_at); + {paginatedMentalModels.map((m) => { + const refreshedDate = new Date(m.last_refreshed_at); const dateDisplay = refreshedDate.toLocaleDateString("en-US", { month: "short", day: "numeric", @@ -227,18 +228,23 @@ export function ReflectionsView() { return ( setSelectedReflection(r)} + onClick={() => setSelectedMentalModel(m)} > -
{r.name}
+ + {m.id} + +
+ +
{m.name}
- {r.source_query} + {m.source_query}
@@ -252,7 +258,7 @@ export function ReflectionsView() { className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive" onClick={(e) => { e.stopPropagation(); - setDeleteTarget({ id: r.id, name: r.name }); + setDeleteTarget({ id: m.id, name: m.name }); }} > @@ -269,8 +275,8 @@ export function ReflectionsView() { {totalPages > 1 && (
- {startIndex + 1}-{Math.min(endIndex, filteredReflections.length)} of{" "} - {filteredReflections.length} + {startIndex + 1}-{Math.min(endIndex, filteredMentalModels.length)} of{" "} + {filteredMentalModels.length}
)} )} - setShowCreateReflection(false)} + setShowCreateMentalModel(false)} onCreated={() => { - setShowCreateReflection(false); - // Reload the list immediately to show the new reflection + setShowCreateMentalModel(false); + // Reload the list immediately to show the new mental model loadData(); }} /> @@ -342,7 +348,7 @@ export function ReflectionsView() { !open && setDeleteTarget(null)}> - Delete Reflection + Delete Mental Model Are you sure you want to delete{" "} "{deleteTarget?.name}"? @@ -365,16 +371,16 @@ export function ReflectionsView() { - {selectedReflection && ( - setSelectedReflection(null)} + {selectedMentalModel && ( + setSelectedMentalModel(null)} onDelete={() => - setDeleteTarget({ id: selectedReflection.id, name: selectedReflection.name }) + setDeleteTarget({ id: selectedMentalModel.id, name: selectedMentalModel.name }) } onRefreshed={(updated) => { - setReflections((prev) => prev.map((r) => (r.id === updated.id ? updated : r))); - setSelectedReflection(updated); + setMentalModels((prev) => prev.map((m) => (m.id === updated.id ? updated : m))); + setSelectedMentalModel(updated); }} /> )} @@ -382,7 +388,7 @@ export function ReflectionsView() { ); } -function CreateReflectionDialog({ +function CreateMentalModelDialog({ open, onClose, onCreated, @@ -407,8 +413,8 @@ function CreateReflectionDialog({ const maxTokens = parseInt(form.maxTokens) || 2048; - // Submit reflection creation - content will be generated in background - await client.createReflection(currentBank, { + // Submit mental model creation - content will be generated in background + await client.createMentalModel(currentBank, { name: form.name.trim(), source_query: form.sourceQuery.trim(), tags: tags.length > 0 ? tags : undefined, @@ -418,8 +424,8 @@ function CreateReflectionDialog({ setForm({ name: "", sourceQuery: "", maxTokens: "2048", tags: "" }); onCreated(); } catch (error) { - console.error("Error creating reflection:", error); - alert("Error creating reflection: " + (error as Error).message); + console.error("Error creating mental model:", error); + alert("Error creating mental model: " + (error as Error).message); } finally { setCreating(false); } @@ -437,9 +443,9 @@ function CreateReflectionDialog({ > - Create Reflection + Create Mental Model - Create a reflection by running a query. The content will be auto-generated and can be + Create a mental model by running a query. The content will be auto-generated and can be refreshed later. @@ -513,39 +519,39 @@ function CreateReflectionDialog({ ); } -function ReflectionDetailPanel({ - reflection, +function MentalModelDetailPanel({ + mentalModel, onClose, onDelete, onRefreshed, }: { - reflection: Reflection; + mentalModel: MentalModel; onClose: () => void; onDelete: () => void; - onRefreshed: (r: Reflection) => void; + onRefreshed: (m: MentalModel) => void; }) { const { currentBank } = useBank(); const [refreshing, setRefreshing] = useState(false); const [viewMemoryId, setViewMemoryId] = useState(null); const [isEditing, setIsEditing] = useState(false); - const [editName, setEditName] = useState(reflection.name); + const [editName, setEditName] = useState(mentalModel.name); const [saving, setSaving] = useState(false); - // Reset edit form when reflection changes + // Reset edit form when mental model changes useEffect(() => { - setEditName(reflection.name); + setEditName(mentalModel.name); setIsEditing(false); - }, [reflection.id, reflection.name]); + }, [mentalModel.id, mentalModel.name]); const handleRefresh = async () => { if (!currentBank) return; setRefreshing(true); - const originalRefreshedAt = reflection.last_refreshed_at; + const originalRefreshedAt = mentalModel.last_refreshed_at; try { // Submit the refresh task - await client.refreshReflection(currentBank, reflection.id); + await client.refreshMentalModel(currentBank, mentalModel.id); // Poll until last_refreshed_at changes const pollInterval = 1000; // 1 second @@ -555,7 +561,7 @@ function ReflectionDetailPanel({ const poll = async (): Promise => { attempts++; try { - const updated = await client.getReflection(currentBank, reflection.id); + const updated = await client.getMentalModel(currentBank, mentalModel.id); if (updated.last_refreshed_at !== originalRefreshedAt) { // Refresh complete onRefreshed(updated); @@ -571,7 +577,7 @@ function ReflectionDetailPanel({ // Continue polling setTimeout(poll, pollInterval); } catch (error) { - console.error("Error polling reflection:", error); + console.error("Error polling mental model:", error); setRefreshing(false); } }; @@ -579,7 +585,7 @@ function ReflectionDetailPanel({ // Start polling after a short delay setTimeout(poll, pollInterval); } catch (error) { - console.error("Error refreshing reflection:", error); + console.error("Error refreshing mental model:", error); alert("Error refreshing: " + (error as Error).message); setRefreshing(false); } @@ -590,13 +596,13 @@ function ReflectionDetailPanel({ setSaving(true); try { - const updated = await client.updateReflection(currentBank, reflection.id, { + const updated = await client.updateMentalModel(currentBank, mentalModel.id, { name: editName.trim(), }); onRefreshed(updated); setIsEditing(false); } catch (error) { - console.error("Error updating reflection:", error); + console.error("Error updating mental model:", error); alert("Error updating: " + (error as Error).message); } finally { setSaving(false); @@ -616,12 +622,15 @@ function ReflectionDetailPanel({ })}`; }; - // Extract facts by type from based_on - const basedOn = reflection.reflect_response?.based_on || {}; - const worldFacts = basedOn["world"] || []; - const experienceFacts = basedOn["experience"] || []; - const mentalModels = basedOn["mental-models"] || []; - const totalFacts = worldFacts.length + experienceFacts.length + mentalModels.length; + // Extract all memories from based_on (excluding observations which are shown separately) + const basedOnFacts = mentalModel.reflect_response?.based_on + ? Object.entries(mentalModel.reflect_response.based_on) + .filter(([factType]) => factType !== "observation") + .flatMap(([factType, facts]) => facts.map((fact) => ({ ...fact, factType }))) + : []; + + // Observations are now in based_on with type=observation + const observations = mentalModel.reflect_response?.based_on?.observation || []; return (
@@ -645,7 +654,7 @@ function ReflectionDetailPanel({ size="sm" variant="outline" onClick={() => { - setEditName(reflection.name); + setEditName(mentalModel.name); setIsEditing(false); }} > @@ -656,7 +665,7 @@ function ReflectionDetailPanel({ ) : ( <>
-

{reflection.name}

+

{mentalModel.name}

-

{reflection.source_query}

+

{mentalModel.source_query}

)}
@@ -697,123 +706,84 @@ function ReflectionDetailPanel({ Content
- {reflection.content} + {mentalModel.content}
- {/* Based On Section with Tabs */} - {totalFacts > 0 ? ( + {/* Based On Facts Section */} + {basedOnFacts.length > 0 && (
- Based On ({totalFacts} {totalFacts === 1 ? "item" : "items"}) + Based On ({basedOnFacts.length} {basedOnFacts.length === 1 ? "fact" : "facts"})
- 0 - ? "world" - : experienceFacts.length > 0 - ? "experience" - : "mental-models" - } - > - - - World - - {worldFacts.length} - - - - Experience - - {experienceFacts.length} - - - - Mental Models - - {mentalModels.length} - - - - - -
- {worldFacts.map((fact, i) => ( -
+ {basedOnFacts.map((fact, i) => ( +
+
+ -
-

- {fact.text} -

- -
-
- ))} -
- - - -
- {experienceFacts.map((fact, i) => ( -
+ -
-
- ))} + View + +
+

{fact.text}

-
- - -
- {mentalModels.map((model, i) => ( -
-
-

- {model.text} -

- -
-
- ))} -
-
-
+ ))} +
- ) : !reflection.reflect_response ? ( + )} + + {/* Observations Used Section */} + {observations.length > 0 && ( +
+
+ Observations Used ({observations.length}) +
+
+ {observations.map((obs, i) => ( +
+
+ + observation + + +
+

{obs.text}

+
+ ))} +
+
+ )} + + {/* No based_on data yet */} + {!mentalModel.reflect_response && (
Based On

@@ -821,15 +791,15 @@ function ReflectionDetailPanel({ tracking.

- ) : null} + )} - {reflection.tags && reflection.tags.length > 0 && ( + {mentalModel.tags && mentalModel.tags.length > 0 && (
Tags
- {reflection.tags.map((tag) => ( + {mentalModel.tags.map((tag) => ( - Created: {formatDateTime(reflection.created_at)} - Refreshed: {formatDateTime(reflection.last_refreshed_at)} + Created: {formatDateTime(mentalModel.created_at)} + Refreshed: {formatDateTime(mentalModel.last_refreshed_at)}
@@ -851,7 +821,7 @@ function ReflectionDetailPanel({ ID
- {reflection.id} + {mentalModel.id}
diff --git a/hindsight-control-plane/src/components/search-debug-view.tsx b/hindsight-control-plane/src/components/search-debug-view.tsx index 316e5c75..aa65635b 100644 --- a/hindsight-control-plane/src/components/search-debug-view.tsx +++ b/hindsight-control-plane/src/components/search-debug-view.tsx @@ -32,7 +32,7 @@ import JsonView from "react18-json-view"; import "react18-json-view/src/style.css"; import { MemoryDetailPanel } from "./memory-detail-panel"; -type FactType = "world" | "experience" | "mental_model"; +type FactType = "world" | "experience" | "observation"; type Budget = "low" | "mid" | "high"; type TagsMatch = "any" | "all" | "any_strict" | "all_strict"; type ViewMode = "results" | "trace" | "json"; @@ -55,7 +55,7 @@ export function SearchDebugView() { const [results, setResults] = useState(null); const [entities, setEntities] = useState(null); const [chunks, setChunks] = useState(null); - const [mentalModels, setMentalModels] = useState(null); + const [observations, setObservations] = useState(null); const [trace, setTrace] = useState(null); const [loading, setLoading] = useState(false); const [viewMode, setViewMode] = useState("results"); @@ -109,7 +109,7 @@ export function SearchDebugView() { // Must select at least one type if (factTypes.length === 0) { - alert("Please select at least one type (World, Experience, or Mental Models)"); + alert("Please select at least one type (World, Experience, or Observations)"); return; } @@ -142,7 +142,7 @@ export function SearchDebugView() { setResults(data.results || []); setEntities(data.entities || null); setChunks(data.chunks || null); - setMentalModels(data.mental_models || null); + setObservations(data.observations || null); setTrace(data.trace || null); setViewMode("results"); } catch (error) { @@ -208,10 +208,10 @@ export function SearchDebugView() { ))} @@ -360,29 +360,29 @@ export function SearchDebugView() { {/* Results View */} {viewMode === "results" && (
- {/* Mental Models Section */} - {mentalModels && mentalModels.length > 0 && ( + {/* Observations Section */} + {observations && observations.length > 0 && ( - Mental Models - ({mentalModels.length}) + Observations + ({observations.length}) - {mentalModels.map((mm: any, idx: number) => ( + {observations.map((obs: any, idx: number) => (
-

{mm.text}

+

{obs.text}

- Mental Model + Observation - Proof count: {mm.proof_count || 1} - Relevance: {(mm.relevance || 0).toFixed(3)} + Proof count: {obs.proof_count || 1} + Relevance: {(obs.relevance || 0).toFixed(3)}
))} @@ -392,7 +392,7 @@ export function SearchDebugView() { {/* Memories Section */}
- {results.length === 0 && (!mentalModels || mentalModels.length === 0) ? ( + {results.length === 0 && (!observations || observations.length === 0) ? ( @@ -1010,7 +1010,7 @@ export function SearchDebugView() { results, ...(entities && { entities }), ...(chunks && { chunks }), - ...(mentalModels && { mental_models: mentalModels }), + ...(observations && { observations }), trace, }} collapsed={2} diff --git a/hindsight-control-plane/src/components/think-view.tsx b/hindsight-control-plane/src/components/think-view.tsx index 5d9d523f..ecf08ac2 100644 --- a/hindsight-control-plane/src/components/think-view.tsx +++ b/hindsight-control-plane/src/components/think-view.tsx @@ -52,9 +52,9 @@ export function ThinkView() { const [selectedDirective, setSelectedDirective] = useState(null); const [fullDirective, setFullDirective] = useState(null); const [loadingDirective, setLoadingDirective] = useState(false); - const [selectedMentalModel, setSelectedMentalModel] = useState(null); - const [fullMentalModel, setFullMentalModel] = useState(null); - const [loadingMentalModel, setLoadingMentalModel] = useState(false); + const [selectedObservation, setSelectedObservation] = useState(null); + const [fullObservation, setFullObservation] = useState(null); + const [loadingObservation, setLoadingObservation] = useState(false); const FEEDBACK_DIRECTIVE_NAME = "General Feedback"; @@ -77,22 +77,22 @@ export function ThinkView() { } }; - // Load full mental model data when one is selected - const handleSelectMentalModel = async (model: any) => { - setSelectedMentalModel(model); - setFullMentalModel(null); - if (!currentBank || !model?.id) return; + // Load full observation data when one is selected + const handleSelectObservation = async (observation: any) => { + setSelectedObservation(observation); + setFullObservation(null); + if (!currentBank || !observation?.id) return; - setLoadingMentalModel(true); + setLoadingObservation(true); try { - const models = await client.listMentalModels(currentBank); - const fullModel = models.items?.find((m: any) => m.id === model.id); - setFullMentalModel(fullModel || model); + const observations = await client.listObservations(currentBank); + const fullObs = observations.items?.find((o: any) => o.id === observation.id); + setFullObservation(fullObs || observation); } catch (error) { - console.error("Failed to load mental model:", error); - setFullMentalModel(model); // Fall back to partial data + console.error("Failed to load observation:", error); + setFullObservation(observation); // Fall back to partial data } finally { - setLoadingMentalModel(false); + setLoadingObservation(false); } }; @@ -436,33 +436,33 @@ export function ThinkView() { {/* Trace View - Split Layout */} {viewMode === "trace" && (
- {/* Mental Models Created */} - {result.mental_models_created && result.mental_models_created.length > 0 && ( + {/* Observations Created */} + {result.observations_created && result.observations_created.length > 0 && ( - Mental Models Created ({result.mental_models_created.length}) + Observations Created ({result.observations_created.length}) - New mental models learned during this reflection + New observations learned during this reflection
- {result.mental_models_created.map((model: any, i: number) => ( + {result.observations_created.map((obs: any, i: number) => (
- {model.name} + {obs.name}
- {model.description} + {obs.description}
- ID: {model.id} + ID: {obs.id}
))} @@ -665,10 +665,10 @@ export function ThinkView() { Based On {(result.based_on?.memories?.length || 0) + - (result.based_on?.mental_models?.filter( - (m: any) => m.subtype !== "directive" + (result.based_on?.observations?.filter( + (o: any) => o.subtype !== "directive" )?.length || 0) + - (result.trace?.mental_models?.filter((m: any) => m.subtype === "directive") + (result.trace?.observations?.filter((o: any) => o.subtype === "directive") ?.length || 0)}{" "} items used @@ -685,8 +685,7 @@ export function ThinkView() {
) : (result.based_on?.memories && result.based_on.memories.length > 0) || - (result.based_on?.mental_models && - result.based_on.mental_models.length > 0) ? ( + (result.based_on?.observations && result.based_on.observations.length > 0) ? (
{(() => { const memories = result.based_on?.memories || []; @@ -695,12 +694,12 @@ export function ThinkView() { (f: any) => f.type === "experience" ); const opinionFacts = memories.filter((f: any) => f.type === "opinion"); - const mentalModels = (result.based_on?.mental_models || []).filter( - (m: any) => m.subtype !== "directive" + const observations = (result.based_on?.observations || []).filter( + (o: any) => o.subtype !== "directive" ); const directives = - result.trace?.mental_models?.filter( - (m: any) => m.subtype === "directive" + result.trace?.observations?.filter( + (o: any) => o.subtype === "directive" ) || []; return ( @@ -742,21 +741,21 @@ export function ThinkView() {
)} - {/* Mental Models */} - {mentalModels.length > 0 && ( + {/* Observations */} + {observations.length > 0 && (
- Mental Models ({mentalModels.length}) + Observations ({observations.length})
- {mentalModels.map((model: any, i: number) => ( + {observations.map((obs: any, i: number) => (
handleSelectMentalModel(model)} + onClick={() => handleSelectObservation(obs)} > -
{model.name}
+
{obs.name}
))}
@@ -999,73 +998,43 @@ export function ThinkView() {
)} - {/* Mental Model Detail Panel */} - {selectedMentalModel && ( + {/* Observation Detail Panel */} + {selectedObservation && (
-

Mental Model

+

Observation

- {loadingMentalModel ? ( + {loadingObservation ? (
) : (
-

Name

+

Text

- {fullMentalModel?.name || selectedMentalModel.name} + {fullObservation?.text || selectedObservation.text}

- {fullMentalModel?.description && ( -
-

Description

-

{fullMentalModel.description}

-
- )} -
-
-

Type

-

{selectedMentalModel.type}

-
-
-

Subtype

- - {selectedMentalModel.subtype} - -
-
- {fullMentalModel?.tags && fullMentalModel.tags.length > 0 && ( + {fullObservation?.tags && fullObservation.tags.length > 0 && (

Tags

- {fullMentalModel.tags.map((tag: string) => ( + {fullObservation.tags.map((tag: string) => (
)} - {fullMentalModel?.observations && fullMentalModel.observations.length > 0 && ( + {fullObservation?.source_memories && fullObservation.source_memories.length > 0 && (

- Observations ({fullMentalModel.observations.length}) + Source Memories ({fullObservation.source_memories.length})

- {fullMentalModel.observations.map((obs: any, i: number) => ( + {fullObservation.source_memories.map((mem: any, i: number) => (
- {obs.title &&
{obs.title}
}
- {obs.content || obs.text || (typeof obs === "string" ? obs : "")} + {mem.text || (typeof mem === "string" ? mem : "")}
- {obs.memory_ids && obs.memory_ids.length > 0 && ( -
- Based on {obs.memory_ids.length} memories -
- )}
))}
@@ -1102,7 +1065,7 @@ export function ThinkView() {

ID

- {selectedMentalModel.id} + {selectedObservation.id}

diff --git a/hindsight-control-plane/src/lib/api.ts b/hindsight-control-plane/src/lib/api.ts index 7230fcbe..252cbd67 100644 --- a/hindsight-control-plane/src/lib/api.ts +++ b/hindsight-control-plane/src/lib/api.ts @@ -51,7 +51,7 @@ export class ControlPlaneClient { include?: { entities?: { max_tokens: number } | null; chunks?: { max_tokens: number } | null; - mental_models?: { max_results?: number } | null; + observations?: { max_results?: number } | null; }; query_timestamp?: string; tags?: string[]; @@ -244,14 +244,14 @@ export class ControlPlaneClient { } /** - * Clear all mental models for a bank + * Clear all observations for a bank */ - async clearMentalModels(bankId: string) { + async clearObservations(bankId: string) { return this.fetchApi<{ success: boolean; message: string; deleted_count: number; - }>(`/api/banks/${bankId}/mental-models`, { + }>(`/api/banks/${bankId}/observations`, { method: "DELETE", }); } @@ -470,12 +470,12 @@ export class ControlPlaneClient { }); } - // ============= MENTAL MODELS (auto-consolidated, read-only) ============= + // ============= OBSERVATIONS (auto-consolidated, read-only) ============= /** - * List mental models for a bank (auto-consolidated knowledge) + * List observations for a bank (auto-consolidated knowledge) */ - async listMentalModels(bankId: string, tags?: string[], tagsMatch?: string) { + async listObservations(bankId: string, tags?: string[], tagsMatch?: string) { const params = new URLSearchParams(); if (tags && tags.length > 0) { tags.forEach((t) => params.append("tags", t)); @@ -508,13 +508,13 @@ export class ControlPlaneClient { created_at: string; updated_at: string; }>; - }>(`/api/banks/${bankId}/mental-models${query ? `?${query}` : ""}`); + }>(`/api/banks/${bankId}/observations${query ? `?${query}` : ""}`); } /** - * Get a mental model with source memories + * Get an observation with source memories */ - async getMentalModel(bankId: string, modelId: string) { + async getObservation(bankId: string, observationId: string) { return this.fetchApi<{ id: string; bank_id: string; @@ -537,15 +537,15 @@ export class ControlPlaneClient { }>; created_at: string; updated_at: string; - }>(`/api/banks/${bankId}/mental-models/${modelId}`); + }>(`/api/banks/${bankId}/observations/${observationId}`); } - // ============= REFLECTIONS ============= + // ============= MENTAL MODELS (stored reflect responses) ============= /** - * List reflections for a bank + * List mental models for a bank */ - async listReflections(bankId: string, tags?: string[], tagsMatch?: string) { + async listMentalModels(bankId: string, tags?: string[], tagsMatch?: string) { const params = new URLSearchParams(); if (tags && tags.length > 0) { tags.forEach((t) => params.append("tags", t)); @@ -567,17 +567,16 @@ export class ControlPlaneClient { reflect_response?: { text: string; based_on: Record>; - mental_models?: Array<{ id: string; text: string }>; }; }>; - }>(`/api/banks/${bankId}/reflections${query ? `?${query}` : ""}`); + }>(`/api/banks/${bankId}/mental-models${query ? `?${query}` : ""}`); } /** - * Create a reflection (async - content auto-generated in background) + * Create a mental model (async - content auto-generated in background) * Returns operation_id to track progress */ - async createReflection( + async createMentalModel( bankId: string, params: { name: string; @@ -588,16 +587,16 @@ export class ControlPlaneClient { ) { return this.fetchApi<{ operation_id: string; - }>(`/api/banks/${bankId}/reflections`, { + }>(`/api/banks/${bankId}/mental-models`, { method: "POST", body: JSON.stringify(params), }); } /** - * Get a reflection + * Get a mental model */ - async getReflection(bankId: string, reflectionId: string) { + async getMentalModel(bankId: string, mentalModelId: string) { return this.fetchApi<{ id: string; bank_id: string; @@ -610,17 +609,17 @@ export class ControlPlaneClient { reflect_response?: { text: string; based_on: Record>; - mental_models?: Array<{ id: string; text: string }>; + observations?: Array<{ id: string; text: string }>; }; - }>(`/api/banks/${bankId}/reflections/${reflectionId}`); + }>(`/api/banks/${bankId}/mental-models/${mentalModelId}`); } /** - * Update a reflection + * Update a mental model */ - async updateReflection( + async updateMentalModel( bankId: string, - reflectionId: string, + mentalModelId: string, params: { name?: string; } @@ -637,30 +636,30 @@ export class ControlPlaneClient { reflect_response?: { text: string; based_on: Record>; - mental_models?: Array<{ id: string; text: string }>; + observations?: Array<{ id: string; text: string }>; }; - }>(`/api/banks/${bankId}/reflections/${reflectionId}`, { + }>(`/api/banks/${bankId}/mental-models/${mentalModelId}`, { method: "PATCH", body: JSON.stringify(params), }); } /** - * Delete a reflection + * Delete a mental model */ - async deleteReflection(bankId: string, reflectionId: string) { - return this.fetchApi(`/api/banks/${bankId}/reflections/${reflectionId}`, { + async deleteMentalModel(bankId: string, mentalModelId: string) { + return this.fetchApi(`/api/banks/${bankId}/mental-models/${mentalModelId}`, { method: "DELETE", }); } /** - * Refresh a reflection (re-run source query) - async operation + * Refresh a mental model (re-run source query) - async operation */ - async refreshReflection(bankId: string, reflectionId: string) { + async refreshMentalModel(bankId: string, mentalModelId: string) { return this.fetchApi<{ operation_id: string; - }>(`/api/banks/${bankId}/reflections/${reflectionId}/refresh`, { + }>(`/api/banks/${bankId}/mental-models/${mentalModelId}/refresh`, { method: "POST", }); } @@ -673,7 +672,7 @@ export class ControlPlaneClient { return this.fetchApi<{ api_version: string; features: { - mental_models: boolean; + observations: boolean; mcp: boolean; worker: boolean; }; diff --git a/hindsight-control-plane/src/lib/features-context.tsx b/hindsight-control-plane/src/lib/features-context.tsx index 9fb6b5d4..6807989e 100644 --- a/hindsight-control-plane/src/lib/features-context.tsx +++ b/hindsight-control-plane/src/lib/features-context.tsx @@ -4,7 +4,7 @@ import React, { createContext, useContext, useState, useEffect } from "react"; import { client } from "./api"; interface Features { - mental_models: boolean; + observations: boolean; mcp: boolean; worker: boolean; } @@ -16,7 +16,7 @@ interface FeaturesContextType { } const defaultFeatures: Features = { - mental_models: false, + observations: false, mcp: false, worker: false, }; diff --git a/hindsight-docs/docs/cookbook/applications/openai-fitness-coach.md b/hindsight-docs/docs/cookbook/applications/openai-fitness-coach.md index 78643073..67f88ac9 100644 --- a/hindsight-docs/docs/cookbook/applications/openai-fitness-coach.md +++ b/hindsight-docs/docs/cookbook/applications/openai-fitness-coach.md @@ -48,7 +48,7 @@ OpenAI Assistant (analyzes, gives advice) | Function Call: store_memory(advice as experience) | -Hindsight API (stores coach's advice, consolidates into mental models) +Hindsight API (stores coach's advice, consolidates into observations) | Personalized Answer ``` @@ -126,7 +126,7 @@ retrieve_memories(query, fact_types, top_k) search_workouts(after_date, before_date, workout_type) get_nutrition_summary(after_date, before_date) get_user_goals() -get_coach_insights(about) # Retrieves mental models +get_coach_insights(about) # Retrieves observations ``` Each function makes API calls to Hindsight to fetch relevant memories. @@ -192,7 +192,7 @@ The OpenAI Agent can retrieve different memory types from Hindsight: - **World Facts** (`fact_type: "world"`): Workouts, meals, activities - **Experience Facts** (`fact_type: "experience"`): Goals, intentions, coach advice -- **Mental Models** (`fact_type: "mental_model"`): Consolidated knowledge about user patterns +- **Observations** (`fact_type: "observation"`): Consolidated knowledge about user patterns ## Customization @@ -266,7 +266,7 @@ The key benefit: **Separation of concerns** **Use Hindsight directly when:** - You want a complete memory-first solution -- You want automatic memory retrieval and mental model consolidation +- You want automatic memory retrieval and observation consolidation - You want to use different LLM providers (not just OpenAI) - You want the `/reflect` endpoint's integrated approach diff --git a/hindsight-docs/docs/cookbook/recipes/quickstart.md b/hindsight-docs/docs/cookbook/recipes/quickstart.md index 4a453e01..affb8392 100644 --- a/hindsight-docs/docs/cookbook/recipes/quickstart.md +++ b/hindsight-docs/docs/cookbook/recipes/quickstart.md @@ -127,7 +127,7 @@ for r in results.results: ## Reflect: Generate Insights -The `reflect` operation performs reasoning over existing memories using the bank's disposition. It retrieves relevant facts and mental models to generate contextual responses. +The `reflect` operation performs reasoning over existing memories using the bank's disposition. It retrieves relevant facts and observations to generate contextual responses. Example use cases: - An AI Project Manager reflecting on what risks need to be mitigated @@ -142,11 +142,11 @@ print(response) ## Memory Types -Hindsight organizes knowledge into facts and consolidated mental models: +Hindsight organizes knowledge into facts and consolidated observations: - **World**: Facts about the world ("The stove gets hot") - **Experience**: Agent's own experiences ("I touched the stove and it really hurt") -- **Mental Model**: Consolidated knowledge synthesized from facts ("Always be careful around hot surfaces") +- **Observation**: Consolidated knowledge synthesized from facts ("Always be careful around hot surfaces") ## Cleanup diff --git a/hindsight-docs/docs/developer/admin-cli.md b/hindsight-docs/docs/developer/admin-cli.md index 651d3714..f2d59c29 100644 --- a/hindsight-docs/docs/developer/admin-cli.md +++ b/hindsight-docs/docs/developer/admin-cli.md @@ -78,7 +78,7 @@ The backup includes: - Memory banks and their configuration - Documents and chunks - Entities and their relationships -- Memory units (facts, experiences, mental models) +- Memory units (facts, experiences, observations) - Entity cooccurrences and memory links :::note Consistency diff --git a/hindsight-docs/docs/developer/api/main-methods.mdx b/hindsight-docs/docs/developer/api/main-methods.mdx index 731dd328..b7d2d760 100644 --- a/hindsight-docs/docs/developer/api/main-methods.mdx +++ b/hindsight-docs/docs/developer/api/main-methods.mdx @@ -89,7 +89,7 @@ hindsight recall my-bank "Tell me about Alice" -v ## Reflect: Reason with Disposition -Generate disposition-aware responses using memories and mental models. +Generate disposition-aware responses using memories and observations. @@ -104,7 +104,7 @@ Generate disposition-aware responses using memories and mental models. # Basic reflect hindsight reflect my-bank "Should we adopt TypeScript for our backend?" -# Verbose output (shows sources and mental models) +# Verbose output (shows sources and observations) hindsight reflect my-bank "What are Alice's strengths for the team lead role?" -v # With higher reasoning budget @@ -114,7 +114,7 @@ hindsight reflect my-bank "Analyze our tech stack" --budget high -**What happens:** Memories and mental models are recalled, bank disposition is applied, and the LLM reasons through the evidence to generate a response. +**What happens:** Memories and observations are recalled, bank disposition is applied, and the LLM reasons through the evidence to generate a response. **See:** [Reflect Details](./reflect) for disposition configuration. @@ -126,9 +126,9 @@ hindsight reflect my-bank "Analyze our tech stack" --budget high |---------|--------|--------|---------| | **Purpose** | Store information | Find information | Reason about information | | **Input** | Raw text/documents | Search query | Question/prompt | -| **Output** | Memory IDs | Ranked facts + mental models | Reasoned response | +| **Output** | Memory IDs | Ranked facts + observations | Reasoned response | | **Uses LLM** | Yes (extraction) | No | Yes (generation) | -| **Uses mental models** | No | Yes | Yes | +| **Uses observations** | No | Yes | Yes | | **Disposition** | No | No | Yes | --- diff --git a/hindsight-docs/docs/developer/api/reflections.mdx b/hindsight-docs/docs/developer/api/mental-models.mdx similarity index 52% rename from hindsight-docs/docs/developer/api/reflections.mdx rename to hindsight-docs/docs/developer/api/mental-models.mdx index 4b7b07e5..850c9823 100644 --- a/hindsight-docs/docs/developer/api/reflections.mdx +++ b/hindsight-docs/docs/developer/api/mental-models.mdx @@ -2,7 +2,7 @@ sidebar_position: 4 --- -# Reflections +# Mental Models User-curated summaries that provide high-quality, pre-computed answers for common queries. @@ -11,23 +11,23 @@ import TabItem from '@theme/TabItem'; import CodeSnippet from '@site/src/components/CodeSnippet'; {/* Import raw source files */} -import reflectionsPy from '!!raw-loader!@site/examples/api/reflections.py'; +import mentalModelsPy from '!!raw-loader!@site/examples/api/mental-models.py'; -## What Are Reflections? +## What Are Mental Models? -Reflections are **saved reflect responses** that you curate for your memory bank. When you create a reflection, Hindsight runs a reflect operation with your source query and stores the result. During future reflect calls, these pre-computed summaries are checked first — providing faster, more consistent answers. +Mental models are **saved reflect responses** that you curate for your memory bank. When you create a mental model, Hindsight runs a reflect operation with your source query and stores the result. During future reflect calls, these pre-computed summaries are checked first — providing faster, more consistent answers. ```mermaid graph LR - A[Create Reflection] --> B[Run Reflect] + A[Create Mental Model] --> B[Run Reflect] B --> C[Store Result] C --> D[Future Queries] D --> E{Match Found?} - E -->|Yes| F[Return Reflection] + E -->|Yes| F[Return Mental Model] E -->|No| G[Run Full Reflect] ``` -### Why Use Reflections? +### Why Use Mental Models? | Benefit | Description | |---------|-------------| @@ -40,27 +40,27 @@ graph LR During reflect, the agent checks sources in priority order: -1. **Reflections** — User-curated summaries (highest priority) -2. **Mental Models** — Consolidated knowledge +1. **Mental Models** — User-curated summaries (highest priority) +2. **Observations** — Consolidated knowledge 3. **Raw Facts** — Ground truth memories -Reflections are checked first because they represent your explicitly curated knowledge. +Mental models are checked first because they represent your explicitly curated knowledge. --- -## Create a Reflection +## Create a Mental Model -Creating a reflection runs a reflect operation in the background and saves the result: +Creating a mental model runs a reflect operation in the background and saves the result: - + ```bash -# Create a reflection (async operation) -curl -X POST "http://localhost:8888/v1/default/banks/my-bank/reflections" \ +# Create a mental model (async operation) +curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \ -H "Content-Type: application/json" \ -d '{ "name": "Team Communication Preferences", @@ -79,23 +79,23 @@ curl -X POST "http://localhost:8888/v1/default/banks/my-bank/reflections" \ | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `name` | string | Yes | Human-readable name for the reflection | +| `name` | string | Yes | Human-readable name for the mental model | | `source_query` | string | Yes | The query to run to generate content | | `tags` | list | No | Tags for filtering during retrieval | -| `max_tokens` | int | No | Maximum tokens for the reflection content | +| `max_tokens` | int | No | Maximum tokens for the mental model content | --- -## List Reflections +## List Mental Models - + ```bash -curl "http://localhost:8888/v1/default/banks/my-bank/reflections" +curl "http://localhost:8888/v1/default/banks/my-bank/mental-models" ``` @@ -103,16 +103,16 @@ curl "http://localhost:8888/v1/default/banks/my-bank/reflections" --- -## Get a Reflection +## Get a Mental Model - + ```bash -curl "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflection_id}" +curl "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" ``` @@ -122,30 +122,30 @@ curl "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflection_id} | Field | Type | Description | |-------|------|-------------| -| `id` | string | Unique reflection ID | +| `id` | string | Unique mental model ID | | `bank_id` | string | Memory bank ID | | `name` | string | Human-readable name | | `source_query` | string | The query used to generate content | -| `content` | string | The generated reflection text | +| `content` | string | The generated mental model text | | `tags` | list | Tags for filtering | -| `last_refreshed_at` | string | When the reflection was last updated | -| `created_at` | string | When the reflection was created | +| `last_refreshed_at` | string | When the mental model was last updated | +| `created_at` | string | When the mental model was created | | `reflect_response` | object | Full reflect response including `based_on` facts | --- -## Refresh a Reflection +## Refresh a Mental Model -Re-run the source query to update the reflection with current knowledge: +Re-run the source query to update the mental model with current knowledge: - + ```bash -curl -X POST "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflection_id}/refresh" +curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}/refresh" ``` @@ -153,23 +153,23 @@ curl -X POST "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflec Refreshing is useful when: - New memories have been retained that affect the topic -- Mental models have been updated -- You want to ensure the reflection reflects current knowledge +- Observations have been updated +- You want to ensure the mental model reflects current knowledge --- -## Update a Reflection +## Update a Mental Model -Update the reflection's name: +Update the mental model's name: - + ```bash -curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflection_id}" \ +curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" \ -H "Content-Type: application/json" \ -d '{"name": "Updated Team Communication Preferences"}' ``` @@ -179,16 +179,16 @@ curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/reflections/{refle --- -## Delete a Reflection +## Delete a Mental Model - + ```bash -curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/reflections/{reflection_id}" +curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" ``` @@ -209,6 +209,6 @@ curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/reflections/{refl ## Next Steps -- [**Reflect**](./reflect) — How the agentic loop uses reflections -- [**Mental Models**](/developer/mental-models) — How knowledge is consolidated -- [**Operations**](./operations) — Track async reflection creation +- [**Reflect**](./reflect) — How the agentic loop uses mental models +- [**Observations**](/developer/observations) — How knowledge is consolidated +- [**Operations**](./operations) — Track async mental model creation diff --git a/hindsight-docs/docs/developer/api/operations.md b/hindsight-docs/docs/developer/api/operations.md index a6596ae9..76540b08 100644 --- a/hindsight-docs/docs/developer/api/operations.md +++ b/hindsight-docs/docs/developer/api/operations.md @@ -25,7 +25,7 @@ Support for external streaming platforms like Kafka for scale-out processing is | Operation | Trigger | Description | |-----------|---------|-------------| | **batch_retain** | `retain_batch` with `async=True` | Processes large content batches in the background | -| **consolidate** | After `retain` | Consolidates new facts into mental models | +| **consolidate** | After `retain` | Consolidates new facts into observations | ## Async Retain Example diff --git a/hindsight-docs/docs/developer/api/recall.mdx b/hindsight-docs/docs/developer/api/recall.mdx index 8c044b2e..6f8e6104 100644 --- a/hindsight-docs/docs/developer/api/recall.mdx +++ b/hindsight-docs/docs/developer/api/recall.mdx @@ -42,7 +42,7 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `query` | string | required | Natural language query | -| `types` | list | all | Filter: `world`, `experience`, `mental_model` | +| `types` | list | all | Filter: `world`, `experience`, `observation` | | `budget` | string | "mid" | Budget level: `low`, `mid`, `high` | | `max_tokens` | int | 4096 | Token budget for results | | `trace` | bool | false | Enable trace output for debugging | @@ -68,15 +68,15 @@ Recall specific memory types: - + -:::tip About Mental Models -Mental models are consolidated knowledge synthesized from multiple facts. They capture patterns, preferences, and learnings that the memory bank has built up over time. Mental models are automatically created in the background after retain operations. +:::tip About Observations +Observations are consolidated knowledge synthesized from multiple facts. They capture patterns, preferences, and learnings that the memory bank has built up over time. Observations are automatically created in the background after retain operations. ::: ## Token Budget Management diff --git a/hindsight-docs/docs/developer/api/reflect.mdx b/hindsight-docs/docs/developer/api/reflect.mdx index 435f8a3e..2bc28ba2 100644 --- a/hindsight-docs/docs/developer/api/reflect.mdx +++ b/hindsight-docs/docs/developer/api/reflect.mdx @@ -11,7 +11,7 @@ When you call **reflect**, Hindsight runs an **agentic loop** that: 2. **Applies** the bank's disposition traits to shape the reasoning style 3. **Generates** a grounded answer with citations to the sources used -The agent has access to hierarchical retrieval tools (reflections → mental models → raw facts) and decides what information it needs to answer your query. +The agent has access to hierarchical retrieval tools (mental models → observations → raw facts) and decides what information it needs to answer your query. import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -66,7 +66,7 @@ The `budget` parameter controls how thoroughly the agent searches for informatio | `mid` | 1x base | Balanced exploration | | `high` | 2x base | Complex questions, comprehensive analysis | -Higher budgets allow the agent more iterations to search reflections, mental models, and raw facts before generating a response. Use `high` for questions that require synthesizing information from multiple sources. +Higher budgets allow the agent more iterations to search mental models, observations, and raw facts before generating a response. Use `high` for questions that require synthesizing information from multiple sources. ### Max Tokens @@ -78,8 +78,8 @@ The `max_tokens` parameter limits the length of the final generated response. Th |-------|------|-------------| | `text` | string | The generated answer text | | `used_memory_ids` | array | Memory IDs cited by the agent | -| `used_reflection_ids` | array | Reflection IDs cited by the agent | | `used_mental_model_ids` | array | Mental model IDs cited by the agent | +| `used_observation_ids` | array | Observation IDs cited by the agent | | `structured_output` | object | Parsed structured output (when `response_schema` provided) | | `iterations` | int | Number of agent loop iterations | | `tools_called` | int | Total number of tool calls made | @@ -123,8 +123,8 @@ The bank's disposition affects reflect responses: The agent cites which sources it used to generate the response: - `used_memory_ids` — Raw memory facts that were retrieved and cited -- `used_reflection_ids` — User-curated reflections that were used -- `used_mental_model_ids` — Consolidated mental models that were used +- `used_mental_model_ids` — User-curated mental models that were used +- `used_observation_ids` — Consolidated observations that were used **Important:** Only IDs that were actually retrieved during the agent loop can be cited. The agent validates citations to prevent hallucinated references. diff --git a/hindsight-docs/docs/developer/index.md b/hindsight-docs/docs/developer/index.md index bda449ce..baa17663 100644 --- a/hindsight-docs/docs/developer/index.md +++ b/hindsight-docs/docs/developer/index.md @@ -13,7 +13,7 @@ AI agents forget everything between sessions. Every conversation starts from zer - **Simple vector search isn't enough** — "What did Alice do last spring?" requires temporal reasoning, not just semantic similarity - **Facts get disconnected** — Knowing "Alice works at Google" and "Google is in Mountain View" should let you answer "Where does Alice work?" even if you never stored that directly -- **AI Agents need to consolidate knowledge** — A coding assistant that remembers "the user prefers functional programming" should consolidate this into a mental model and weigh it when making recommendations +- **AI Agents need to consolidate knowledge** — A coding assistant that remembers "the user prefers functional programming" should consolidate this into an observation and weigh it when making recommendations - **Context matters** — The same information means different things to different memory banks with different personalities Hindsight solves these problems with a memory system designed specifically for AI agents. @@ -31,12 +31,12 @@ graph LR subgraph bank["Memory Bank"] direction TB - MentalModels[Mental Models] + Observations[Observations] MemEnt[Memories & Entities] Chunks[Chunks] Documents[Documents] - MentalModels --> MemEnt --> Chunks --> Documents + Observations --> MemEnt --> Chunks --> Documents end end @@ -53,13 +53,13 @@ graph LR ### Memory Types -Hindsight organizes knowledge into facts and consolidated mental models: +Hindsight organizes knowledge into facts and consolidated observations: | Type | What it stores | Example | |------|----------------|---------| | **World** | Objective facts received | "Alice works at Google" | | **Experience** | Bank's own actions and interactions | "I recommended Python to Bob" | -| **Mental Model** | Consolidated knowledge from facts | "The user prefers functional programming patterns" +| **Observation** | Consolidated knowledge from facts | "The user prefers functional programming patterns" ### Multi-Strategy Retrieval (TEMPR) @@ -88,13 +88,13 @@ graph LR | **Graph** | Related entities, indirect connections | | **Temporal** | "last spring", "in June", time ranges | -### Mental Model Consolidation +### Observation Consolidation -After memories are retained, Hindsight automatically consolidates related facts into **mental models** — synthesized knowledge representations that capture patterns and learnings: +After memories are retained, Hindsight automatically consolidates related facts into **observations** — synthesized knowledge representations that capture patterns and learnings: -- **Automatic synthesis**: New facts are analyzed and consolidated into existing or new mental models -- **Evidence tracking**: Each mental model tracks which facts support it -- **Continuous refinement**: Mental models evolve as new evidence arrives +- **Automatic synthesis**: New facts are analyzed and consolidated into existing or new observations +- **Evidence tracking**: Each observation tracks which facts support it +- **Continuous refinement**: Observations evolve as new evidence arrives ### Disposition Traits diff --git a/hindsight-docs/docs/developer/multilingual.md b/hindsight-docs/docs/developer/multilingual.md index 3177af0a..8695a179 100644 --- a/hindsight-docs/docs/developer/multilingual.md +++ b/hindsight-docs/docs/developer/multilingual.md @@ -4,7 +4,7 @@ sidebar_position: 5 # Multilingual Support -Hindsight automatically detects the language of your input and responds in the same language. This means facts, entities, and reflections are preserved in their original language without translation to English. +Hindsight automatically detects the language of your input and responds in the same language. This means facts, entities, and reflect responses are preserved in their original language without translation to English. ## How It Works diff --git a/hindsight-docs/docs/developer/mental-models.mdx b/hindsight-docs/docs/developer/observations.mdx similarity index 52% rename from hindsight-docs/docs/developer/mental-models.mdx rename to hindsight-docs/docs/developer/observations.mdx index f7e020a6..26c2e394 100644 --- a/hindsight-docs/docs/developer/mental-models.mdx +++ b/hindsight-docs/docs/developer/observations.mdx @@ -5,33 +5,33 @@ sidebar_position: 5 import CodeSnippet from '@site/src/components/CodeSnippet'; import recallPy from '!!raw-loader!@site/examples/api/recall.py'; -# Mental Models: Knowledge Consolidation +# Observations: Knowledge Consolidation -After memories are retained, Hindsight automatically consolidates related facts into **mental models** — synthesized knowledge representations that capture patterns and learnings. +After memories are retained, Hindsight automatically consolidates related facts into **observations** — synthesized knowledge representations that capture patterns and learnings. ```mermaid graph LR A[New Facts] --> B[Consolidation Engine] - B --> C{Existing Model?} - C -->|Yes| D[Refine Model] - C -->|No| E[Create Model] - D --> F[Mental Models] + B --> C{Existing Observation?} + C -->|Yes| D[Refine Observation] + C -->|No| E[Create Observation] + D --> F[Observations] E --> F ``` --- -## What Are Mental Models? +## What Are Observations? -Mental models are **consolidated knowledge** synthesized from multiple facts. Unlike raw facts which are individual pieces of information, mental models represent patterns, preferences, and learnings that emerge from accumulated evidence. +Observations are **consolidated knowledge** synthesized from multiple facts. Unlike raw facts which are individual pieces of information, observations represent patterns, preferences, and learnings that emerge from accumulated evidence. -| Raw Facts | Mental Model | +| Raw Facts | Observation | |-----------|--------------| | "Alice prefers Python" | "Alice is a Python-focused developer who values readability and simplicity" | | "Alice dislikes verbose code" | | | "Alice recommends type hints" | | -Mental models provide: +Observations provide: - **Synthesis**: Patterns that emerge from multiple facts - **Context**: Richer understanding than individual facts - **Efficiency**: Condensed knowledge for faster retrieval @@ -44,87 +44,87 @@ Mental models provide: After `retain()` completes, the consolidation engine runs automatically: -1. **New facts analyzed** — Each new fact is compared against existing mental models +1. **New facts analyzed** — Each new fact is compared against existing observations 2. **Pattern detection** — Related facts are grouped and synthesized -3. **Model creation/update** — New mental models are created or existing ones refined -4. **Evidence tracking** — Each mental model maintains references to supporting facts +3. **Observation creation/update** — New observations are created or existing ones refined +4. **Evidence tracking** — Each observation maintains references to supporting facts ### Evidence-Based Evolution -Mental models evolve as new evidence arrives: +Observations evolve as new evidence arrives: -| Event | What the bank learns | Mental model state | +| Event | What the bank learns | Observation state | |-------|---------------------|----------------| | **Day 1** | "Redis is open source under BSD license" | "Redis is excellent for caching — fast, reliable, and OSS-friendly" (2 supporting facts) | -| **Day 2** | "Redis has great community support" | Mental model reinforced (3 supporting facts) | -| **Day 30** | "Redis changed license to SSPL" | Mental model refined: "Redis is technically strong, but has license concerns for cloud" | -| **Day 45** | "Valkey forked Redis under BSD" | New mental model: "Consider Valkey for new projects requiring true OSS" | +| **Day 2** | "Redis has great community support" | Observation reinforced (3 supporting facts) | +| **Day 30** | "Redis changed license to SSPL" | Observation refined: "Redis is technically strong, but has license concerns for cloud" | +| **Day 45** | "Valkey forked Redis under BSD" | New observation: "Consider Valkey for new projects requiring true OSS" | ### Handling Contradictory Evidence -What happens when a new fact contradicts an existing mental model? +What happens when a new fact contradicts an existing observation? The consolidation engine doesn't blindly overwrite — it **reconciles** the contradiction by capturing the evolution: **Example: User preference changes** -| Time | Fact | Mental Model | +| Time | Fact | Observation | |------|------|--------------| | Week 1 | "User says they love React" | "User prefers React for frontend development" | | Week 2 | "User praises React's component model" | "User is enthusiastic about React, particularly its component model" | | Week 3 | "User says they've switched to Vue and won't use React anymore" | "User was previously a React enthusiast who appreciated its component model, but has now switched to Vue and no longer uses React" | -Notice how the final mental model captures the **full journey** — not just "User prefers Vue" but the complete evolution of their preference. This nuanced understanding means: +Notice how the final observation captures the **full journey** — not just "User prefers Vue" but the complete evolution of their preference. This nuanced understanding means: - Your agent won't recommend React tutorials to someone who explicitly moved away from it - Your agent understands *why* this matters (they were enthusiastic before, so this is a deliberate choice) - Your agent can reference this history when relevant ("I know you used to work with React...") The system: -1. **Detects the conflict** — New fact contradicts existing model -2. **Preserves history** — Incorporates the previous understanding into the new model -3. **Creates nuanced model** — Synthesizes a richer understanding that captures the change -4. **Updates freshness** — Marks the model as recently updated +1. **Detects the conflict** — New fact contradicts existing observation +2. **Preserves history** — Incorporates the previous understanding into the new observation +3. **Creates nuanced observation** — Synthesizes a richer understanding that captures the change +4. **Updates freshness** — Marks the observation as recently updated **Example: Correcting misinformation** -| Time | Fact | Mental Model | +| Time | Fact | Observation | |------|------|--------------| | Day 1 | "Alice works at Google" | "Alice is a Google employee" | | Day 10 | "Alice actually works at Meta, not Google" | "Alice works at Meta (previously thought to work at Google)" | -When a fact explicitly corrects previous information, the mental model is updated to reflect the correction while noting the previous understanding. The raw facts are always preserved, so you can trace back to see what was originally stated and when it was corrected. +When a fact explicitly corrects previous information, the observation is updated to reflect the correction while noting the previous understanding. The raw facts are always preserved, so you can trace back to see what was originally stated and when it was corrected. --- -## Mental Models in Retrieval +## Observations in Retrieval -Mental models are automatically included in both `recall()` and `reflect()` operations: +Observations are automatically included in both `recall()` and `reflect()` operations: ### In Recall -Mental models are returned alongside raw facts, filtered by the `types` parameter: +Observations are returned alongside raw facts, filtered by the `types` parameter: - + ### In Reflect The reflect agent uses **hierarchical retrieval**: -1. **[Reflections](/developer/api/reflections)** — User-curated summaries (highest priority) -2. **Mental Models** — Consolidated knowledge with freshness awareness +1. **[Mental Models](/developer/api/mental-models)** — User-curated summaries (highest priority) +2. **Observations** — Consolidated knowledge with freshness awareness 3. **Raw Facts** — Ground truth for verification -The agent automatically queries mental models and uses them to inform its reasoning. +The agent automatically queries observations and uses them to inform its reasoning. --- ## Freshness Awareness -Mental models track when they were last updated. During reflect, the agent considers freshness: +Observations track when they were last updated. During reflect, the agent considers freshness: -- **Fresh models**: Used directly for reasoning -- **Stale models**: Agent verifies against current facts before relying on them +- **Fresh observations**: Used directly for reasoning +- **Stale observations**: Agent verifies against current facts before relying on them This ensures responses stay accurate even as the underlying data changes. @@ -132,7 +132,7 @@ This ensures responses stay accurate even as the underlying data changes. ## Mission-Oriented Consolidation -The bank's **mission** directly influences what knowledge gets consolidated into mental models. When you set a mission on your memory bank, the consolidation engine focuses on extracting knowledge that serves that mission. +The bank's **mission** directly influences what knowledge gets consolidated into observations. When you set a mission on your memory bank, the consolidation engine focuses on extracting knowledge that serves that mission. **Example:** @@ -148,11 +148,11 @@ client.create_bank( With this mission, the consolidation engine will: - **Prioritize** customer preferences, issue patterns, and communication styles - **Skip** ephemeral details that don't serve support goals -- **Synthesize** mental models focused on helping customers +- **Synthesize** observations focused on helping customers Without a mission, the engine performs general-purpose consolidation. With a mission, it becomes focused and efficient — extracting only knowledge that matters for your use case. -| Mission | Mental Models Focus | +| Mission | Observations Focus | |---------|-------------------| | *Customer support agent* | Customer preferences, issue patterns, resolution history | | *Code review assistant* | Coding patterns, team conventions, common mistakes | @@ -162,13 +162,13 @@ Without a mission, the engine performs general-purpose consolidation. With a mis ## Configuration -Mental model consolidation runs automatically. You can monitor consolidation via the [Operations API](./api/operations). +Observation consolidation runs automatically. You can monitor consolidation via the [Operations API](./api/operations). --- ## Next Steps - [**Retain**](./retain) — How facts are stored and trigger consolidation -- [**Recall**](./retrieval) — How mental models are retrieved -- [**Reflect**](./reflect) — How the agentic loop uses mental models -- [**Reflections**](./api/reflections) — User-curated summaries for common queries +- [**Recall**](./retrieval) — How observations are retrieved +- [**Reflect**](./reflect) — How the agentic loop uses observations +- [**Mental Models**](./api/mental-models) — User-curated summaries for common queries diff --git a/hindsight-docs/docs/developer/reflect.mdx b/hindsight-docs/docs/developer/reflect.mdx index ab355a13..4ac91310 100644 --- a/hindsight-docs/docs/developer/reflect.mdx +++ b/hindsight-docs/docs/developer/reflect.mdx @@ -14,8 +14,8 @@ graph TB subgraph agent["Reflect Agent Loop"] A[Query] --> B{Need more info?} B -->|Yes| C[Call Tools] - C --> D[search_reflections] - C --> E[search_mental_models] + C --> D[search_mental_models] + C --> E[search_observations] C --> F[recall] C --> G[expand] D --> B @@ -34,9 +34,9 @@ graph TB Unlike simple retrieval, reflect is an **agentic system** that: 1. **Autonomously gathers evidence** — The agent decides what information it needs and calls appropriate tools -2. **Uses hierarchical retrieval** — Checks reflections first, then mental models, then raw facts +2. **Uses hierarchical retrieval** — Checks mental models first, then observations, then raw facts 3. **Applies disposition** — Shapes reasoning based on the bank's personality traits -4. **Cites sources** — Returns which memories and mental models were used +4. **Cites sources** — Returns which memories and observations were used ### The Agentic Loop @@ -44,8 +44,8 @@ The reflect agent runs in a loop with access to these tools: | Tool | Purpose | Priority | |------|---------|----------| -| `search_reflections` | User-curated summaries | Highest (check first) | -| `search_mental_models` | Consolidated knowledge | High | +| `search_mental_models` | User-curated summaries | Highest (check first) | +| `search_observations` | Consolidated knowledge | High | | `recall` | Raw facts (ground truth) | Fallback | | `expand` | Get more context for a memory | As needed | | `done` | Complete with final answer | When ready | @@ -59,13 +59,13 @@ The agent: The agent uses a smart retrieval hierarchy: -1. **[Reflections](/developer/api/reflections)** — User-curated summaries you've pre-computed for common queries -2. **[Mental Models](/developer/mental-models)** — Consolidated knowledge with freshness awareness -3. **Raw Facts** — Ground truth for verification when models are stale +1. **[Mental Models](/developer/api/mental-models)** — User-curated summaries you've pre-computed for common queries +2. **[Observations](/developer/observations)** — Consolidated knowledge with freshness awareness +3. **Raw Facts** — Ground truth for verification when observations are stale -**Reflections** are saved reflect responses that you create for frequently asked questions. They're checked first because they represent explicitly curated knowledge. See the [Reflections API](/developer/api/reflections) for how to create and manage them. +**Mental models** are saved reflect responses that you create for frequently asked questions. They're checked first because they represent explicitly curated knowledge. See the [Mental Models API](/developer/api/mental-models) for how to create and manage them. -If a mental model is marked as **stale**, the agent automatically verifies it against current facts. +If an observation is marked as **stale**, the agent automatically verifies it against current facts. --- @@ -85,7 +85,7 @@ Without reflect: With reflect: - **Consistent character**: A "detail-oriented, cautious" bank emphasizes risks and thorough planning -- **Evolving knowledge**: Mental models strengthen and adapt as evidence accumulates +- **Evolving knowledge**: Observations strengthen and adapt as evidence accumulates - **Contextual reasoning**: "Based on what I know about your team's remote work success..." - **Differentiated behavior**: Support bots sound diplomatic, code reviewers sound direct @@ -162,7 +162,7 @@ When you call `reflect()`: **Returns:** - **Response text** — Disposition-influenced answer from the agent - **based_on** — Evidence used: memories that grounded the response -- **trace** — Tool calls, LLM calls, and mental models accessed (when `include.tool_calls=True`) +- **trace** — Tool calls, LLM calls, and observations accessed (when `include.tool_calls=True`) - **structured_output** — Parsed response if `response_schema` was provided - **usage** — Token usage metrics @@ -183,8 +183,8 @@ When you call `reflect()`: "llm_calls": [ {"scope": "agent_1", "duration_ms": 1200} ], - "mental_models": [ - {"id": "mm-789", "name": "Alice", "type": "entity", "subtype": "structural"} + "observations": [ + {"id": "obs-789", "name": "Alice", "type": "entity", "subtype": "structural"} ] }, "usage": {"input_tokens": 1500, "output_tokens": 500, "total_tokens": 2000} @@ -204,13 +204,13 @@ Without disposition, all AI assistants sound the same. With disposition: - **Creative assistants** can be open to unconventional ideas - **Risk analysts** can be appropriately cautious -Disposition creates **consistent character** across conversations while mental models **evolve with evidence**. +Disposition creates **consistent character** across conversations while observations **evolve with evidence**. --- ## Next Steps -- [**Mental Models**](./mental-models) — How knowledge is consolidated +- [**Observations**](./observations) — How knowledge is consolidated - [**Retain**](./retain) — How rich facts are stored - [**Recall**](./retrieval) — How multi-strategy search works - [**Reflect API**](./api/reflect) — Code examples and parameters diff --git a/hindsight-docs/docs/developer/retain.md b/hindsight-docs/docs/developer/retain.md index ce29fd99..87b14b85 100644 --- a/hindsight-docs/docs/developer/retain.md +++ b/hindsight-docs/docs/developer/retain.md @@ -63,7 +63,7 @@ Hindsight distinguishes between **world** facts (about others) and **experience* | **experience** | Conversations and events | "I recommended Python to Alice" | -**Note:** Mental models are consolidated automatically in the background after `retain()` operations complete. This consolidation process synthesizes patterns from new facts into the bank's knowledge base. +**Note:** Observations are consolidated automatically in the background after `retain()` operations complete. This consolidation process synthesizes patterns from new facts into the bank's knowledge base. --- @@ -176,24 +176,24 @@ All stored in your isolated **memory bank**, ready for `recall()` and `reflect() --- -## Mental Model Consolidation +## Observation Consolidation -After `retain()` completes, Hindsight automatically triggers **mental model consolidation** in the background. This process: +After `retain()` completes, Hindsight automatically triggers **observation consolidation** in the background. This process: -1. Analyzes new facts against existing mental models -2. Creates new mental models when patterns emerge -3. Refines existing mental models with new evidence -4. Tracks which facts support each mental model +1. Analyzes new facts against existing observations +2. Creates new observations when patterns emerge +3. Refines existing observations with new evidence +4. Tracks which facts support each observation This happens asynchronously — your `retain()` call returns immediately while consolidation runs in the background. -See [Mental Models](./mental-models) for details on how consolidation works. +See [Observations](./observations) for details on how consolidation works. --- ## Next Steps -- [**Mental Models**](./mental-models) — How knowledge is consolidated after retain +- [**Observations**](./observations) — How knowledge is consolidated after retain - [**Recall**](./retrieval) — How multi-strategy search retrieves relevant memories -- [**Reflect**](./reflect) — How the agentic loop uses mental models +- [**Reflect**](./reflect) — How the agentic loop uses observations - [**Retain API**](./api/retain) — Code examples and parameters diff --git a/hindsight-docs/docs/developer/retrieval.md b/hindsight-docs/docs/developer/retrieval.md index b3093072..7af0aaca 100644 --- a/hindsight-docs/docs/developer/retrieval.md +++ b/hindsight-docs/docs/developer/retrieval.md @@ -133,7 +133,7 @@ Hindsight is built for AI agents, not humans. Traditional search systems return **Parameters you control:** - `max_tokens`: How much memory content to return (default: 4096 tokens) - `budget`: Search depth level (low, mid, high) -- `types`: Filter by world, experience, mental_model, or all +- `types`: Filter by world, experience, observation, or all - `tags`: Filter memories by visibility tags - `tags_match`: How to match tags (see [Recall API](./api/recall) for all options) diff --git a/hindsight-docs/docs/sdks/cli.md b/hindsight-docs/docs/sdks/cli.md index 305657a8..07268c6b 100644 --- a/hindsight-docs/docs/sdks/cli.md +++ b/hindsight-docs/docs/sdks/cli.md @@ -74,7 +74,7 @@ hindsight memory recall "hiking recommendations" \ --max-tokens 8192 # Filter by fact type -hindsight memory recall "query" --fact-type world,mental_model +hindsight memory recall "query" --fact-type world,observation # Show trace information hindsight memory recall "query" --trace @@ -206,7 +206,7 @@ The explorer provides an interactive terminal interface to: - **Browse memory banks** — View all banks and their statistics - **Search memories** — Run recall queries with real-time results - **Inspect entities** — Explore the knowledge graph and entity relationships -- **View facts** — Browse world facts, experiences, and mental models +- **View facts** — Browse world facts, experiences, and observations - **Navigate documents** — See source documents and their extracted memories ### Keyboard Shortcuts diff --git a/hindsight-docs/docs/sdks/nodejs.md b/hindsight-docs/docs/sdks/nodejs.md index 0067ca7c..38d87132 100644 --- a/hindsight-docs/docs/sdks/nodejs.md +++ b/hindsight-docs/docs/sdks/nodejs.md @@ -83,7 +83,7 @@ for (const r of response.results) { // With options const response = await client.recall('my-bank', 'What does Alice do?', { - types: ['world', 'mental_model'], // Filter by fact type + types: ['world', 'observation'], // Filter by fact type maxTokens: 4096, budget: 'high', // 'low', 'mid', or 'high' }); diff --git a/hindsight-docs/docs/sdks/python.md b/hindsight-docs/docs/sdks/python.md index 828e6e57..3cd82d1c 100644 --- a/hindsight-docs/docs/sdks/python.md +++ b/hindsight-docs/docs/sdks/python.md @@ -150,7 +150,7 @@ for r in results.results: results = client.recall( bank_id="my-bank", query="What does Alice do?", - types=["world", "mental_model"], # Filter by fact type + types=["world", "observation"], # Filter by fact type max_tokens=4096, budget="high", # low, mid, or high ) diff --git a/hindsight-docs/examples/api/reflections.py b/hindsight-docs/examples/api/mental-models.py similarity index 52% rename from hindsight-docs/examples/api/reflections.py rename to hindsight-docs/examples/api/mental-models.py index 805da30c..74e1f227 100644 --- a/hindsight-docs/examples/api/reflections.py +++ b/hindsight-docs/examples/api/mental-models.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 """ -Reflections API examples for Hindsight. -Run: python examples/api/reflections.py +Mental Models API examples for Hindsight. +Run: python examples/api/mental-models.py """ import os import time import requests HINDSIGHT_URL = os.getenv("HINDSIGHT_API_URL", "http://localhost:8888") -BANK_ID = "reflections-demo-bank" +BANK_ID = "mental-models-demo-bank" # ============================================================================= # Setup (not shown in docs) @@ -18,7 +18,7 @@ from hindsight_client import Hindsight client = Hindsight(base_url=HINDSIGHT_URL) # Create bank and seed some data -client.create_bank(bank_id=BANK_ID, name="Reflections Demo") +client.create_bank(bank_id=BANK_ID, name="Mental Models Demo") client.retain(bank_id=BANK_ID, content="The team prefers async communication via Slack") client.retain(bank_id=BANK_ID, content="For urgent issues, use the #incidents channel") client.retain(bank_id=BANK_ID, content="Weekly syncs happen every Monday at 10am") @@ -30,10 +30,10 @@ time.sleep(2) # Doc Examples # ============================================================================= -# [docs:create-reflection] -# Create a reflection (runs reflect in background) +# [docs:create-mental-model] +# Create a mental model (runs reflect in background) response = requests.post( - f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections", + f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models", json={ "name": "Team Communication Preferences", "source_query": "How does the team prefer to communicate?", @@ -44,66 +44,66 @@ result = response.json() # Returns an operation_id - check operations endpoint for completion print(f"Operation ID: {result['operation_id']}") -# [/docs:create-reflection] +# [/docs:create-mental-model] -# Wait for the reflection to be created +# Wait for the mental model to be created time.sleep(5) -# [docs:list-reflections] -# List all reflections in a bank -response = requests.get(f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections") -reflections = response.json() +# [docs:list-mental-models] +# List all mental models in a bank +response = requests.get(f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models") +mental_models = response.json() -for reflection in reflections["items"]: - print(f"- {reflection['name']}: {reflection['source_query']}") -# [/docs:list-reflections] +for mental_model in mental_models["items"]: + print(f"- {mental_model['name']}: {mental_model['source_query']}") +# [/docs:list-mental-models] -# Get the reflection ID for subsequent examples -reflection_id = reflections["items"][0]["id"] if reflections["items"] else None +# Get the mental model ID for subsequent examples +mental_model_id = mental_models["items"][0]["id"] if mental_models["items"] else None -if reflection_id: - # [docs:get-reflection] - # Get a specific reflection +if mental_model_id: + # [docs:get-mental-model] + # Get a specific mental model response = requests.get( - f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections/{reflection_id}" + f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models/{mental_model_id}" ) - reflection = response.json() + mental_model = response.json() - print(f"Name: {reflection['name']}") - print(f"Content: {reflection['content']}") - print(f"Last refreshed: {reflection['last_refreshed_at']}") - # [/docs:get-reflection] + print(f"Name: {mental_model['name']}") + print(f"Content: {mental_model['content']}") + print(f"Last refreshed: {mental_model['last_refreshed_at']}") + # [/docs:get-mental-model] - # [docs:refresh-reflection] - # Refresh a reflection to update with current knowledge + # [docs:refresh-mental-model] + # Refresh a mental model to update with current knowledge response = requests.post( - f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections/{reflection_id}/refresh" + f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models/{mental_model_id}/refresh" ) result = response.json() print(f"Refresh operation ID: {result['operation_id']}") - # [/docs:refresh-reflection] + # [/docs:refresh-mental-model] - # [docs:update-reflection] - # Update a reflection's name + # [docs:update-mental-model] + # Update a mental model's name response = requests.patch( - f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections/{reflection_id}", + f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models/{mental_model_id}", json={"name": "Updated Team Communication Preferences"} ) updated = response.json() print(f"Updated name: {updated['name']}") - # [/docs:update-reflection] + # [/docs:update-mental-model] - # [docs:delete-reflection] - # Delete a reflection + # [docs:delete-mental-model] + # Delete a mental model requests.delete( - f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/reflections/{reflection_id}" + f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}/mental-models/{mental_model_id}" ) - # [/docs:delete-reflection] + # [/docs:delete-mental-model] # ============================================================================= @@ -111,4 +111,4 @@ if reflection_id: # ============================================================================= requests.delete(f"{HINDSIGHT_URL}/v1/default/banks/{BANK_ID}") -print("reflections.py: All examples passed") +print("mental-models.py: All examples passed") diff --git a/hindsight-docs/examples/api/recall.py b/hindsight-docs/examples/api/recall.py index a824e668..04012921 100644 --- a/hindsight-docs/examples/api/recall.py +++ b/hindsight-docs/examples/api/recall.py @@ -67,31 +67,31 @@ experience = client.recall( # [/docs:recall-experience-only] -# [docs:recall-mental-models-only] -# Only mental models (consolidated knowledge) -mental_models = client.recall( +# [docs:recall-observations-only] +# Only observations (consolidated knowledge) +observations = client.recall( bank_id="my-bank", query="What patterns have I learned?", - types=["mental_model"] + types=["observation"] ) -# [/docs:recall-mental-models-only] +# [/docs:recall-observations-only] -# [docs:recall-with-mental-models] -# Include mental models in recall +# [docs:recall-with-observations] +# Include observations in recall results = client.recall( bank_id="my-bank", query="What programming languages does Alice prefer?", - types=["world", "experience", "mental_model"] + types=["world", "experience", "observation"] ) -# Mental models only -models = client.recall( +# Observations only +observations = client.recall( bank_id="my-bank", query="What patterns have I learned?", - types=["mental_model"] + types=["observation"] ) -# [/docs:recall-with-mental-models] +# [/docs:recall-with-observations] # [docs:recall-token-budget] diff --git a/hindsight-docs/examples/api/recall.sh b/hindsight-docs/examples/api/recall.sh index 55872ef1..30de6c95 100755 --- a/hindsight-docs/examples/api/recall.sh +++ b/hindsight-docs/examples/api/recall.sh @@ -29,7 +29,7 @@ hindsight memory recall my-bank "hiking recommendations" \ # [docs:recall-fact-type] -hindsight memory recall my-bank "query" --fact-type world,mental_model +hindsight memory recall my-bank "query" --fact-type world,observation # [/docs:recall-fact-type] diff --git a/hindsight-docs/examples/api/reflect.mjs b/hindsight-docs/examples/api/reflect.mjs index 20aab518..0295d142 100644 --- a/hindsight-docs/examples/api/reflect.mjs +++ b/hindsight-docs/examples/api/reflect.mjs @@ -83,13 +83,15 @@ const responseSchema = { required: ['recommendation', 'confidence', 'key_factors'], }; -const structuredResponse = await client.reflect('hiring-team', 'Should we hire Alice for the ML team lead position?', { +const structuredResponse = await client.reflect('my-bank', 'What do you know about Alice and her career?', { responseSchema: responseSchema, }); -// Structured output -console.log(structuredResponse.structuredOutput.recommendation); -console.log(structuredResponse.structuredOutput.keyFactors); +// Structured output (if returned) +if (structuredResponse.structuredOutput) { + console.log('Recommendation:', structuredResponse.structuredOutput.recommendation || 'N/A'); + console.log('Key factors:', structuredResponse.structuredOutput.key_factors || []); +} // [/docs:reflect-structured-output] diff --git a/hindsight-docs/examples/api/reflect.sh b/hindsight-docs/examples/api/reflect.sh index b26ef00f..47afaf66 100755 --- a/hindsight-docs/examples/api/reflect.sh +++ b/hindsight-docs/examples/api/reflect.sh @@ -33,20 +33,25 @@ hindsight memory reflect my-bank "Summarize my week" --budget high # [docs:reflect-structured-output] # First, create a JSON schema file schema.json: -# { -# "type": "object", -# "properties": { -# "recommendation": {"type": "string"}, -# "confidence": {"type": "string", "enum": ["low", "medium", "high"]}, -# "key_factors": {"type": "array", "items": {"type": "string"}} -# }, -# "required": ["recommendation", "confidence", "key_factors"] -# } +cat > schema.json << 'EOF' +{ + "type": "object", + "properties": { + "recommendation": {"type": "string"}, + "confidence": {"type": "string", "enum": ["low", "medium", "high"]}, + "key_factors": {"type": "array", "items": {"type": "string"}} + }, + "required": ["recommendation", "confidence", "key_factors"] +} +EOF # Then use the --schema flag: hindsight memory reflect hiring-team \ "Should we hire Alice for the ML team lead position?" \ --schema schema.json + +# Cleanup the temporary schema file +rm -f schema.json # [/docs:reflect-structured-output] diff --git a/hindsight-docs/sidebars.ts b/hindsight-docs/sidebars.ts index 266b7832..93d0d696 100644 --- a/hindsight-docs/sidebars.ts +++ b/hindsight-docs/sidebars.ts @@ -29,8 +29,8 @@ const sidebars: SidebarsConfig = { }, { type: 'doc', - id: 'developer/mental-models', - label: 'Mental Models', + id: 'developer/observations', + label: 'Observations', }, { type: 'doc', @@ -81,8 +81,8 @@ const sidebars: SidebarsConfig = { }, { type: 'doc', - id: 'developer/api/reflections', - label: 'Reflections', + id: 'developer/api/mental-models', + label: 'Mental Models', }, { type: 'doc', diff --git a/hindsight-docs/static/openapi.json b/hindsight-docs/static/openapi.json index 31f63d6c..e6216587 100644 --- a/hindsight-docs/static/openapi.json +++ b/hindsight-docs/static/openapi.json @@ -804,14 +804,14 @@ } } }, - "/v1/default/banks/{bank_id}/reflections": { + "/v1/default/banks/{bank_id}/mental-models": { "get": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "List reflections", + "summary": "List mental models", "description": "List user-curated living documents that stay current.", - "operationId": "list_reflections", + "operationId": "list_mental_models", "parameters": [ { "name": "bank_id", @@ -906,7 +906,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReflectionListResponse" + "$ref": "#/components/schemas/MentalModelListResponse" } } } @@ -925,11 +925,11 @@ }, "post": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "Create reflection", - "description": "Create a reflection by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status.", - "operationId": "create_reflection", + "summary": "Create mental model", + "description": "Create a mental model by running reflect with the source query in the background. Returns an operation ID to track progress. The content is auto-generated by the reflect endpoint. Use the operations endpoint to check completion status.", + "operationId": "create_mental_model", "parameters": [ { "name": "bank_id", @@ -962,7 +962,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateReflectionRequest" + "$ref": "#/components/schemas/CreateMentalModelRequest" } } } @@ -973,7 +973,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateReflectionResponse" + "$ref": "#/components/schemas/CreateMentalModelResponse" } } } @@ -991,14 +991,14 @@ } } }, - "/v1/default/banks/{bank_id}/reflections/{reflection_id}": { + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}": { "get": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "Get reflection", - "description": "Get a specific reflection by ID.", - "operationId": "get_reflection", + "summary": "Get mental model", + "description": "Get a specific mental model by ID.", + "operationId": "get_mental_model", "parameters": [ { "name": "bank_id", @@ -1010,12 +1010,12 @@ } }, { - "name": "reflection_id", + "name": "mental_model_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Reflection Id" + "title": "Mental Model Id" } }, { @@ -1041,7 +1041,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReflectionResponse" + "$ref": "#/components/schemas/MentalModelResponse" } } } @@ -1060,11 +1060,11 @@ }, "patch": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "Update reflection", - "description": "Update a reflection's name.", - "operationId": "update_reflection", + "summary": "Update mental model", + "description": "Update a mental model's name.", + "operationId": "update_mental_model", "parameters": [ { "name": "bank_id", @@ -1076,12 +1076,12 @@ } }, { - "name": "reflection_id", + "name": "mental_model_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Reflection Id" + "title": "Mental Model Id" } }, { @@ -1106,7 +1106,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateReflectionRequest" + "$ref": "#/components/schemas/UpdateMentalModelRequest" } } } @@ -1117,7 +1117,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReflectionResponse" + "$ref": "#/components/schemas/MentalModelResponse" } } } @@ -1136,11 +1136,11 @@ }, "delete": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "Delete reflection", - "description": "Delete a reflection.", - "operationId": "delete_reflection", + "summary": "Delete mental model", + "description": "Delete a mental model.", + "operationId": "delete_mental_model", "parameters": [ { "name": "bank_id", @@ -1152,12 +1152,12 @@ } }, { - "name": "reflection_id", + "name": "mental_model_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Reflection Id" + "title": "Mental Model Id" } }, { @@ -1199,14 +1199,14 @@ } } }, - "/v1/default/banks/{bank_id}/reflections/{reflection_id}/refresh": { + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh": { "post": { "tags": [ - "Reflections" + "Mental Models" ], - "summary": "Refresh reflection", + "summary": "Refresh mental model", "description": "Submit an async task to re-run the source query through reflect and update the content.", - "operationId": "refresh_reflection", + "operationId": "refresh_mental_model", "parameters": [ { "name": "bank_id", @@ -1218,12 +1218,12 @@ } }, { - "name": "reflection_id", + "name": "mental_model_id", "in": "path", "required": true, "schema": { "type": "string", - "title": "Reflection Id" + "title": "Mental Model Id" } }, { @@ -2690,14 +2690,14 @@ } } }, - "/v1/default/banks/{bank_id}/mental-models": { + "/v1/default/banks/{bank_id}/observations": { "delete": { "tags": [ "Banks" ], - "summary": "Clear all mental models", - "description": "Delete all mental models for a memory bank. This is useful for resetting the consolidated knowledge.", - "operationId": "clear_mental_models", + "summary": "Clear all observations", + "description": "Delete all observations for a memory bank. This is useful for resetting the consolidated knowledge.", + "operationId": "clear_observations", "parameters": [ { "name": "bank_id", @@ -2755,7 +2755,7 @@ "Banks" ], "summary": "Trigger consolidation", - "description": "Run memory consolidation to create/update mental models from recent memories.", + "description": "Run memory consolidation to create/update observations from recent memories.", "operationId": "trigger_consolidation", "parameters": [ { @@ -3260,13 +3260,13 @@ "pending_consolidation": { "type": "integer", "title": "Pending Consolidation", - "description": "Number of memories not yet processed into mental models", + "description": "Number of memories not yet processed into observations", "default": 0 }, - "total_mental_models": { + "total_observations": { "type": "integer", - "title": "Total Mental Models", - "description": "Total number of mental models", + "title": "Total Observations", + "description": "Total number of observations", "default": 0 } }, @@ -3315,8 +3315,8 @@ "pending_operations": 2, "total_documents": 10, "total_links": 300, - "total_mental_models": 45, - "total_nodes": 150 + "total_nodes": 150, + "total_observations": 45 } }, "Budget": { @@ -3571,12 +3571,12 @@ "title": "CreateDirectiveRequest", "description": "Request model for creating a directive." }, - "CreateReflectionRequest": { + "CreateMentalModelRequest": { "properties": { "name": { "type": "string", "title": "Name", - "description": "Human-readable name for the reflection" + "description": "Human-readable name for the mental model" }, "source_query": { "type": "string", @@ -3605,8 +3605,8 @@ "name", "source_query" ], - "title": "CreateReflectionRequest", - "description": "Request model for creating a reflection.", + "title": "CreateMentalModelRequest", + "description": "Request model for creating a mental model.", "example": { "max_tokens": 2048, "name": "Team Communication Preferences", @@ -3616,7 +3616,7 @@ ] } }, - "CreateReflectionResponse": { + "CreateMentalModelResponse": { "properties": { "operation_id": { "type": "string", @@ -3628,8 +3628,8 @@ "required": [ "operation_id" ], - "title": "CreateReflectionResponse", - "description": "Response model for reflection creation." + "title": "CreateMentalModelResponse", + "description": "Response model for mental model creation." }, "DeleteDocumentResponse": { "properties": { @@ -4192,10 +4192,10 @@ }, "FeaturesInfo": { "properties": { - "mental_models": { + "observations": { "type": "boolean", - "title": "Mental Models", - "description": "Whether mental models (auto-consolidation) are enabled" + "title": "Observations", + "description": "Whether observations (auto-consolidation) are enabled" }, "mcp": { "type": "boolean", @@ -4210,7 +4210,7 @@ }, "type": "object", "required": [ - "mental_models", + "observations", "mcp", "worker" ], @@ -4605,6 +4605,99 @@ "timestamp": "2024-01-15T10:30:00Z" } }, + "MentalModelListResponse": { + "properties": { + "items": { + "items": { + "$ref": "#/components/schemas/MentalModelResponse" + }, + "type": "array", + "title": "Items" + } + }, + "type": "object", + "required": [ + "items" + ], + "title": "MentalModelListResponse", + "description": "Response model for listing mental models." + }, + "MentalModelResponse": { + "properties": { + "id": { + "type": "string", + "title": "Id" + }, + "bank_id": { + "type": "string", + "title": "Bank Id" + }, + "name": { + "type": "string", + "title": "Name" + }, + "source_query": { + "type": "string", + "title": "Source Query" + }, + "content": { + "type": "string", + "title": "Content" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tags" + }, + "last_refreshed_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Last Refreshed At" + }, + "created_at": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Created At" + }, + "reflect_response": { + "anyOf": [ + { + "additionalProperties": true, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Reflect Response", + "description": "Full reflect API response payload including based_on facts and observations" + } + }, + "type": "object", + "required": [ + "id", + "bank_id", + "name", + "source_query", + "content" + ], + "title": "MentalModelResponse", + "description": "Response model for a mental model (stored reflect response)." + }, "OperationResponse": { "properties": { "id": { @@ -4827,7 +4920,7 @@ } ], "title": "Types", - "description": "List of fact types to recall: 'world', 'experience', 'mental_model'. Defaults to world and experience if not specified. Note: 'opinion' is accepted but ignored (opinions are excluded from recall)." + "description": "List of fact types to recall: 'world', 'experience', 'observation'. Defaults to world and experience if not specified. Note: 'opinion' is accepted but ignored (opinions are excluded from recall)." }, "budget": { "$ref": "#/components/schemas/Budget", @@ -5316,54 +5409,6 @@ "title": "ReflectLLMCall", "description": "An LLM call made during reflect agent execution." }, - "ReflectMentalModel": { - "properties": { - "id": { - "type": "string", - "title": "Id", - "description": "Mental model ID" - }, - "name": { - "type": "string", - "title": "Name", - "description": "Mental model name" - }, - "type": { - "type": "string", - "title": "Type", - "description": "Mental model type: entity, concept, event" - }, - "subtype": { - "type": "string", - "title": "Subtype", - "description": "Mental model subtype: structural, emergent, learned, directive" - }, - "observations": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Observations", - "description": "Observations for directive mental models (subtype='directive')" - } - }, - "type": "object", - "required": [ - "id", - "name", - "type", - "subtype" - ], - "title": "ReflectMentalModel", - "description": "A mental model accessed during reflect." - }, "ReflectRequest": { "properties": { "query": { @@ -5564,9 +5609,9 @@ "scope": "agent_1" } ], - "mental_models": [ + "observations": [ { - "id": "mm-1", + "id": "obs-1", "name": "AI Technology", "subtype": "structural", "type": "concept" @@ -5653,113 +5698,12 @@ "type": "array", "title": "Llm Calls", "description": "LLM calls made during reflection" - }, - "mental_models": { - "items": { - "$ref": "#/components/schemas/ReflectMentalModel" - }, - "type": "array", - "title": "Mental Models", - "description": "Mental models used during reflection (includes directives with subtype='directive')" } }, "type": "object", "title": "ReflectTrace", "description": "Execution trace of LLM and tool calls during reflection." }, - "ReflectionListResponse": { - "properties": { - "items": { - "items": { - "$ref": "#/components/schemas/ReflectionResponse" - }, - "type": "array", - "title": "Items" - } - }, - "type": "object", - "required": [ - "items" - ], - "title": "ReflectionListResponse", - "description": "Response model for listing reflections." - }, - "ReflectionResponse": { - "properties": { - "id": { - "type": "string", - "title": "Id" - }, - "bank_id": { - "type": "string", - "title": "Bank Id" - }, - "name": { - "type": "string", - "title": "Name" - }, - "source_query": { - "type": "string", - "title": "Source Query" - }, - "content": { - "type": "string", - "title": "Content" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Tags" - }, - "last_refreshed_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Last Refreshed At" - }, - "created_at": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Created At" - }, - "reflect_response": { - "anyOf": [ - { - "additionalProperties": true, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Reflect Response", - "description": "Full reflect API response payload including based_on facts and mental_models" - } - }, - "type": "object", - "required": [ - "id", - "bank_id", - "name", - "source_query", - "content" - ], - "title": "ReflectionResponse", - "description": "Response model for a reflection." - }, "RetainRequest": { "properties": { "items": { @@ -6028,7 +5972,7 @@ "title": "UpdateDispositionRequest", "description": "Request model for updating disposition traits." }, - "UpdateReflectionRequest": { + "UpdateMentalModelRequest": { "properties": { "name": { "anyOf": [ @@ -6040,12 +5984,12 @@ } ], "title": "Name", - "description": "New name for the reflection" + "description": "New name for the mental model" } }, "type": "object", - "title": "UpdateReflectionRequest", - "description": "Request model for updating a reflection.", + "title": "UpdateMentalModelRequest", + "description": "Request model for updating a mental model.", "example": { "name": "Updated Team Communication Preferences" } @@ -6106,7 +6050,7 @@ "api_version": "1.0.0", "features": { "mcp": true, - "mental_models": false, + "observations": false, "worker": true } } diff --git a/hindsight-integrations/litellm/hindsight_litellm/wrappers.py b/hindsight-integrations/litellm/hindsight_litellm/wrappers.py index 7afc4bb2..ccfb5451 100644 --- a/hindsight-integrations/litellm/hindsight_litellm/wrappers.py +++ b/hindsight-integrations/litellm/hindsight_litellm/wrappers.py @@ -111,7 +111,7 @@ def recall( query: The query string to search memories for bank_id: Override the configured bank_id. For multi-user support, use different bank_ids per user (e.g., f"user-{user_id}") - fact_types: Filter by fact types (world, experience, mental_model) + fact_types: Filter by fact types (world, experience, observation) budget: Recall budget level (low, mid, high) - controls how many memories are returned max_tokens: Maximum tokens for memory context hindsight_api_url: Override the configured API URL @@ -1523,7 +1523,7 @@ def wrap_openai( - store_conversations: Whether to store conversations (default: True) - inject_memories: Whether to inject memories (default: True) - budget: Recall budget level - low/mid/high (default: "mid") - - fact_types: Filter by fact types (world/experience/mental_model) + - fact_types: Filter by fact types (world/experience/observation) - max_memories: Max memories to inject (None = no limit) - max_memory_tokens: Max tokens for memory context (default: 4096) - use_reflect: Use reflect API instead of recall (default: False) @@ -1623,7 +1623,7 @@ def wrap_anthropic( - store_conversations: Whether to store conversations (default: True) - inject_memories: Whether to inject memories (default: True) - budget: Recall budget level - low/mid/high (default: "mid") - - fact_types: Filter by fact types (world/experience/mental_model) + - fact_types: Filter by fact types (world/experience/observation) - max_memories: Max memories to inject (None = no limit) - max_memory_tokens: Max tokens for memory context (default: 4096) - use_reflect: Use reflect API instead of recall (default: False)