hindsight-api 0.0.21__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. hindsight_api/__init__.py +10 -2
  2. hindsight_api/alembic/README +1 -0
  3. hindsight_api/alembic/env.py +146 -0
  4. hindsight_api/alembic/script.py.mako +28 -0
  5. hindsight_api/alembic/versions/5a366d414dce_initial_schema.py +274 -0
  6. hindsight_api/alembic/versions/b7c4d8e9f1a2_add_chunks_table.py +70 -0
  7. hindsight_api/alembic/versions/c8e5f2a3b4d1_add_retain_params_to_documents.py +39 -0
  8. hindsight_api/alembic/versions/d9f6a3b4c5e2_rename_bank_to_interactions.py +48 -0
  9. hindsight_api/alembic/versions/e0a1b2c3d4e5_disposition_to_3_traits.py +62 -0
  10. hindsight_api/alembic/versions/rename_personality_to_disposition.py +65 -0
  11. hindsight_api/api/__init__.py +2 -4
  12. hindsight_api/api/http.py +112 -164
  13. hindsight_api/api/mcp.py +2 -1
  14. hindsight_api/config.py +154 -0
  15. hindsight_api/engine/__init__.py +7 -2
  16. hindsight_api/engine/cross_encoder.py +225 -16
  17. hindsight_api/engine/embeddings.py +198 -19
  18. hindsight_api/engine/entity_resolver.py +56 -29
  19. hindsight_api/engine/llm_wrapper.py +147 -106
  20. hindsight_api/engine/memory_engine.py +337 -192
  21. hindsight_api/engine/response_models.py +15 -17
  22. hindsight_api/engine/retain/bank_utils.py +25 -35
  23. hindsight_api/engine/retain/entity_processing.py +5 -5
  24. hindsight_api/engine/retain/fact_extraction.py +86 -24
  25. hindsight_api/engine/retain/fact_storage.py +1 -1
  26. hindsight_api/engine/retain/link_creation.py +12 -6
  27. hindsight_api/engine/retain/link_utils.py +50 -56
  28. hindsight_api/engine/retain/observation_regeneration.py +264 -0
  29. hindsight_api/engine/retain/orchestrator.py +31 -44
  30. hindsight_api/engine/retain/types.py +14 -0
  31. hindsight_api/engine/search/reranking.py +6 -10
  32. hindsight_api/engine/search/retrieval.py +2 -2
  33. hindsight_api/engine/search/think_utils.py +59 -30
  34. hindsight_api/engine/search/tracer.py +1 -1
  35. hindsight_api/main.py +201 -0
  36. hindsight_api/migrations.py +61 -39
  37. hindsight_api/models.py +1 -2
  38. hindsight_api/pg0.py +17 -36
  39. hindsight_api/server.py +43 -0
  40. {hindsight_api-0.0.21.dist-info → hindsight_api-0.1.1.dist-info}/METADATA +2 -3
  41. hindsight_api-0.1.1.dist-info/RECORD +60 -0
  42. hindsight_api-0.1.1.dist-info/entry_points.txt +2 -0
  43. hindsight_api/cli.py +0 -128
  44. hindsight_api/web/__init__.py +0 -12
  45. hindsight_api/web/server.py +0 -109
  46. hindsight_api-0.0.21.dist-info/RECORD +0 -50
  47. hindsight_api-0.0.21.dist-info/entry_points.txt +0 -2
  48. {hindsight_api-0.0.21.dist-info → hindsight_api-0.1.1.dist-info}/WHEEL +0 -0
hindsight_api/api/http.py CHANGED
@@ -36,27 +36,13 @@ from pydantic import BaseModel, Field, ConfigDict
36
36
  from hindsight_api import MemoryEngine
37
37
  from hindsight_api.engine.memory_engine import Budget
38
38
  from hindsight_api.engine.db_utils import acquire_with_retry
39
+ from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
39
40
  from hindsight_api.metrics import get_metrics_collector, initialize_metrics, create_metrics_collector
40
41
 
41
42
 
42
43
  logger = logging.getLogger(__name__)
43
44
 
44
45
 
