Skip to content

Commit 1676535

Browse files
committed
fixes
1 parent 8f8a472 commit 1676535

19 files changed

Lines changed: 781 additions & 45 deletions

File tree

hindsight-api/hindsight_api/api/http.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,34 @@ class AsyncOperationSubmitResponse(BaseModel):
12881288
status: str
12891289

12901290

1291+
class FeaturesInfo(BaseModel):
1292+
"""Feature flags indicating which capabilities are enabled."""
1293+
1294+
mental_models: bool = Field(description="Whether mental models (auto-consolidation) are enabled")
1295+
mcp: bool = Field(description="Whether MCP (Model Context Protocol) server is enabled")
1296+
worker: bool = Field(description="Whether the background worker is enabled")
1297+
1298+
1299+
class VersionResponse(BaseModel):
1300+
"""Response model for the version/info endpoint."""
1301+
1302+
model_config = ConfigDict(
1303+
json_schema_extra={
1304+
"example": {
1305+
"api_version": "1.0.0",
1306+
"features": {
1307+
"mental_models": False,
1308+
"mcp": True,
1309+
"worker": True,
1310+
},
1311+
}
1312+
}
1313+
)
1314+
1315+
api_version: str = Field(description="API version string")
1316+
features: FeaturesInfo = Field(description="Enabled feature flags")
1317+
1318+
12911319
def create_app(
12921320
memory: MemoryEngine,
12931321
initialize_memory: bool = True,
@@ -1501,6 +1529,34 @@ async def health_endpoint():
15011529
status_code = 200 if health.get("status") == "healthy" else 503
15021530
return JSONResponse(content=health, status_code=status_code)
15031531

1532+
@app.get(
1533+
"/version",
1534+
response_model=VersionResponse,
1535+
summary="Get API version and feature flags",
1536+
description="Returns API version information and enabled feature flags. "
1537+
"Use this to check which capabilities are available in this deployment.",
1538+
tags=["Monitoring"],
1539+
operation_id="get_version",
1540+
)
1541+
async def version_endpoint() -> VersionResponse:
1542+
"""
1543+
Get API version and enabled features.
1544+
1545+
Returns version info and feature flags that can be used by clients
1546+
to determine which capabilities are available.
1547+
"""
1548+
from hindsight_api.config import get_config
1549+
1550+
config = get_config()
1551+
return VersionResponse(
1552+
api_version="1.0.0",
1553+
features=FeaturesInfo(
1554+
mental_models=config.enable_mental_models,
1555+
mcp=config.mcp_enabled,
1556+
worker=config.worker_enabled,
1557+
),
1558+
)
1559+
15041560
@app.get(
15051561
"/metrics",
15061562
summary="Prometheus metrics endpoint",

hindsight-api/hindsight_api/config.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@
9393
ENV_RETAIN_EXTRACTION_MODE = "HINDSIGHT_API_RETAIN_EXTRACTION_MODE"
9494
ENV_RETAIN_OBSERVATIONS_ASYNC = "HINDSIGHT_API_RETAIN_OBSERVATIONS_ASYNC"
9595

96-
# Consolidation settings
97-
ENV_ENABLE_CONSOLIDATION = "HINDSIGHT_API_ENABLE_CONSOLIDATION"
96+
# Mental models settings
97+
ENV_ENABLE_MENTAL_MODELS = "HINDSIGHT_API_ENABLE_MENTAL_MODELS"
9898
ENV_CONSOLIDATION_SIMILARITY_THRESHOLD = "HINDSIGHT_API_CONSOLIDATION_SIMILARITY_THRESHOLD"
9999
ENV_CONSOLIDATION_BATCH_SIZE = "HINDSIGHT_API_CONSOLIDATION_BATCH_SIZE"
100100

@@ -176,8 +176,8 @@
176176
RETAIN_EXTRACTION_MODES = ("concise", "verbose") # Allowed extraction modes
177177
DEFAULT_RETAIN_OBSERVATIONS_ASYNC = False # Run observation generation async (after retain completes)
178178

179-
# Consolidation defaults
180-
DEFAULT_ENABLE_CONSOLIDATION = False # Consolidation disabled by default (experimental)
179+
# Mental models defaults
180+
DEFAULT_ENABLE_MENTAL_MODELS = False # Mental models disabled by default (experimental)
181181
DEFAULT_CONSOLIDATION_SIMILARITY_THRESHOLD = 0.75 # Minimum similarity to consider a learning related
182182
DEFAULT_CONSOLIDATION_BATCH_SIZE = 50 # Memories to load per batch (internal memory optimization)
183183

@@ -334,8 +334,8 @@ class HindsightConfig:
334334
retain_extraction_mode: str
335335
retain_observations_async: bool
336336

337-
# Consolidation settings
338-
enable_consolidation: bool
337+
# Mental models settings
338+
enable_mental_models: bool
339339
consolidation_similarity_threshold: float
340340
consolidation_batch_size: int
341341

@@ -441,8 +441,8 @@ def from_env(cls) -> "HindsightConfig":
441441
ENV_RETAIN_OBSERVATIONS_ASYNC, str(DEFAULT_RETAIN_OBSERVATIONS_ASYNC)
442442
).lower()
443443
== "true",
444-
# Consolidation settings
445-
enable_consolidation=os.getenv(ENV_ENABLE_CONSOLIDATION, str(DEFAULT_ENABLE_CONSOLIDATION)).lower()
444+
# Mental models settings
445+
enable_mental_models=os.getenv(ENV_ENABLE_MENTAL_MODELS, str(DEFAULT_ENABLE_MENTAL_MODELS)).lower()
446446
== "true",
447447
consolidation_similarity_threshold=float(
448448
os.getenv(ENV_CONSOLIDATION_SIMILARITY_THRESHOLD, str(DEFAULT_CONSOLIDATION_SIMILARITY_THRESHOLD))

hindsight-api/hindsight_api/engine/consolidation/consolidator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async def run_consolidation_job(
8989
max_memories_per_batch = config.consolidation_batch_size
9090

9191
# Check if consolidation is enabled
92-
if not config.enable_consolidation:
92+
if not config.enable_mental_models:
9393
logger.debug(f"Consolidation disabled for bank {bank_id}")
9494
return {"status": "disabled", "bank_id": bank_id}
9595

@@ -150,6 +150,10 @@ async def run_consolidation_job(
150150
await _update_last_consolidated_at(conn, bank_id)
151151
return {"status": "no_new_memories", "bank_id": bank_id, "memories_processed": 0}
152152

153+
logger.info(
154+
f"[CONSOLIDATION] bank={bank_id} memories={len(memories)} "
155+
f"batch_size={max_memories_per_batch} since={last_consolidated_at or 'beginning'}"
156+
)
153157
perf.log(f"[1] Found {len(memories)} pending memories to consolidate")
154158

155159
# Process each memory sequentially

hindsight-api/hindsight_api/engine/memory_engine.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,6 @@ async def _handle_consolidation(self, task_dict: dict[str, Any]):
519519
if not bank_id:
520520
raise ValueError("bank_id is required for consolidation task")
521521

522-
logger.info(f"[CONSOLIDATION_TASK] Starting consolidation for bank_id={bank_id}")
523-
524522
from hindsight_api.models import RequestContext
525523

526524
from .consolidation import run_consolidation_job
@@ -532,7 +530,7 @@ async def _handle_consolidation(self, task_dict: dict[str, Any]):
532530
request_context=internal_context,
533531
)
534532

535-
logger.info(f"[CONSOLIDATION_TASK] Completed consolidation for bank_id={bank_id}: {result}")
533+
logger.info(f"[CONSOLIDATION] bank={bank_id} completed: {result.get('memories_processed', 0)} processed")
536534

537535
async def _handle_create_reflection(self, task_dict: dict[str, Any]):
538536
"""
@@ -1322,7 +1320,7 @@ async def retain_batch_async(
13221320
from ..config import get_config
13231321

13241322
config = get_config()
1325-
if config.enable_consolidation:
1323+
if config.enable_mental_models:
13261324
try:
13271325
await self.submit_async_consolidation(bank_id=bank_id, request_context=request_context)
13281326
except Exception as e:

hindsight-api/hindsight_api/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def release_lock():
212212
retain_extract_causal_links=config.retain_extract_causal_links,
213213
retain_extraction_mode=config.retain_extraction_mode,
214214
retain_observations_async=config.retain_observations_async,
215-
enable_consolidation=config.enable_consolidation,
215+
enable_mental_models=config.enable_mental_models,
216216
consolidation_similarity_threshold=config.consolidation_similarity_threshold,
217217
consolidation_batch_size=config.consolidation_batch_size,
218218
skip_llm_verification=config.skip_llm_verification,

hindsight-api/hindsight_api/worker/poller.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import asyncio
99
import json
1010
import logging
11+
import time
1112
import traceback
1213
from collections.abc import Awaitable, Callable
1314
from typing import TYPE_CHECKING, Any
@@ -17,6 +18,9 @@
1718

1819
logger = logging.getLogger(__name__)
1920

21+
# Progress logging interval in seconds
22+
PROGRESS_LOG_INTERVAL = 30
23+
2024

2125
def fq_table(table: str, schema: str | None = None) -> str:
2226
"""Get fully-qualified table name with optional schema prefix."""
@@ -66,6 +70,9 @@ def __init__(
6670
self._current_tasks: set[asyncio.Task] = set()
6771
self._in_flight_count = 0
6872
self._in_flight_lock = asyncio.Lock()
73+
self._last_progress_log = 0.0
74+
self._tasks_completed_since_log = 0
75+
self._active_banks: set[str] = set()
6976

7077
async def claim_batch(self) -> list[tuple[str, dict[str, Any]]]:
7178
"""
@@ -281,6 +288,9 @@ async def run(self):
281288
except asyncio.TimeoutError:
282289
pass # Normal timeout, continue polling
283290

291+
# Log progress stats periodically
292+
await self._log_progress_if_due()
293+
284294
except asyncio.CancelledError:
285295
logger.info(f"Worker {self._worker_id} polling loop cancelled")
286296
break
@@ -317,6 +327,72 @@ async def shutdown_graceful(self, timeout: float = 30.0):
317327

318328
logger.warning(f"Worker {self._worker_id} shutdown timeout after {timeout}s")
319329

330+
async def _log_progress_if_due(self):
331+
"""Log progress stats every PROGRESS_LOG_INTERVAL seconds."""
332+
now = time.time()
333+
if now - self._last_progress_log < PROGRESS_LOG_INTERVAL:
334+
return
335+
336+
self._last_progress_log = now
337+
338+
try:
339+
table = fq_table("async_operations", self._schema)
340+
async with self._pool.acquire() as conn:
341+
# Get global stats by status
342+
stats = await conn.fetch(
343+
f"""
344+
SELECT status, COUNT(*) as count
345+
FROM {table}
346+
WHERE created_at > now() - interval '24 hours'
347+
GROUP BY status
348+
"""
349+
)
350+
351+
# Get currently processing tasks grouped by type and bank
352+
processing = await conn.fetch(
353+
f"""
354+
SELECT operation_type, bank_id, COUNT(*) as count
355+
FROM {table}
356+
WHERE status = 'processing'
357+
GROUP BY operation_type, bank_id
358+
"""
359+
)
360+
361+
# Build stats dict
362+
status_counts = {row["status"]: row["count"] for row in stats}
363+
pending = status_counts.get("pending", 0)
364+
processing_count = status_counts.get("processing", 0)
365+
completed = status_counts.get("completed", 0)
366+
failed = status_counts.get("failed", 0)
367+
368+
# Build processing breakdown
369+
processing_info = []
370+
banks_working = set()
371+
for row in processing:
372+
op_type = row["operation_type"]
373+
bank_id = row["bank_id"]
374+
count = row["count"]
375+
banks_working.add(bank_id)
376+
processing_info.append(f"{op_type}:{bank_id}({count})")
377+
378+
# Format log
379+
async with self._in_flight_lock:
380+
in_flight = self._in_flight_count
381+
382+
processing_str = ", ".join(processing_info[:10]) if processing_info else "none"
383+
if len(processing_info) > 10:
384+
processing_str += f" +{len(processing_info) - 10} more"
385+
386+
logger.info(
387+
f"[WORKER_STATS] worker={self._worker_id} in_flight={in_flight} | "
388+
f"global: pending={pending} processing={processing_count} "
389+
f"completed_24h={completed} failed_24h={failed} | "
390+
f"active: {processing_str}"
391+
)
392+
393+
except Exception as e:
394+
logger.debug(f"Failed to log progress stats: {e}")
395+
320396
@property
321397
def worker_id(self) -> str:
322398
"""Get the worker ID."""

hindsight-api/tests/test_consolidation.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@
1919

2020

2121
@pytest.fixture(autouse=True)
22-
def enable_consolidation():
23-
"""Enable consolidation for all tests in this module."""
22+
def enable_mental_models():
23+
"""Enable mental models for all tests in this module."""
2424
from hindsight_api.config import get_config
2525

2626
config = get_config()
27-
original_value = config.enable_consolidation
28-
config.enable_consolidation = True
27+
original_value = config.enable_mental_models
28+
config.enable_mental_models = True
2929
yield
30-
config.enable_consolidation = original_value
30+
config.enable_mental_models = original_value
3131

3232

3333
class TestConsolidationIntegration:
@@ -551,17 +551,17 @@ class TestConsolidationDisabled:
551551
async def test_consolidation_returns_disabled_status(
552552
self, memory: MemoryEngine, request_context
553553
):
554-
"""Test that consolidation returns disabled status when enable_consolidation is False."""
554+
"""Test that consolidation returns disabled status when enable_mental_models is False."""
555555
from unittest.mock import patch
556556

557557
bank_id = f"test-consolidation-disabled-{uuid.uuid4().hex[:8]}"
558558

559559
# Create the bank
560560
await memory.get_bank_profile(bank_id=bank_id, request_context=request_context)
561561

562-
# Disable consolidation via config
562+
# Disable mental models via config
563563
with patch("hindsight_api.config.get_config") as mock_config:
564-
mock_config.return_value.enable_consolidation = False
564+
mock_config.return_value.enable_mental_models = False
565565

566566
result = await run_consolidation_job(
567567
memory_engine=memory,

hindsight-cli/smoke-test.sh

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -115,31 +115,10 @@ run_test "list documents" "$HINDSIGHT_CLI" document list "$TEST_BANK" || FAILED=
115115
# Test 14: Clear memories
116116
run_test "clear memories" "$HINDSIGHT_CLI" memory clear "$TEST_BANK" || FAILED=1
117117

118-
# Test 15: Health check
119-
run_test_output "health check" "healthy" "$HINDSIGHT_CLI" health || FAILED=1
120-
121-
# Test 16: List memories (new command)
122-
run_test "list memories" "$HINDSIGHT_CLI" memory list "$TEST_BANK" || FAILED=1
123-
124-
# Test 17: List tags
125-
run_test "list tags" "$HINDSIGHT_CLI" tag list "$TEST_BANK" || FAILED=1
126-
127-
# Test 18: List mental models
128-
run_test "list mental models" "$HINDSIGHT_CLI" mental-model list "$TEST_BANK" || FAILED=1
129-
130-
# Test 19: Create mental model
131-
run_test "create mental model" "$HINDSIGHT_CLI" mental-model create "$TEST_BANK" "Test Model" "A test mental model" || FAILED=1
132-
133-
# Test 20: List mental models (should have one now)
134-
run_test_output "list mental models with model" "Test Model" "$HINDSIGHT_CLI" mental-model list "$TEST_BANK" || FAILED=1
135-
136-
# Test 21: Bank graph
137-
run_test "bank graph" "$HINDSIGHT_CLI" bank graph "$TEST_BANK" || FAILED=1
138-
139-
# Test 22: List operations
118+
# Test 15: List operations
140119
run_test "list operations" "$HINDSIGHT_CLI" operation list "$TEST_BANK" || FAILED=1
141120

142-
# Test 23: Delete bank
121+
# Test 16: Delete bank
143122
run_test "delete bank" "$HINDSIGHT_CLI" bank delete "$TEST_BANK" -y || FAILED=1
144123

145124
echo ""

hindsight-clients/python/.openapi-generator/FILES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ hindsight_client_api/models/entity_list_item.py
4242
hindsight_client_api/models/entity_list_response.py
4343
hindsight_client_api/models/entity_observation_response.py
4444
hindsight_client_api/models/entity_state_response.py
45+
hindsight_client_api/models/features_info.py
4546
hindsight_client_api/models/graph_data_response.py
4647
hindsight_client_api/models/http_validation_error.py
4748
hindsight_client_api/models/include_options.py
@@ -76,5 +77,6 @@ hindsight_client_api/models/update_disposition_request.py
7677
hindsight_client_api/models/update_reflection_request.py
7778
hindsight_client_api/models/validation_error.py
7879
hindsight_client_api/models/validation_error_loc_inner.py
80+
hindsight_client_api/models/version_response.py
7981
hindsight_client_api/rest.py
8082
hindsight_client_api_README.md

hindsight-clients/python/hindsight_client_api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
from hindsight_client_api.models.entity_list_response import EntityListResponse
6868
from hindsight_client_api.models.entity_observation_response import EntityObservationResponse
6969
from hindsight_client_api.models.entity_state_response import EntityStateResponse
70+
from hindsight_client_api.models.features_info import FeaturesInfo
7071
from hindsight_client_api.models.graph_data_response import GraphDataResponse
7172
from hindsight_client_api.models.http_validation_error import HTTPValidationError
7273
from hindsight_client_api.models.include_options import IncludeOptions
@@ -101,3 +102,4 @@
101102
from hindsight_client_api.models.update_reflection_request import UpdateReflectionRequest
102103
from hindsight_client_api.models.validation_error import ValidationError
103104
from hindsight_client_api.models.validation_error_loc_inner import ValidationErrorLocInner
105+
from hindsight_client_api.models.version_response import VersionResponse

0 commit comments

Comments
 (0)