Skip to content

Commit 35127d5

Browse files
Add MentalModelRefreshContext and pre-operation validation for mental model create/refresh (#271)
Wire up validate_mental_model_refresh hook in the HTTP routes for both create and refresh mental model endpoints, allowing extensions to reject operations (e.g. insufficient credits) before queuing async LLM work.
1 parent 86c733c commit 35127d5

File tree

3 files changed

+66
-0
lines changed

3 files changed

+66
-0
lines changed

hindsight-api/hindsight_api/api/http.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,23 @@ async def api_create_mental_model(
23632363
):
23642364
"""Create a mental model (async - returns operation_id)."""
23652365
try:
2366+
# Pre-operation validation hook
2367+
validator = app.state.memory._operation_validator
2368+
if validator:
2369+
from hindsight_api.extensions.operation_validator import MentalModelRefreshContext
2370+
2371+
ctx = MentalModelRefreshContext(
2372+
bank_id=bank_id,
2373+
mental_model_id=None, # Not yet created
2374+
request_context=request_context,
2375+
)
2376+
validation = await validator.validate_mental_model_refresh(ctx)
2377+
if not validation.allowed:
2378+
raise OperationValidationError(
2379+
validation.reason or "Operation not allowed",
2380+
status_code=validation.status_code,
2381+
)
2382+
23662383
# 1. Create the mental model with placeholder content
23672384
mental_model = await app.state.memory.create_mental_model(
23682385
bank_id=bank_id,
@@ -2385,6 +2402,8 @@ async def api_create_mental_model(
23852402
raise HTTPException(status_code=400, detail=str(e))
23862403
except (AuthenticationError, HTTPException):
23872404
raise
2405+
except OperationValidationError as e:
2406+
raise HTTPException(status_code=e.status_code, detail=e.reason)
23882407
except Exception as e:
23892408
import traceback
23902409

@@ -2407,6 +2426,23 @@ async def api_refresh_mental_model(
24072426
):
24082427
"""Refresh a mental model by re-running its source query (async)."""
24092428
try:
2429+
# Pre-operation validation hook
2430+
validator = app.state.memory._operation_validator
2431+
if validator:
2432+
from hindsight_api.extensions.operation_validator import MentalModelRefreshContext
2433+
2434+
ctx = MentalModelRefreshContext(
2435+
bank_id=bank_id,
2436+
mental_model_id=mental_model_id,
2437+
request_context=request_context,
2438+
)
2439+
validation = await validator.validate_mental_model_refresh(ctx)
2440+
if not validation.allowed:
2441+
raise OperationValidationError(
2442+
validation.reason or "Operation not allowed",
2443+
status_code=validation.status_code,
2444+
)
2445+
24102446
result = await app.state.memory.submit_async_refresh_mental_model(
24112447
bank_id=bank_id,
24122448
mental_model_id=mental_model_id,
@@ -2417,6 +2453,8 @@ async def api_refresh_mental_model(
24172453
raise HTTPException(status_code=404, detail=str(e))
24182454
except (AuthenticationError, HTTPException):
24192455
raise
2456+
except OperationValidationError as e:
2457+
raise HTTPException(status_code=e.status_code, detail=e.reason)
24202458
except Exception as e:
24212459
import traceback
24222460

hindsight-api/hindsight_api/extensions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
# Mental Model operations
2828
MentalModelGetContext,
2929
MentalModelGetResult,
30+
MentalModelRefreshContext,
3031
MentalModelRefreshResult,
3132
# Core operations
3233
OperationValidationError,
@@ -72,6 +73,7 @@
7273
# Operation Validator - Mental Model
7374
"MentalModelGetContext",
7475
"MentalModelGetResult",
76+
"MentalModelRefreshContext",
7577
"MentalModelRefreshResult",
7678
# Tenant/Auth
7779
"ApiKeyTenantExtension",

hindsight-api/hindsight_api/extensions/operation_validator.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ class MentalModelGetContext:
210210
request_context: "RequestContext"
211211

212212

213+
@dataclass
214+
class MentalModelRefreshContext:
215+
"""Context for a mental model refresh/create operation validation (pre-operation)."""
216+
217+
bank_id: str
218+
mental_model_id: str | None # None for create (not yet assigned)
219+
request_context: "RequestContext"
220+
221+
213222
@dataclass
214223
class MentalModelGetResult:
215224
"""Result context for post-mental-model-GET hook."""
@@ -466,6 +475,23 @@ async def validate_mental_model_get(self, ctx: MentalModelGetContext) -> Validat
466475
"""
467476
return ValidationResult.accept()
468477

478+
async def validate_mental_model_refresh(self, ctx: MentalModelRefreshContext) -> ValidationResult:
479+
"""
480+
Validate a mental model refresh/create operation before execution.
481+
482+
Override to implement custom validation logic for mental model refresh.
483+
484+
Args:
485+
ctx: Context containing:
486+
- bank_id: Bank identifier
487+
- mental_model_id: Mental model identifier (None for create)
488+
- request_context: Request context with auth info
489+
490+
Returns:
491+
ValidationResult indicating whether the operation is allowed.
492+
"""
493+
return ValidationResult.accept()
494+
469495
# =========================================================================
470496
# Mental Model - Post-operation hooks (optional - override to implement)
471497
# =========================================================================

0 commit comments

Comments
 (0)