45
- class MetadataFilter(BaseModel):
46
- """Filter for metadata fields. Matches records where (key=value) OR (key not set) when match_unset=True."""
47
- model_config = ConfigDict(json_schema_extra={
48
- "example": {
49
- "key": "source",
50
- "value": "slack",
51
- "match_unset": True
52
- }
53
- })
54
-
55
- key: str = Field(description="Metadata key to filter on")
56
- value: Optional[str] = Field(default=None, description="Value to match. If None with match_unset=True, matches any record where key is not set.")
57
- match_unset: bool = Field(default=True, description="If True, also match records where this metadata key is not set")
58
-
59
-
60
46
  class EntityIncludeOptions(BaseModel):
61
47
  """Options for including entity observations in recall results."""
62
48
  max_tokens: int = Field(default=500, description="Maximum tokens for entity observations")
@@ -89,7 +75,6 @@ class RecallRequest(BaseModel):
89
75
  "max_tokens": 4096,
90
76
  "trace": True,
91
77
  "query_timestamp": "2023-05-30T23:40:00",
92
- "filters": [{"key": "source", "value": "slack", "match_unset": True}],
93
78
  "include": {
94
79
  "entities": {
95
80
  "max_tokens": 500
@@ -104,7 +89,6 @@ class RecallRequest(BaseModel):
104
89
  max_tokens: int = 4096
105
90
  trace: bool = False
106
91
  query_timestamp: Optional[str] = Field(default=None, description="ISO format date string (e.g., '2023-05-30T23:40:00')")
107
- filters: Optional[List[MetadataFilter]] = Field(default=None, description="Filter by metadata. Multiple filters are ANDed together.")
108
92
  include: IncludeOptions = Field(default_factory=IncludeOptions, description="Options for including additional data (entities are included by default)")
109
93
 
110
94
 
@@ -362,7 +346,6 @@ class ReflectRequest(BaseModel):
362
346
  "query": "What do you think about artificial intelligence?",
363
347
  "budget": "low",
364
348
  "context": "This is for a research paper on AI ethics",
365
- "filters": [{"key": "source", "value": "slack", "match_unset": True}],
366
349
  "include": {
367
350
  "facts": {}
368
351
  }
@@ -372,7 +355,6 @@ class ReflectRequest(BaseModel):
372
355
  query: str
373
356
  budget: Budget = Budget.LOW
374
357
  context: Optional[str] = None
375
- filters: Optional[List[MetadataFilter]] = Field(default=None, description="Filter by metadata. Multiple filters are ANDed together.")
376
358
  include: ReflectIncludeOptions = Field(default_factory=ReflectIncludeOptions, description="Options for including additional data (disabled by default)")
377
359
 
378
360
 
@@ -439,24 +421,18 @@ class BanksResponse(BaseModel):
439
421
 
440
422
 
441
423
  class DispositionTraits(BaseModel):
442
- """Disposition traits based on Big Five model."""
424
+ """Disposition traits that influence how memories are formed and interpreted."""
443
425
  model_config = ConfigDict(json_schema_extra={
444
426
  "example": {
445
- "openness": 0.8,
446
- "conscientiousness": 0.6,
447
- "extraversion": 0.5,
448
- "agreeableness": 0.7,
449
- "neuroticism": 0.3,
450
- "bias_strength": 0.7
427
+ "skepticism": 3,
428
+ "literalism": 3,
429
+ "empathy": 3
451
430
  }
452
431
  })
453
432
 
454
- openness: float = Field(ge=0.0, le=1.0, description="Openness to experience (0-1)")
455
- conscientiousness: float = Field(ge=0.0, le=1.0, description="Conscientiousness (0-1)")
456
- extraversion: float = Field(ge=0.0, le=1.0, description="Extraversion (0-1)")
457
- agreeableness: float = Field(ge=0.0, le=1.0, description="Agreeableness (0-1)")
458
- neuroticism: float = Field(ge=0.0, le=1.0, description="Neuroticism (0-1)")
459
- bias_strength: float = Field(ge=0.0, le=1.0, description="How strongly disposition influences opinions (0-1)")
433
+ skepticism: int = Field(ge=1, le=5, description="How skeptical vs trusting (1=trusting, 5=skeptical)")
434
+ literalism: int = Field(ge=1, le=5, description="How literally to interpret information (1=flexible, 5=literal)")
435
+ empathy: int = Field(ge=1, le=5, description="How much to consider emotional context (1=detached, 5=empathetic)")
460
436
 
461
437
 
462
438
  class BankProfileResponse(BaseModel):
@@ -466,12 +442,9 @@ class BankProfileResponse(BaseModel):
466
442
  "bank_id": "user123",
467
443
  "name": "Alice",
468
444
  "disposition": {
469
- "openness": 0.8,
470
- "conscientiousness": 0.6,
471
- "extraversion": 0.5,
472
- "agreeableness": 0.7,
473
- "neuroticism": 0.3,
474
- "bias_strength": 0.7
445
+ "skepticism": 3,
446
+ "literalism": 3,
447
+ "empathy": 3
475
448
  },
476
449
  "background": "I am a software engineer with 10 years of experience in startups"
477
450
  }
@@ -500,7 +473,7 @@ class AddBackgroundRequest(BaseModel):
500
473
  content: str = Field(description="New background information to add or merge")
501
474
  update_disposition: bool = Field(
502
475
  default=True,
503
- description="If true, infer Big Five disposition traits from the merged background (default: true)"
476
+ description="If true, infer disposition traits from the merged background (default: true)"
504
477
  )
505
478
 
506
479
 
@@ -510,12 +483,9 @@ class BackgroundResponse(BaseModel):
510
483
  "example": {
511
484
  "background": "I was born in Texas. I am a software engineer with 10 years of experience.",
512
485
  "disposition": {
513
- "openness": 0.7,
514
- "conscientiousness": 0.6,
515
- "extraversion": 0.5,
516
- "agreeableness": 0.8,
517
- "neuroticism": 0.4,
518
- "bias_strength": 0.6
486
+ "skepticism": 3,
487
+ "literalism": 3,
488
+ "empathy": 3
519
489
  }
520
490
  }
521
491
  })
