hindsight-api 0.2.1__py3-none-any.whl → 0.4.0__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 (88) hide show
  1. hindsight_api/admin/__init__.py +1 -0
  2. hindsight_api/admin/cli.py +311 -0
  3. hindsight_api/alembic/versions/f1a2b3c4d5e6_add_memory_links_composite_index.py +44 -0
  4. hindsight_api/alembic/versions/g2a3b4c5d6e7_add_tags_column.py +48 -0
  5. hindsight_api/alembic/versions/h3c4d5e6f7g8_mental_models_v4.py +112 -0
  6. hindsight_api/alembic/versions/i4d5e6f7g8h9_delete_opinions.py +41 -0
  7. hindsight_api/alembic/versions/j5e6f7g8h9i0_mental_model_versions.py +95 -0
  8. hindsight_api/alembic/versions/k6f7g8h9i0j1_add_directive_subtype.py +58 -0
  9. hindsight_api/alembic/versions/l7g8h9i0j1k2_add_worker_columns.py +109 -0
  10. hindsight_api/alembic/versions/m8h9i0j1k2l3_mental_model_id_to_text.py +41 -0
  11. hindsight_api/alembic/versions/n9i0j1k2l3m4_learnings_and_pinned_reflections.py +134 -0
  12. hindsight_api/alembic/versions/o0j1k2l3m4n5_migrate_mental_models_data.py +113 -0
  13. hindsight_api/alembic/versions/p1k2l3m4n5o6_new_knowledge_architecture.py +194 -0
  14. hindsight_api/alembic/versions/q2l3m4n5o6p7_fix_mental_model_fact_type.py +50 -0
  15. hindsight_api/alembic/versions/r3m4n5o6p7q8_add_reflect_response_to_reflections.py +47 -0
  16. hindsight_api/alembic/versions/s4n5o6p7q8r9_add_consolidated_at_to_memory_units.py +53 -0
  17. hindsight_api/alembic/versions/t5o6p7q8r9s0_rename_mental_models_to_observations.py +134 -0
  18. hindsight_api/alembic/versions/u6p7q8r9s0t1_mental_models_text_id.py +41 -0
  19. hindsight_api/alembic/versions/v7q8r9s0t1u2_add_max_tokens_to_mental_models.py +50 -0
  20. hindsight_api/api/http.py +1406 -118
  21. hindsight_api/api/mcp.py +11 -196
  22. hindsight_api/config.py +359 -27
  23. hindsight_api/engine/consolidation/__init__.py +5 -0
  24. hindsight_api/engine/consolidation/consolidator.py +859 -0
  25. hindsight_api/engine/consolidation/prompts.py +69 -0
  26. hindsight_api/engine/cross_encoder.py +706 -88
  27. hindsight_api/engine/db_budget.py +284 -0
  28. hindsight_api/engine/db_utils.py +11 -0
  29. hindsight_api/engine/directives/__init__.py +5 -0
  30. hindsight_api/engine/directives/models.py +37 -0
  31. hindsight_api/engine/embeddings.py +553 -29
  32. hindsight_api/engine/entity_resolver.py +8 -5
  33. hindsight_api/engine/interface.py +40 -17
  34. hindsight_api/engine/llm_wrapper.py +744 -68
  35. hindsight_api/engine/memory_engine.py +2505 -1017
  36. hindsight_api/engine/mental_models/__init__.py +14 -0
  37. hindsight_api/engine/mental_models/models.py +53 -0
  38. hindsight_api/engine/query_analyzer.py +4 -3
  39. hindsight_api/engine/reflect/__init__.py +18 -0
  40. hindsight_api/engine/reflect/agent.py +933 -0
  41. hindsight_api/engine/reflect/models.py +109 -0
  42. hindsight_api/engine/reflect/observations.py +186 -0
  43. hindsight_api/engine/reflect/prompts.py +483 -0
  44. hindsight_api/engine/reflect/tools.py +437 -0
  45. hindsight_api/engine/reflect/tools_schema.py +250 -0
  46. hindsight_api/engine/response_models.py +168 -4
  47. hindsight_api/engine/retain/bank_utils.py +79 -201
  48. hindsight_api/engine/retain/fact_extraction.py +424 -195
  49. hindsight_api/engine/retain/fact_storage.py +35 -12
  50. hindsight_api/engine/retain/link_utils.py +29 -24
  51. hindsight_api/engine/retain/orchestrator.py +24 -43
  52. hindsight_api/engine/retain/types.py +11 -2
  53. hindsight_api/engine/search/graph_retrieval.py +43 -14
  54. hindsight_api/engine/search/link_expansion_retrieval.py +391 -0
  55. hindsight_api/engine/search/mpfp_retrieval.py +362 -117
  56. hindsight_api/engine/search/reranking.py +2 -2
  57. hindsight_api/engine/search/retrieval.py +848 -201
  58. hindsight_api/engine/search/tags.py +172 -0
  59. hindsight_api/engine/search/think_utils.py +42 -141
  60. hindsight_api/engine/search/trace.py +12 -1
  61. hindsight_api/engine/search/tracer.py +26 -6
  62. hindsight_api/engine/search/types.py +21 -3
  63. hindsight_api/engine/task_backend.py +113 -106
  64. hindsight_api/engine/utils.py +1 -152
  65. hindsight_api/extensions/__init__.py +10 -1
  66. hindsight_api/extensions/builtin/tenant.py +5 -1
  67. hindsight_api/extensions/context.py +10 -1
  68. hindsight_api/extensions/operation_validator.py +81 -4
  69. hindsight_api/extensions/tenant.py +26 -0
  70. hindsight_api/main.py +69 -6
  71. hindsight_api/mcp_local.py +12 -53
  72. hindsight_api/mcp_tools.py +494 -0
  73. hindsight_api/metrics.py +433 -48
  74. hindsight_api/migrations.py +141 -1
  75. hindsight_api/models.py +3 -3
  76. hindsight_api/pg0.py +53 -0
  77. hindsight_api/server.py +39 -2
  78. hindsight_api/worker/__init__.py +11 -0
  79. hindsight_api/worker/main.py +296 -0
  80. hindsight_api/worker/poller.py +486 -0
  81. {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/METADATA +16 -6
  82. hindsight_api-0.4.0.dist-info/RECORD +112 -0
  83. {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/entry_points.txt +2 -0
  84. hindsight_api/engine/retain/observation_regeneration.py +0 -254
  85. hindsight_api/engine/search/observation_utils.py +0 -125
  86. hindsight_api/engine/search/scoring.py +0 -159
  87. hindsight_api-0.2.1.dist-info/RECORD +0 -75
  88. {hindsight_api-0.2.1.dist-info → hindsight_api-0.4.0.dist-info}/WHEEL +0 -0
@@ -28,6 +28,18 @@ class TenantContext:
28
28
  schema_name: str
29
29
 
30
30
 
31
+ @dataclass
32
+ class Tenant:
33
+ """
34
+ Represents a tenant for worker discovery.
35
+
36
+ Used by list_tenants() to return tenant information including
37
+ the PostgreSQL schema name for database operations.
38
+ """
39
+
40
+ schema: str
41
+
42
+
31
43
  class TenantExtension(Extension, ABC):
32
44
  """
33
45
  Extension for multi-tenancy and API key authentication.
@@ -61,3 +73,17 @@ class TenantExtension(Extension, ABC):
61
73
  AuthenticationError: If authentication fails.
62
74
  """
63
75
  ...
76
+
77
+ @abstractmethod
78
+ async def list_tenants(self) -> list[Tenant]:
79
+ """
80
+ List all tenants that should be processed by workers.
81
+
82
+ This method is used by the worker to discover all tenants that need
83
+ task polling. Workers will poll for pending tasks in each tenant's schema.
84
+
85
+ Returns:
86
+ List of Tenant objects containing schema information.
87
+ For single-tenant setups, return [Tenant(schema="public")].
88
+ """
89
+ ...
hindsight_api/main.py CHANGED
@@ -23,7 +23,7 @@ import uvicorn
23
23
  from . import MemoryEngine
24
24
  from .api import create_app
25
25
  from .banner import print_banner
26
- from .config import HindsightConfig, get_config
26
+ from .config import DEFAULT_WORKERS, ENV_WORKERS, HindsightConfig, get_config
27
27
  from .daemon import (
28
28
  DEFAULT_DAEMON_PORT,
29
29
  DEFAULT_IDLE_TIMEOUT,
@@ -95,7 +95,12 @@ def main():
95
95
 
96
96
  # Development options
97
97
  parser.add_argument("--reload", action="store_true", help="Enable auto-reload on code changes (development only)")
98
- parser.add_argument("--workers", type=int, default=1, help="Number of worker processes (default: 1)")
98
+ parser.add_argument(
99
+ "--workers",
100
+ type=int,
101
+ default=int(os.getenv(ENV_WORKERS, str(DEFAULT_WORKERS))),
102
+ help=f"Number of worker processes (env: {ENV_WORKERS}, default: {DEFAULT_WORKERS})",
103
+ )
99
104
 
100
105
  # Access log options
101
106
  parser.add_argument("--access-log", action="store_true", help="Enable access log")
@@ -171,21 +176,62 @@ def main():
171
176
  llm_base_url=config.llm_base_url,
172
177
  llm_max_concurrent=config.llm_max_concurrent,
173
178
  llm_timeout=config.llm_timeout,
179
+ retain_llm_provider=config.retain_llm_provider,
180
+ retain_llm_api_key=config.retain_llm_api_key,
181
+ retain_llm_model=config.retain_llm_model,
182
+ retain_llm_base_url=config.retain_llm_base_url,
183
+ reflect_llm_provider=config.reflect_llm_provider,
184
+ reflect_llm_api_key=config.reflect_llm_api_key,
185
+ reflect_llm_model=config.reflect_llm_model,
186
+ reflect_llm_base_url=config.reflect_llm_base_url,
187
+ consolidation_llm_provider=config.consolidation_llm_provider,
188
+ consolidation_llm_api_key=config.consolidation_llm_api_key,
189
+ consolidation_llm_model=config.consolidation_llm_model,
190
+ consolidation_llm_base_url=config.consolidation_llm_base_url,
174
191
  embeddings_provider=config.embeddings_provider,
175
192
  embeddings_local_model=config.embeddings_local_model,
176
193
  embeddings_tei_url=config.embeddings_tei_url,
194
+ embeddings_openai_base_url=config.embeddings_openai_base_url,
195
+ embeddings_cohere_base_url=config.embeddings_cohere_base_url,
177
196
  reranker_provider=config.reranker_provider,
178
197
  reranker_local_model=config.reranker_local_model,
179
198
  reranker_tei_url=config.reranker_tei_url,
199
+ reranker_tei_batch_size=config.reranker_tei_batch_size,
200
+ reranker_tei_max_concurrent=config.reranker_tei_max_concurrent,
201
+ reranker_max_candidates=config.reranker_max_candidates,
202
+ reranker_cohere_base_url=config.reranker_cohere_base_url,
180
203
  host=args.host,
181
204
  port=args.port,
182
205
  log_level=args.log_level,
206
+ log_format=config.log_format,
183
207
  mcp_enabled=config.mcp_enabled,
184
208
  graph_retriever=config.graph_retriever,
185
- observation_min_facts=config.observation_min_facts,
186
- observation_top_entities=config.observation_top_entities,
209
+ mpfp_top_k_neighbors=config.mpfp_top_k_neighbors,
210
+ recall_max_concurrent=config.recall_max_concurrent,
211
+ recall_connection_budget=config.recall_connection_budget,
212
+ retain_max_completion_tokens=config.retain_max_completion_tokens,
213
+ retain_chunk_size=config.retain_chunk_size,
214
+ retain_extract_causal_links=config.retain_extract_causal_links,
215
+ retain_extraction_mode=config.retain_extraction_mode,
216
+ retain_custom_instructions=config.retain_custom_instructions,
217
+ retain_observations_async=config.retain_observations_async,
218
+ enable_observations=config.enable_observations,
219
+ consolidation_batch_size=config.consolidation_batch_size,
187
220
  skip_llm_verification=config.skip_llm_verification,
188
221
  lazy_reranker=config.lazy_reranker,
222
+ run_migrations_on_startup=config.run_migrations_on_startup,
223
+ db_pool_min_size=config.db_pool_min_size,
224
+ db_pool_max_size=config.db_pool_max_size,
225
+ db_command_timeout=config.db_command_timeout,
226
+ db_acquire_timeout=config.db_acquire_timeout,
227
+ worker_enabled=config.worker_enabled,
228
+ worker_id=config.worker_id,
229
+ worker_poll_interval_ms=config.worker_poll_interval_ms,
230
+ worker_max_retries=config.worker_max_retries,
231
+ worker_batch_size=config.worker_batch_size,
232
+ worker_http_port=config.worker_http_port,
233
+ reflect_max_iterations=config.reflect_max_iterations,
234
+ mental_model_refresh_concurrency=config.mental_model_refresh_concurrency,
189
235
  )
190
236
  config.configure_logging()
191
237
  if not args.daemon:
@@ -211,7 +257,11 @@ def main():
211
257
  logging.info(f"Loaded tenant extension: {tenant_extension.__class__.__name__}")
212
258
 
213
259
  # Create MemoryEngine (reads configuration from environment)
214
- _memory = MemoryEngine(operation_validator=operation_validator, tenant_extension=tenant_extension)
260
+ _memory = MemoryEngine(
261
+ operation_validator=operation_validator,
262
+ tenant_extension=tenant_extension,
263
+ run_migrations=config.run_migrations_on_startup,
264
+ )
215
265
 
216
266
  # Set extension context on tenant extension (needed for schema provisioning)
217
267
  if tenant_extension:
@@ -238,14 +288,27 @@ def main():
238
288
  app = idle_middleware
239
289
 
240
290
  # Prepare uvicorn config
291
+ # When using workers or reload, we must use import string so each worker can import the app
292
+ use_import_string = args.workers > 1 or args.reload
293
+ # Check for uvloop availability
294
+ try:
295
+ import uvloop # noqa: F401
296
+
297
+ loop_impl = "uvloop"
298
+ print("uvloop available, will use for event loop")
299
+ except ImportError:
300
+ loop_impl = "asyncio"
301
+ print("uvloop not installed, using default asyncio event loop")
302
+
241
303
  uvicorn_config = {
242
- "app": app,
304
+ "app": "hindsight_api.server:app" if use_import_string else app,
243
305
  "host": args.host,
244
306
  "port": args.port,
245
307
  "log_level": args.log_level,
246
308
  "access_log": args.access_log,
247
309
  "proxy_headers": args.proxy_headers,
248
310
  "ws": "wsproto", # Use wsproto instead of websockets to avoid deprecation warnings
311
+ "loop": loop_impl, # Explicitly set event loop implementation
249
312
  }
250
313
 
251
314
  # Add optional parameters if provided
@@ -44,7 +44,6 @@ import os
44
44
  import sys
45
45
 
46
46
  from mcp.server.fastmcp import FastMCP
47
- from mcp.types import Icon
48
47
 
49
48
  from hindsight_api.config import (
50
49
  DEFAULT_MCP_LOCAL_BANK_ID,
@@ -53,6 +52,7 @@ from hindsight_api.config import (
53
52
  ENV_MCP_INSTRUCTIONS,
54
53
  ENV_MCP_LOCAL_BANK_ID,
55
54
  )
55
+ from hindsight_api.mcp_tools import MCPToolsConfig, register_mcp_tools
56
56
 
57
57
  # Configure logging - default to warning to avoid polluting stderr during MCP init
58
58
  # MCP clients interpret stderr output as errors, so we suppress INFO logs by default
@@ -85,9 +85,6 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
85
85
  """
86
86
  # Import here to avoid slow startup if just checking --help
87
87
  from hindsight_api import MemoryEngine
88
- from hindsight_api.engine.memory_engine import Budget
89
- from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES
90
- from hindsight_api.models import RequestContext
91
88
 
92
89
  # Create memory engine with pg0 embedded database if not provided
93
90
  if memory is None:
@@ -105,55 +102,17 @@ def create_local_mcp_server(bank_id: str, memory=None) -> FastMCP:
105
102
 
106
103
  mcp = FastMCP("hindsight")
107
104
 
108
- @mcp.tool(description=retain_description)
109
- async def retain(content: str, context: str = "general") -> dict:
110
- """
111
- Args:
112
- content: The fact/memory to store (be specific and include relevant details)
113
- context: Category for the memory (e.g., 'preferences', 'work', 'hobbies', 'family'). Default: 'general'
114
- """
115
- import asyncio
116
-
117
- async def _retain():
118
- try:
119
- await memory.retain_batch_async(
120
- bank_id=bank_id,
121
- contents=[{"content": content, "context": context}],
122
- request_context=RequestContext(),
123
- )
124
- except Exception as e:
125
- logger.error(f"Error storing memory: {e}", exc_info=True)
126
-
127
- # Fire and forget - don't block on memory storage
128
- asyncio.create_task(_retain())
129
- return {"status": "accepted", "message": "Memory storage initiated"}
130
-
131
- @mcp.tool(description=recall_description)
132
- async def recall(query: str, max_tokens: int = 4096, budget: str = "low") -> dict:
133
- """
134
- Args:
135
- query: Natural language search query (e.g., "user's food preferences", "what projects is user working on")
136
- max_tokens: Maximum tokens to return in results (default: 4096)
137
- budget: Search budget level - "low", "mid", or "high" (default: "low")
138
- """
139
- try:
140
- # Map string budget to enum
141
- budget_map = {"low": Budget.LOW, "mid": Budget.MID, "high": Budget.HIGH}
142
- budget_enum = budget_map.get(budget.lower(), Budget.LOW)
143
-
144
- search_result = await memory.recall_async(
145
- bank_id=bank_id,
146
- query=query,
147
- fact_type=list(VALID_RECALL_FACT_TYPES),
148
- budget=budget_enum,
149
- max_tokens=max_tokens,
150
- request_context=RequestContext(),
151
- )
152
-
153
- return search_result.model_dump()
154
- except Exception as e:
155
- logger.error(f"Error searching: {e}", exc_info=True)
156
- return {"error": str(e), "results": []}
105
+ # Configure and register tools using shared module
106
+ config = MCPToolsConfig(
107
+ bank_id_resolver=lambda: bank_id,
108
+ include_bank_id_param=False, # Local MCP uses fixed bank_id
109
+ tools={"retain", "recall"}, # Local MCP only has retain and recall
110
+ retain_description=retain_description,
111
+ recall_description=recall_description,
112
+ retain_fire_and_forget=True, # Local MCP uses fire-and-forget pattern
113
+ )
114
+
115
+ register_mcp_tools(mcp, memory, config)
157
116
 
158
117
  return mcp
159
118