@@ -543,12 +513,9 @@ class BankListResponse(BaseModel):
543
513
  "bank_id": "user123",
544
514
  "name": "Alice",
545
515
  "disposition": {
546
- "openness": 0.5,
547
- "conscientiousness": 0.5,
548
- "extraversion": 0.5,
549
- "agreeableness": 0.5,
550
- "neuroticism": 0.5,
551
- "bias_strength": 0.5
516
+ "skepticism": 3,
517
+ "literalism": 3,
518
+ "empathy": 3
552
519
  },
553
520
  "background": "I am a software engineer",
554
521
  "created_at": "2024-01-15T10:30:00Z",
@@ -567,12 +534,9 @@ class CreateBankRequest(BaseModel):
567
534
  "example": {
568
535
  "name": "Alice",
569
536
  "disposition": {
570
- "openness": 0.8,
571
- "conscientiousness": 0.6,
572
- "extraversion": 0.5,
573
- "agreeableness": 0.7,
574
- "neuroticism": 0.3,
575
- "bias_strength": 0.7
537
+ "skepticism": 3,
538
+ "literalism": 3,
539
+ "empathy": 3
576
540
  },
577
541
  "background": "I am a creative software engineer with 10 years of experience"
578
542
  }
@@ -715,13 +679,13 @@ class DeleteResponse(BaseModel):
715
679
  success: bool
716
680
 
717
681
 
718
- def create_app(memory: MemoryEngine, run_migrations: bool = True, initialize_memory: bool = True) -> FastAPI:
682
+ def create_app(memory: MemoryEngine, initialize_memory: bool = True) -> FastAPI:
719
683
  """
720
684
  Create and configure the FastAPI application.
721
685
 
722
686
  Args:
723
- memory: MemoryEngine instance (already initialized with required parameters)
724
- run_migrations: Whether to run database migrations on startup (default: True)
687
+ memory: MemoryEngine instance (already initialized with required parameters).
688
+ Migrations are controlled by the MemoryEngine's run_migrations parameter.
725
689
  initialize_memory: Whether to initialize memory system on startup (default: True)
726
690
 
727
691
  Returns:
@@ -752,16 +716,11 @@ def create_app(memory: MemoryEngine, run_migrations: bool = True, initialize_mem
752
716
  app.state.prometheus_reader = None
753
717
  # Metrics collector is already initialized as no-op by default
754
718
 
755
- # Startup: Initialize database and memory system
719
+ # Startup: Initialize database and memory system (migrations run inside initialize if enabled)
756
720
  if initialize_memory:
757
721
  await memory.initialize()
758
722
  logging.info("Memory system initialized")
759
723
 
760
- if run_migrations:
761
- from hindsight_api.migrations import run_migrations as do_migrations
762
- do_migrations(memory.db_url)
763
- logging.info("Database migrations applied")
764
-
765
724
 
766
725
 
767
726
  yield
@@ -770,9 +729,11 @@ def create_app(memory: MemoryEngine, run_migrations: bool = True, initialize_mem
770
729
  await memory.close()
771
730
  logging.info("Memory system closed")
772
731
 
732
+ from hindsight_api import __version__
733
+
773
734
  app = FastAPI(
774
735
  title="Hindsight HTTP API",
775
- version="1.0.0",
736
+ version=__version__,
776
737
  description="HTTP API for Hindsight",
777
738
  contact={
778
739
  "name": "Memory System",
@@ -834,7 +795,8 @@ def _register_routes(app: FastAPI):
834
795
  response_model=GraphDataResponse,
835
796
  summary="Get memory graph data",
836
797
  description="Retrieve graph data for visualization, optionally filtered by type (world/experience/opinion). Limited to 1000 most recent items.",
837
- operation_id="get_graph"
798
+ operation_id="get_graph",
799
+ tags=["Memory"]
838
800
  )
839
801
  async def api_graph(bank_id: str,
840
802
  type: Optional[str] = None
@@ -855,7 +817,8 @@ def _register_routes(app: FastAPI):
855
817
  response_model=ListMemoryUnitsResponse,
856
818
  summary="List memory units",
857
819
  description="List memory units with pagination and optional full-text search. Supports filtering by type. Results are sorted by most recent first (mentioned_at DESC, then created_at DESC).",
858
- operation_id="list_memories"
820
+ operation_id="list_memories",
821
+ tags=["Memory"]
859
822
  )
860
823
  async def api_list(bank_id: str,
861
824
  type: Optional[str] = None,
@@ -896,34 +859,22 @@ def _register_routes(app: FastAPI):
896
859
  "/v1/default/banks/{bank_id}/memories/recall",
897
860
  response_model=RecallResponse,
898
861
  summary="Recall memory",
899
- description="""
900
- Recall memory using semantic similarity and spreading activation.
901
-
902
- The type parameter is optional and must be one of:
903
- - 'world': General knowledge about people, places, events, and things that happen
904
- - 'experience': Memories about experience, conversations, actions taken, and tasks performed
905
- - 'opinion': The bank's formed beliefs, perspectives, and viewpoints
906
-
907
- Set include_entities=true to get entity observations alongside recall results.
908
- """,
909
- operation_id="recall_memories"
862
+ description="Recall memory using semantic similarity and spreading activation.\n\n"
863
+ "The type parameter is optional and must be one of:\n"
864
+ "- `world`: General knowledge about people, places, events, and things that happen\n"
865
+ "- `experience`: Memories about experience, conversations, actions taken, and tasks performed\n"
866
+ "- `opinion`: The bank's formed beliefs, perspectives, and viewpoints\n\n"
867
+ "Set `include_entities=true` to get entity observations alongside recall results.",
868
+ operation_id="recall_memories",
869
+ tags=["Memory"]
910
870
  )
911
871
  async def api_recall(bank_id: str, request: RecallRequest):
912
872
  """Run a recall and return results with trace."""
913
873
  metrics = get_metrics_collector()
914
874
 
915
875
  try:
916
- # Validate types
917
- valid_fact_types = ["world", "experience", "opinion"]
918
-
919
876
  # Default to world, experience, opinion if not specified (exclude observation by default)
920
- fact_types = request.types if request.types else ["world", "experience", "opinion"]
921
- for ft in fact_types:
922
- if ft not in valid_fact_types:
923
- raise HTTPException(
924
- status_code=400,
925
- detail=f"Invalid type '{ft}'. Must be one of: {', '.join(valid_fact_types)}"
926
- )
877
+ fact_types = request.types if request.types else list(VALID_RECALL_FACT_TYPES)
927
878
 
928
879
  # Parse query_timestamp if provided
929
880
  question_date = None
@@ -1022,18 +973,16 @@ def _register_routes(app: FastAPI):
1022
973
  "/v1/default/banks/{bank_id}/reflect",
1023
974
  response_model=ReflectResponse,
1024
975
  summary="Reflect and generate answer",
1025
- description="""
1026
- Reflect and formulate an answer using bank identity, world facts, and opinions.
1027
-
1028
- This endpoint:
1029
- 1. Retrieves experience (conversations and events)
1030
- 2. Retrieves world facts relevant to the query
1031
- 3. Retrieves existing opinions (bank's perspectives)
1032
- 4. Uses LLM to formulate a contextual answer
1033
- 5. Extracts and stores any new opinions formed
1034
- 6. Returns plain text answer, the facts used, and new opinions
1035
- """,
1036
- operation_id="reflect"
976
+ description="Reflect and formulate an answer using bank identity, world facts, and opinions.\n\n"
977
+ "This endpoint:\n"
978
+ "1. Retrieves experience (conversations and events)\n"
979
+ "2. Retrieves world facts relevant to the query\n"
980
+ "3. Retrieves existing opinions (bank's perspectives)\n"
981
+ "4. Uses LLM to formulate a contextual answer\n"
982
+ "5. Extracts and stores any new opinions formed\n"
983
+ "6. Returns plain text answer, the facts used, and new opinions",
984
+ operation_id="reflect",
985
+ tags=["Memory"]
1037
986
  )
1038
987
  async def api_reflect(bank_id: str, request: ReflectRequest):
1039
988
  metrics = get_metrics_collector()
@@ -1079,7 +1028,8 @@ def _register_routes(app: FastAPI):
1079
1028
  response_model=BankListResponse,
1080
1029
  summary="List all memory banks",
1081
1030
  description="Get a list of all agents with their profiles",
1082
- operation_id="list_banks"
1031
+ operation_id="list_banks",
1032
+ tags=["Banks"]
1083
1033
  )
1084
1034
  async def api_list_banks():
1085
1035
  """Get list of all banks with their profiles."""
@@ -1096,7 +1046,8 @@ def _register_routes(app: FastAPI):
1096
1046
  "/v1/default/banks/{bank_id}/stats",
1097
1047
  summary="Get statistics for memory bank",
1098
1048
  description="Get statistics about nodes and links for a specific agent",
1099
- operation_id="get_agent_stats"
1049
+ operation_id="get_agent_stats",
1050
+ tags=["Banks"]
1100
1051
  )
1101
1052
  async def api_stats(bank_id: str):
1102
1053
  """Get statistics about memory nodes and links for a memory bank."""
@@ -1217,7 +1168,8 @@ def _register_routes(app: FastAPI):
1217
1168
  response_model=EntityListResponse,
1218
1169
  summary="List entities",
1219
1170
  description="List all entities (people, organizations, etc.) known by the bank, ordered by mention count.",
1220
- operation_id="list_entities"
1171
+ operation_id="list_entities",
1172
+ tags=["Entities"]
1221
1173
  )
1222
1174
  async def api_list_entities(bank_id: str,
1223
1175
  limit: int = Query(default=100, description="Maximum number of entities to return")
@@ -1239,7 +1191,8 @@ def _register_routes(app: FastAPI):
1239
1191
  response_model=EntityDetailResponse,
1240
1192
  summary="Get entity details",
1241
1193
  description="Get detailed information about an entity including observations (mental model).",
1242
- operation_id="get_entity"
1194
+ operation_id="get_entity",
1195
+ tags=["Entities"]
1243
1196
  )
1244
1197
  async def api_get_entity(bank_id: str, entity_id: str):
1245
1198
  """Get entity details with observations."""
@@ -1289,7 +1242,8 @@ def _register_routes(app: FastAPI):
1289
1242
  response_model=EntityDetailResponse,
1290
1243
  summary="Regenerate entity observations",
1291
1244
  description="Regenerate observations for an entity based on all facts mentioning it.",
1292
- operation_id="regenerate_entity_observations"
1245
+ operation_id="regenerate_entity_observations",
1246
+ tags=["Entities"]
1293
1247
  )
1294
1248
  async def api_regenerate_entity_observations(bank_id: str, entity_id: str):
1295
1249
  """Regenerate observations for an entity."""
@@ -1346,7 +1300,8 @@ def _register_routes(app: FastAPI):
1346
1300
  response_model=ListDocumentsResponse,
1347
1301
  summary="List documents",
1348
1302
  description="List documents with pagination and optional search. Documents are the source content from which memory units are extracted.",
1349
- operation_id="list_documents"
1303
+ operation_id="list_documents",
1304
+ tags=["Documents"]
1350
1305
  )
1351
1306
  async def api_list_documents(bank_id: str,
1352
1307
  q: Optional[str] = None,
@@ -1382,7 +1337,8 @@ def _register_routes(app: FastAPI):
1382
1337
  response_model=DocumentResponse,
1383
1338
  summary="Get document details",
1384
1339
  description="Get a specific document including its original text",
1385
- operation_id="get_document"
1340
+ operation_id="get_document",
1341
+ tags=["Documents"]
1386
1342
  )
1387
1343
  async def api_get_document(bank_id: str,
1388
1344
  document_id: str
@@ -1413,7 +1369,8 @@ def _register_routes(app: FastAPI):
1413
1369
  response_model=ChunkResponse,
1414
1370
  summary="Get chunk details",
1415
1371
  description="Get a specific chunk by its ID",
1416
- operation_id="get_chunk"
1372
+ operation_id="get_chunk",
1373
+ tags=["Documents"]
1417
1374
  )
1418
1375
  async def api_get_chunk(chunk_id: str):
1419
1376
  """
@@ -1439,17 +1396,14 @@ def _register_routes(app: FastAPI):
1439
1396
  @app.delete(
1440
1397
  "/v1/default/banks/{bank_id}/documents/{document_id}",
1441
1398
  summary="Delete a document",
1442
- description="""
1443
- Delete a document and all its associated memory units and links.
1444
-
1445
- This will cascade delete:
1446
- - The document itself
1447
- - All memory units extracted from this document
1448
- - All links (temporal, semantic, entity) associated with those memory units
1449
-
1450
- This operation cannot be undone.
1451
- """,
1452
- operation_id="delete_document"
1399
+ description="Delete a document and all its associated memory units and links.\n\n"
1400
+ "This will cascade delete:\n"
1401
+ "- The document itself\n"
1402
+ "- All memory units extracted from this document\n"
1403
+ "- All links (temporal, semantic, entity) associated with those memory units\n\n"
1404
+ "This operation cannot be undone.",
1405
+ operation_id="delete_document",
1406
+ tags=["Documents"]
1453
1407
  )
1454
1408
  async def api_delete_document(bank_id: str,
1455
1409
  document_id: str
@@ -1486,7 +1440,8 @@ This operation cannot be undone.
1486
1440
  "/v1/default/banks/{bank_id}/operations",
1487
1441
  summary="List async operations",
1488
1442
  description="Get a list of all async operations (pending and failed) for a specific agent, including error messages for failed operations",
1489
- operation_id="list_operations"
1443
+ operation_id="list_operations",
1444
+ tags=["Operations"]
1490
1445
  )
1491
1446
  async def api_list_operations(bank_id: str):
1492
1447
  """List all async operations (pending and failed) for a memory bank."""
@@ -1530,7 +1485,8 @@ This operation cannot be undone.
1530
1485
  "/v1/default/banks/{bank_id}/operations/{operation_id}",
1531
1486
  summary="Cancel a pending async operation",
1532
1487
  description="Cancel a pending async operation by removing it from the queue",
1533
- operation_id="cancel_operation"
1488
+ operation_id="cancel_operation",
1489
+ tags=["Operations"]
1534
1490
  )
1535
1491
  async def api_cancel_operation(bank_id: str, operation_id: str):
1536
1492
  """Cancel a pending async operation."""
@@ -1580,7 +1536,8 @@ This operation cannot be undone.
1580
1536
  response_model=BankProfileResponse,
1581
1537
  summary="Get memory bank profile",
1582
1538
  description="Get disposition traits and background for a memory bank. Auto-creates agent with defaults if not exists.",
1583
- operation_id="get_bank_profile"
1539
+ operation_id="get_bank_profile",
1540
+ tags=["Banks"]
1584
1541
  )
1585
1542
  async def api_get_bank_profile(bank_id: str):
1586
1543
  """Get memory bank profile (disposition + background)."""
@@ -1605,8 +1562,9 @@ This operation cannot be undone.
1605
1562
  "/v1/default/banks/{bank_id}/profile",
1606
1563
  response_model=BankProfileResponse,
1607
1564
  summary="Update memory bank disposition",
1608
- description="Update bank's Big Five disposition traits and bias strength",
1609
- operation_id="update_bank_disposition"
1565
+ description="Update bank's disposition traits (skepticism, literalism, empathy)",
1566
+ operation_id="update_bank_disposition",
1567
+ tags=["Banks"]
1610
1568
  )
1611
1569
  async def api_update_bank_disposition(bank_id: str,
1612
1570
  request: UpdateDispositionRequest
@@ -1640,7 +1598,8 @@ This operation cannot be undone.
1640
1598
  response_model=BackgroundResponse,
1641
1599
  summary="Add/merge memory bank background",
1642
1600
  description="Add new background information or merge with existing. LLM intelligently resolves conflicts, normalizes to first person, and optionally infers disposition traits.",
1643
- operation_id="add_bank_background"
1601
+ operation_id="add_bank_background",
1602
+ tags=["Banks"]
1644
1603
  )
1645
1604
  async def api_add_bank_background(bank_id: str,
1646
1605
  request: AddBackgroundRequest
@@ -1670,7 +1629,8 @@ This operation cannot be undone.
1670
1629
  response_model=BankProfileResponse,
1671
1630
  summary="Create or update memory bank",
1672
1631
  description="Create a new agent or update existing agent with disposition and background. Auto-fills missing fields with defaults.",
1673
- operation_id="create_or_update_bank"
1632
+ operation_id="create_or_update_bank",
1633
+ tags=["Banks"]
1674
1634
  )
1675
1635
  async def api_create_or_update_bank(bank_id: str,
1676
1636
  request: CreateBankRequest
@@ -1740,39 +1700,26 @@ This operation cannot be undone.
1740
1700
  "/v1/default/banks/{bank_id}/memories",
1741
1701
  response_model=RetainResponse,
1742
1702
  summary="Retain memories",
1743
- description="""
1744
- Retain memory items with automatic fact extraction.
1745
-
1746
- This is the main endpoint for storing memories. It supports both synchronous and asynchronous processing
1747
- via the async parameter.
1748
-
1749
- Features:
1750
- - Efficient batch processing
1751
- - Automatic fact extraction from natural language
1752
- - Entity recognition and linking
1753
- - Document tracking with automatic upsert (when document_id is provided on items)
1754
- - Temporal and semantic linking
1755
- - Optional asynchronous processing
1756
-
1757
- The system automatically:
1758
- 1. Extracts semantic facts from the content
1759
- 2. Generates embeddings
1760
- 3. Deduplicates similar facts
1761
- 4. Creates temporal, semantic, and entity links
1762
- 5. Tracks document metadata
1763
-
1764
- When async=true:
1765
- - Returns immediately after queuing the task
1766
- - Processing happens in the background
1767
- - Use the operations endpoint to monitor progress
1768
-
1769
- When async=false (default):
1770
- - Waits for processing to complete
1771
- - Returns after all memories are stored
1772
-
1773
- Note: If a memory item has a document_id that already exists, the old document and its memory units will be deleted before creating new ones (upsert behavior). Items with the same document_id are grouped together for efficient processing.
1774
- """,
1775
- operation_id="retain_memories"
1703
+ description="Retain memory items with automatic fact extraction.\n\n"
1704
+ "This is the main endpoint for storing memories. It supports both synchronous and asynchronous processing via the `async` parameter.\n\n"
1705
+ "**Features:**\n"
1706
+ "- Efficient batch processing\n"
1707
+ "- Automatic fact extraction from natural language\n"
1708
+ "- Entity recognition and linking\n"
1709
+ "- Document tracking with automatic upsert (when document_id is provided)\n"
1710
+ "- Temporal and semantic linking\n"
1711
+ "- Optional asynchronous processing\n\n"
1712
+ "**The system automatically:**\n"
1713
+ "1. Extracts semantic facts from the content\n"
1714
+ "2. Generates embeddings\n"
1715
+ "3. Deduplicates similar facts\n"
1716
+ "4. Creates temporal, semantic, and entity links\n"
1717
+ "5. Tracks document metadata\n\n"
1718
+ "**When `async=true`:** Returns immediately after queuing. Use the operations endpoint to monitor progress.\n\n"
1719
+ "**When `async=false` (default):** Waits for processing to complete.\n\n"
1720
+ "**Note:** If a memory item has a `document_id` that already exists, the old document and its memory units will be deleted before creating new ones (upsert behavior).",
1721
+ operation_id="retain_memories",
1722
+ tags=["Memory"]
1776
1723
  )
1777
1724
  async def api_retain(bank_id: str, request: RetainRequest):
1778
1725
  """Retain memories with optional async processing."""
@@ -1813,7 +1760,7 @@ This operation cannot be undone.
1813
1760
 
1814
1761
  # Submit task to background queue
1815
1762
  await app.state.memory._task_backend.submit_task({
1816
- 'type': 'batch_put',
1763
+ 'type': 'batch_retain',
1817
1764
  'operation_id': str(operation_id),
1818
1765
  'bank_id': bank_id,
1819
1766
  'contents': contents
@@ -1852,8 +1799,9 @@ This operation cannot be undone.
1852
1799
  "/v1/default/banks/{bank_id}/memories",
1853
1800
  response_model=DeleteResponse,
1854
1801
  summary="Clear memory bank memories",
1855
- description="Delete memory units for a memory bank. Optionally filter by type (world, experience, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The bank profile (personality and background) will be preserved.",
1856
- operation_id="clear_bank_memories"
1802
+ description="Delete memory units for a memory bank. Optionally filter by type (world, experience, opinion) to delete only specific types. This is a destructive operation that cannot be undone. The bank profile (disposition and background) will be preserved.",
1803
+ operation_id="clear_bank_memories",
1804
+ tags=["Memory"]
1857
1805
  )
1858
1806
  async def api_clear_bank_memories(bank_id: str,
1859
1807
  type: Optional[str] = Query(None, description="Optional fact type filter (world, experience, opinion)")
hindsight_api/api/mcp.py CHANGED
@@ -8,6 +8,7 @@ from typing import Optional
8
8
 
9
9
  from fastmcp import FastMCP
10
10
  from hindsight_api import MemoryEngine
11
+ from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
11
12
 
12
13
  # Configure logging from HINDSIGHT_API_LOG_LEVEL environment variable
13
14
  _log_level_str = os.environ.get("HINDSIGHT_API_LOG_LEVEL", "info").lower()
@@ -90,7 +91,7 @@ def create_mcp_server(memory: MemoryEngine) -> FastMCP:
90
91
  search_result = await memory.recall_async(
91
92
  bank_id=bank_id,
92
93
  query=query,
93
- fact_type=["world", "experience", "opinion"],
94
+ fact_type=list(VALID_RECALL_FACT_TYPES),
94
95
  budget=Budget.LOW
95
96
  )
96
97