Compare commits

...
Author SHA1 Message Date
Nicolò Boschi 744f74a39a fix: update MCP tool count assertion for clear_mental_model 2026-05-25 15:28:21 +02:00
Nicolò Boschi 07c8870b7b ci: add clear_mental_model to CLI coverage skip list 2026-05-25 15:12:28 +02:00
Nicolò Boschi ef6fb55b00 docs: add SDK code examples for clear_mental_model
Add clear_mental_model to Python and TypeScript wrapper clients, and
add code snippets (Python, Node.js, CLI, Go) to the mental models
docs page using the same CodeSnippet pattern as other operations.
2026-05-25 15:05:00 +02:00
Nicolò Boschi 8e9fa2ac60 feat(api): add clear endpoint for mental model content (#1706)
Add POST /mental-models/{id}/clear that resets content to empty so the
next refresh performs a full re-synthesis regardless of trigger mode.
Useful for periodic compaction of delta-mode models that accumulate
drift over many incremental refreshes.
2026-05-25 14:58:08 +02:00
23 changed files with 1085 additions and 1 deletions
@@ -3971,6 +3971,48 @@ def _register_routes(app: FastAPI):
)
raise HTTPException(status_code=500, detail=str(e))
@app.post(
"/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear",
response_model=MentalModelResponse,
summary="Clear mental model content",
description=(
"Clear a mental model's content so the next refresh performs a full re-synthesis. "
"This is useful for delta-mode models that have accumulated drift over many "
"incremental refreshes. After clearing, call the /refresh endpoint to trigger "
"a clean full rebuild."
),
operation_id="clear_mental_model",
tags=["Mental Models"],
)
@audited("clear_mental_model", request_param=None)
async def api_clear_mental_model(
bank_id: str,
mental_model_id: str,
request_context: RequestContext = Depends(get_request_context),
):
"""Clear a mental model's content."""
try:
mental_model = await app.state.memory.clear_mental_model(
bank_id=bank_id,
mental_model_id=mental_model_id,
request_context=request_context,
)
if mental_model is None:
raise HTTPException(status_code=404, detail=f"Mental model '{mental_model_id}' not found")
return MentalModelResponse(**mental_model)
except OperationValidationError as e:
raise HTTPException(status_code=e.status_code, detail=e.reason)
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 POST /v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear: {error_detail}"
)
raise HTTPException(status_code=500, detail=str(e))
@app.patch(
"/v1/default/banks/{bank_id}/mental-models/{mental_model_id}",
response_model=MentalModelResponse,
@@ -107,6 +107,7 @@ def create_mcp_server(memory: MemoryEngine, multi_bank: bool = True) -> FastMCP:
"update_mental_model",
"delete_mental_model",
"refresh_mental_model",
"clear_mental_model",
"list_directives",
"create_directive",
"delete_directive",
@@ -8121,6 +8121,54 @@ class MemoryEngine(MemoryEngineInterface):
return self._row_to_mental_model(row) if row else None
async def clear_mental_model(
self,
bank_id: str,
mental_model_id: str,
*,
request_context: "RequestContext",
) -> dict[str, Any] | None:
"""Clear a mental model's content so the next refresh performs a full re-synthesis.
Resets content to an empty string and clears structured_content and
last_refreshed_source_query. This is useful for delta-mode models that
have accumulated drift after clearing, a normal /refresh will fall
back to full mode because there is no delta baseline.
Args:
bank_id: Bank identifier
mental_model_id: Mental model UUID
request_context: Request context for authentication
Returns:
Updated mental model dict or None if not found
"""
await self._authenticate_tenant(request_context)
if self._operation_validator:
from hindsight_api.extensions import BankWriteContext
ctx = BankWriteContext(bank_id=bank_id, operation="clear_mental_model", request_context=request_context)
await self._validate_operation(self._operation_validator.validate_bank_write(ctx))
backend = await self._get_backend()
async with acquire_with_retry(backend) as conn:
row = await conn.fetchrow(
f"""
UPDATE {fq_table("mental_models")}
SET content = '',
structured_content = NULL,
last_refreshed_source_query = NULL
WHERE bank_id = $1 AND id = $2
RETURNING id, bank_id, name, source_query, content, tags,
last_refreshed_at, created_at, reflect_response,
max_tokens, trigger, structured_content
""",
bank_id,
mental_model_id,
)
return self._row_to_mental_model(row) if row else None
async def delete_mental_model(
self,
bank_id: str,
@@ -44,6 +44,7 @@ _ALL_TOOLS: frozenset[str] = frozenset(
"update_mental_model",
"delete_mental_model",
"refresh_mental_model",
"clear_mental_model",
"list_directives",
"create_directive",
"delete_directive",
@@ -221,6 +222,7 @@ def register_mcp_tools(
"update_mental_model",
"delete_mental_model",
"refresh_mental_model",
"clear_mental_model",
"list_directives",
"create_directive",
"delete_directive",
@@ -277,6 +279,9 @@ def register_mcp_tools(
if "refresh_mental_model" in tools_to_register:
_register_refresh_mental_model(mcp, memory, config)
if "clear_mental_model" in tools_to_register:
_register_clear_mental_model(mcp, memory, config)
# Directive tools
if "list_directives" in tools_to_register:
_register_list_directives(mcp, memory, config)
@@ -438,6 +443,7 @@ _AUDITABLE_MCP_TOOLS: frozenset[str] = frozenset(
"update_mental_model",
"delete_mental_model",
"refresh_mental_model",
"clear_mental_model",
"create_directive",
"delete_directive",
"delete_document",
@@ -1773,6 +1779,98 @@ def _register_refresh_mental_model(mcp: FastMCP, memory: MemoryEngine, config: M
return {"error": str(e)}
def _register_clear_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPToolsConfig) -> None:
"""Register the clear_mental_model tool."""
if config.include_bank_id_param:
@mcp.tool()
async def clear_mental_model(
mental_model_id: str,
bank_id: str | None = None,
) -> str:
"""
Clear a mental model's content so the next refresh performs a full re-synthesis.
This is useful for delta-mode models that have accumulated drift over many
incremental refreshes. After clearing, call refresh_mental_model to trigger
a clean full rebuild.
Args:
mental_model_id: The ID of the mental model to clear
bank_id: Optional bank (defaults to session bank). Use for cross-bank operations.
"""
try:
target_bank = bank_id or config.bank_id_resolver()
if target_bank is None:
return '{"error": "No bank_id configured"}'
result = await memory.clear_mental_model(
bank_id=target_bank,
mental_model_id=mental_model_id,
request_context=_get_request_context(config),
)
if result is None:
return json.dumps({"error": f"Mental model '{mental_model_id}' not found"})
return json.dumps(
{
"mental_model_id": result["id"],
"status": "cleared",
"message": f"Mental model '{mental_model_id}' content cleared. Call refresh_mental_model to rebuild.",
}
)
except OperationValidationError as e:
logger.warning(f"Operation rejected: {e}")
return json.dumps({"error": str(e)})
except ValueError as e:
return json.dumps({"error": str(e)})
except Exception as e:
logger.error(f"Error clearing mental model: {e}", exc_info=True)
return f'{{"error": "{e}"}}'
else:
@mcp.tool()
async def clear_mental_model(
mental_model_id: str,
) -> dict:
"""
Clear a mental model's content so the next refresh performs a full re-synthesis.
This is useful for delta-mode models that have accumulated drift over many
incremental refreshes. After clearing, call refresh_mental_model to trigger
a clean full rebuild.
Args:
mental_model_id: The ID of the mental model to clear
"""
try:
target_bank = config.bank_id_resolver()
if target_bank is None:
return {"error": "No bank_id configured"}
result = await memory.clear_mental_model(
bank_id=target_bank,
mental_model_id=mental_model_id,
request_context=_get_request_context(config),
)
if result is None:
return {"error": f"Mental model '{mental_model_id}' not found"}
return {
"mental_model_id": result["id"],
"status": "cleared",
"message": f"Mental model '{mental_model_id}' content cleared. Call refresh_mental_model to rebuild.",
}
except OperationValidationError as e:
logger.warning(f"Operation rejected: {e}")
return {"error": str(e)}
except ValueError as e:
return {"error": str(e)}
except Exception as e:
logger.error(f"Error clearing mental model: {e}", exc_info=True)
return {"error": str(e)}
# =========================================================================
# DIRECTIVE TOOLS
# =========================================================================
+2 -1
View File
@@ -360,7 +360,8 @@ class TestMentalModelToolRegistration:
assert "delete_bank" in tools
assert "clear_memories" in tools
assert "sync_retain" in tools
assert len(tools) == 29
assert "clear_mental_model" in tools
assert len(tools) == 30
@pytest.fixture
@@ -1838,3 +1838,51 @@ class TestMentalModelTriggerSchema:
with pytest.raises(ValidationError):
MentalModelTrigger(tag_groups=[{"invalid_key": "bad"}])
class TestClearMentalModel:
"""Test clear_mental_model resets content so next refresh is full."""
async def test_clear_resets_content(self, memory: MemoryEngine, request_context):
"""Clear sets content to empty string and nulls structured/tracking fields."""
bank_id = f"test-mm-clear-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id, request_context=request_context)
mm = await memory.create_mental_model(
bank_id=bank_id,
name="Test Model",
source_query="What do we know?",
content="Some existing content",
request_context=request_context,
)
assert mm["content"] == "Some existing content"
cleared = await memory.clear_mental_model(
bank_id=bank_id,
mental_model_id=mm["id"],
request_context=request_context,
)
assert cleared is not None
assert cleared["content"] == ""
assert cleared["id"] == mm["id"]
assert cleared["name"] == "Test Model"
# Re-fetch to confirm persistence
fetched = await memory.get_mental_model(bank_id, mm["id"], request_context=request_context)
assert fetched["content"] == ""
await memory.delete_bank(bank_id, request_context=request_context)
async def test_clear_nonexistent_returns_none(self, memory: MemoryEngine, request_context):
"""Clearing a non-existent mental model returns None."""
bank_id = f"test-mm-clear-none-{uuid.uuid4().hex[:8]}"
await memory.get_bank_profile(bank_id, request_context=request_context)
result = await memory.clear_mental_model(
bank_id=bank_id,
mental_model_id="nonexistent-id",
request_context=request_context,
)
assert result is None
await memory.delete_bank(bank_id, request_context=request_context)
+3
View File
@@ -35,6 +35,9 @@ list_document_chunks = "UI-only endpoint for the control plane document detail d
# Reprocess triggers an async retain re-run; exposed in the control plane UI only.
reprocess_document = "UI-only endpoint for the control plane document detail dialog"
# Clear mental model content is a new endpoint; CLI subcommand not yet implemented.
clear_mental_model = "Not yet exposed in the CLI; use the HTTP API or SDK"
# ---------------------------------------------------------------------------
# Per-operation parameter skips
# ---------------------------------------------------------------------------
+48
View File
@@ -1157,6 +1157,54 @@ paths:
summary: Refresh mental model
tags:
- Mental Models
/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear:
post:
description: "Clear a mental model's content so the next refresh performs a\
\ full re-synthesis. This is useful for delta-mode models that have accumulated\
\ drift over many incremental refreshes. After clearing, call the /refresh\
\ endpoint to trigger a clean full rebuild."
operationId: clear_mental_model
parameters:
- explode: false
in: path
name: bank_id
required: true
schema:
title: Bank Id
type: string
style: simple
- explode: false
in: path
name: mental_model_id
required: true
schema:
title: Mental Model Id
type: string
style: simple
- explode: false
in: header
name: authorization
required: false
schema:
nullable: true
type: string
style: simple
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/MentalModelResponse'
description: Successful Response
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Clear mental model content
tags:
- Mental Models
/v1/default/banks/{bank_id}/directives:
get:
description: List hard rules that are injected into prompts.
+126
View File
@@ -24,6 +24,132 @@ import (
// MentalModelsAPIService MentalModelsAPI service
type MentalModelsAPIService service
type ApiClearMentalModelRequest struct {
ctx context.Context
ApiService *MentalModelsAPIService
bankId string
mentalModelId string
authorization *string
}
func (r ApiClearMentalModelRequest) Authorization(authorization string) ApiClearMentalModelRequest {
r.authorization = &authorization
return r
}
func (r ApiClearMentalModelRequest) Execute() (*MentalModelResponse, *http.Response, error) {
return r.ApiService.ClearMentalModelExecute(r)
}
/*
ClearMentalModel Clear mental model content
Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param bankId
@param mentalModelId
@return ApiClearMentalModelRequest
*/
func (a *MentalModelsAPIService) ClearMentalModel(ctx context.Context, bankId string, mentalModelId string) ApiClearMentalModelRequest {
return ApiClearMentalModelRequest{
ApiService: a,
ctx: ctx,
bankId: bankId,
mentalModelId: mentalModelId,
}
}
// Execute executes the request
// @return MentalModelResponse
func (a *MentalModelsAPIService) ClearMentalModelExecute(r ApiClearMentalModelRequest) (*MentalModelResponse, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *MentalModelResponse
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "MentalModelsAPIService.ClearMentalModel")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear"
localVarPath = strings.Replace(localVarPath, "{"+"bank_id"+"}", url.PathEscape(parameterValueToString(r.bankId, "bankId")), -1)
localVarPath = strings.Replace(localVarPath, "{"+"mental_model_id"+"}", url.PathEscape(parameterValueToString(r.mentalModelId, "mentalModelId")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
// set Content-Type header
localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
if localVarHTTPContentType != "" {
localVarHeaderParams["Content-Type"] = localVarHTTPContentType
}
// to determine the Accept header
localVarHTTPHeaderAccepts := []string{"application/json"}
// set Accept header
localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
if localVarHTTPHeaderAccept != "" {
localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
}
if r.authorization != nil {
parameterAddToHeaderOrQuery(localVarHeaderParams, "authorization", r.authorization, "simple", "")
}
req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
if err != nil {
return localVarReturnValue, nil, err
}
localVarHTTPResponse, err := a.client.callAPI(req)
if err != nil || localVarHTTPResponse == nil {
return localVarReturnValue, localVarHTTPResponse, err
}
localVarBody, err := io.ReadAll(localVarHTTPResponse.Body)
localVarHTTPResponse.Body.Close()
localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody))
if err != nil {
return localVarReturnValue, localVarHTTPResponse, err
}
if localVarHTTPResponse.StatusCode >= 300 {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: localVarHTTPResponse.Status,
}
if localVarHTTPResponse.StatusCode == 422 {
var v HTTPValidationError
err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr.error = err.Error()
return localVarReturnValue, localVarHTTPResponse, newErr
}
newErr.error = formatErrorMessage(localVarHTTPResponse.Status, &v)
newErr.model = v
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
if err != nil {
newErr := &GenericOpenAPIError{
body: localVarBody,
error: err.Error(),
}
return localVarReturnValue, localVarHTTPResponse, newErr
}
return localVarReturnValue, localVarHTTPResponse, nil
}
type ApiCreateMentalModelRequest struct {
ctx context.Context
ApiService *MentalModelsAPIService
@@ -1048,6 +1048,19 @@ class Hindsight:
"""
return _run_async(self._mental_models_api.refresh_mental_model(bank_id, mental_model_id, _request_timeout=self._timeout))
def clear_mental_model(self, bank_id: str, mental_model_id: str):
"""
Clear a mental model's content so the next refresh performs a full re-synthesis.
Args:
bank_id: The memory bank ID
mental_model_id: The mental model ID
Returns:
MentalModelResponse with cleared content
"""
return _run_async(self._mental_models_api.clear_mental_model(bank_id, mental_model_id, _request_timeout=self._timeout))
def update_mental_model(
self,
bank_id: str,
@@ -44,6 +44,299 @@ class MentalModelsApi:
self.api_client = api_client
@validate_call
async def clear_mental_model(
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> MentalModelResponse:
"""Clear mental model content
Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.
:param bank_id: (required)
:type bank_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
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._clear_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "MentalModelResponse",
'422': "HTTPValidationError",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
await response_data.read()
return self.api_client.response_deserialize(
response_data=response_data,
response_types_map=_response_types_map,
).data
@validate_call
async def clear_mental_model_with_http_info(
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> ApiResponse[MentalModelResponse]:
"""Clear mental model content
Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.
:param bank_id: (required)
:type bank_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
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._clear_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "MentalModelResponse",
'422': "HTTPValidationError",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
await response_data.read()
return self.api_client.response_deserialize(
response_data=response_data,
response_types_map=_response_types_map,
)
@validate_call
async def clear_mental_model_without_preload_content(
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
Annotated[StrictFloat, Field(gt=0)],
Tuple[
Annotated[StrictFloat, Field(gt=0)],
Annotated[StrictFloat, Field(gt=0)]
]
] = None,
_request_auth: Optional[Dict[StrictStr, Any]] = None,
_content_type: Optional[StrictStr] = None,
_headers: Optional[Dict[StrictStr, Any]] = None,
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
) -> RESTResponseType:
"""Clear mental model content
Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.
:param bank_id: (required)
:type bank_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
number provided, it will be total request
timeout. It can also be a pair (tuple) of
(connection, read) timeouts.
:type _request_timeout: int, tuple(int, int), optional
:param _request_auth: set to override the auth_settings for an a single
request; this effectively ignores the
authentication in the spec for a single request.
:type _request_auth: dict, optional
:param _content_type: force content-type for the request.
:type _content_type: str, Optional
:param _headers: set to override the headers for a single
request; this effectively ignores the headers
in the spec for a single request.
:type _headers: dict, optional
:param _host_index: set to override the host_index for a single
request; this effectively ignores the host_index
in the spec for a single request.
:type _host_index: int, optional
:return: Returns the result object.
""" # noqa: E501
_param = self._clear_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "MentalModelResponse",
'422': "HTTPValidationError",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _clear_mental_model_serialize(
self,
bank_id,
mental_model_id,
authorization,
_request_auth,
_content_type,
_headers,
_host_index,
) -> RequestSerialized:
_host = None
_collection_formats: Dict[str, str] = {
}
_path_params: Dict[str, str] = {}
_query_params: List[Tuple[str, str]] = []
_header_params: Dict[str, Optional[str]] = _headers or {}
_form_params: List[Tuple[str, str]] = []
_files: Dict[
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
] = {}
_body_params: Optional[bytes] = None
# process the path parameters
if bank_id is not None:
_path_params['bank_id'] = bank_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
# set the HTTP header `Accept`
if 'Accept' not in _header_params:
_header_params['Accept'] = self.api_client.select_header_accept(
[
'application/json'
]
)
# authentication setting
_auth_settings: List[str] = [
]
return self.api_client.param_serialize(
method='POST',
resource_path='/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear',
path_params=_path_params,
query_params=_query_params,
header_params=_header_params,
body=_body_params,
post_params=_form_params,
files=_files,
auth_settings=_auth_settings,
collection_formats=_collection_formats,
_host=_host,
_request_auth=_request_auth
)
@validate_call
async def create_mental_model(
self,
@@ -23,6 +23,9 @@ import type {
ClearMemoryObservationsData,
ClearMemoryObservationsErrors,
ClearMemoryObservationsResponses,
ClearMentalModelData,
ClearMentalModelErrors,
ClearMentalModelResponses,
ClearObservationsData,
ClearObservationsErrors,
ClearObservationsResponses,
@@ -563,6 +566,19 @@ export const refreshMentalModel = <ThrowOnError extends boolean = false>(
ThrowOnError
>({ url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/refresh", ...options });
/**
* Clear mental model content
*
* Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.
*/
export const clearMentalModel = <ThrowOnError extends boolean = false>(
options: Options<ClearMentalModelData, ThrowOnError>
) =>
(options.client ?? client).post<ClearMentalModelResponses, ClearMentalModelErrors, ThrowOnError>({
url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear",
...options,
});
/**
* List directives
*
@@ -4335,6 +4335,46 @@ export type RefreshMentalModelResponses = {
export type RefreshMentalModelResponse =
RefreshMentalModelResponses[keyof RefreshMentalModelResponses];
export type ClearMentalModelData = {
body?: never;
headers?: {
/**
* Authorization
*/
authorization?: string | null;
};
path: {
/**
* Bank Id
*/
bank_id: string;
/**
* Mental Model Id
*/
mental_model_id: string;
};
query?: never;
url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear";
};
export type ClearMentalModelErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type ClearMentalModelError = ClearMentalModelErrors[keyof ClearMentalModelErrors];
export type ClearMentalModelResponses = {
/**
* Successful Response
*/
200: MentalModelResponse;
};
export type ClearMentalModelResponse = ClearMentalModelResponses[keyof ClearMentalModelResponses];
export type ListDirectivesData = {
body?: never;
headers?: {
+17
View File
@@ -834,6 +834,23 @@ export class HindsightClient {
return this.validateResponse(response, "refreshMentalModel");
}
/**
* Clear a mental model's content so the next refresh performs a full re-synthesis.
*/
async clearMentalModel(
bankId: string,
mentalModelId: string,
options?: { signal?: AbortSignal }
): Promise<MentalModelResponse> {
const response = await sdk.clearMentalModel({
client: this.client,
path: { bank_id: bankId, mental_model_id: mentalModelId },
signal: options?.signal,
});
return this.validateResponse(response, "clearMentalModel");
}
/**
* Update a mental model's metadata.
*/
@@ -0,0 +1,38 @@
import { NextResponse } from "next/server";
import { dataplaneBankUrl, getDataplaneHeaders } from "@/lib/hindsight-client";
export async function POST(
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(
dataplaneBankUrl(bankId, `/mental-models/${encodeURIComponent(mentalModelId)}/clear`),
{ method: "POST", headers: getDataplaneHeaders() }
);
if (!response.ok) {
const errorText = await response.text();
console.error("API error clearing mental model:", errorText);
return NextResponse.json(
{ error: errorText || "Failed to clear mental model" },
{ status: response.status }
);
}
const data = await response.json();
return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error("Error clearing mental model:", error);
return NextResponse.json({ error: "Failed to clear mental model" }, { status: 500 });
}
}
@@ -283,6 +283,35 @@ Refreshing is useful when:
---
## Clear a Mental Model
Clear a mental model's content so the next refresh performs a **full re-synthesis** from scratch, regardless of the model's trigger mode.
This is useful for delta-mode models that have accumulated drift over many incremental refreshes. Over time, small inaccuracies can compound as each delta refresh only sees new facts since the last. Clearing and then refreshing produces a clean baseline from all facts.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={mentalModelsPy} section="clear-mental-model" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={mentalModelsMjs} section="clear-mental-model" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={mentalModelsSh} section="clear-mental-model" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={mentalModelsGo} section="clear-mental-model" language="go" />
</TabItem>
</Tabs>
The clear operation is synchronous and resets the content to an empty string. The model's configuration (name, source query, trigger settings) is preserved. Since the content is now empty, the next `/refresh` call will always perform a full regeneration — even if the model's trigger mode is set to `delta`.
:::tip
For long-lived delta-mode mental models, consider scheduling a periodic clear + refresh (e.g. every 48 hours) to keep the content accurate while still benefiting from incremental delta updates in between.
:::
---
## Update a Mental Model
Update the mental model's name:
@@ -125,6 +125,16 @@ func main() {
fmt.Printf("Refresh operation ID: %s\n", refreshResult.GetOperationId())
// [/docs:refresh-mental-model]
// [docs:clear-mental-model]
// Clear a mental model's content, then refresh for a full re-synthesis
client.MentalModelsAPI.ClearMentalModel(ctx, mmBankID, mentalModelID).Execute()
// Trigger a fresh full rebuild
fullRefreshResult, _, _ := client.MentalModelsAPI.RefreshMentalModel(ctx, mmBankID, mentalModelID).Execute()
fmt.Printf("Full refresh operation ID: %s\n", fullRefreshResult.GetOperationId())
// [/docs:clear-mental-model]
// [docs:update-mental-model]
// Update a mental model's metadata
newName := "Updated Team Communication Preferences"
@@ -96,6 +96,16 @@ const refreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
console.log(`Refresh operation ID: ${refreshResult.operation_id}`);
// [/docs:refresh-mental-model]
// [docs:clear-mental-model]
// Clear a mental model's content, then refresh for a full re-synthesis
await client.clearMentalModel(BANK_ID, mentalModelId);
// Trigger a fresh full rebuild
const fullRefreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
console.log(`Full refresh operation ID: ${fullRefreshResult.operation_id}`);
// [/docs:clear-mental-model]
// [docs:update-mental-model]
// Update a mental model's metadata
const updated = await client.updateMentalModel(BANK_ID, mentalModelId, {
@@ -109,6 +109,23 @@ if mental_model_id:
# [/docs:refresh-mental-model]
# [docs:clear-mental-model]
# Clear a mental model's content, then refresh for a full re-synthesis
client.clear_mental_model(
bank_id=BANK_ID,
mental_model_id=mental_model_id
)
# Trigger a fresh full rebuild
result = client.refresh_mental_model(
bank_id=BANK_ID,
mental_model_id=mental_model_id
)
print(f"Full refresh operation ID: {result.operation_id}")
# [/docs:clear-mental-model]
# [docs:update-mental-model]
# Update a mental model's metadata
updated = client.update_mental_model(
@@ -65,6 +65,14 @@ if [ -n "$MENTAL_MODEL_ID" ]; then
hindsight mental-model refresh "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:refresh-mental-model]
# [docs:clear-mental-model]
# Clear a mental model's content, then refresh for a full re-synthesis
curl -s -X POST "${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}/mental-models/${MENTAL_MODEL_ID}/clear"
# Trigger a fresh full rebuild
hindsight mental-model refresh "$BANK_ID" "$MENTAL_MODEL_ID"
# [/docs:clear-mental-model]
# [docs:update-mental-model]
# Update a mental model's metadata
hindsight mental-model update "$BANK_ID" "$MENTAL_MODEL_ID" \
+68
View File
@@ -1690,6 +1690,74 @@
}
}
},
"/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear": {
"post": {
"tags": [
"Mental Models"
],
"summary": "Clear mental model content",
"description": "Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.",
"operationId": "clear_mental_model",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "mental_model_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Mental Model Id"
}
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MentalModelResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/directives": {
"get": {
"tags": [
@@ -400,6 +400,48 @@ Refreshing is useful when:
---
## Clear a Mental Model
Clear a mental model's content so the next refresh performs a **full re-synthesis** from scratch, regardless of the model's trigger mode.
This is useful for delta-mode models that have accumulated drift over many incremental refreshes. Over time, small inaccuracies can compound as each delta refresh only sees new facts since the last. Clearing and then refreshing produces a clean baseline from all facts.
### Python
```python
# Section 'clear-mental-model' not found in api/mental-models.py
```
### Node.js
```javascript
// Clear a mental model's content, then refresh for a full re-synthesis
await client.clearMentalModel(BANK_ID, mentalModelId);
// Trigger a fresh full rebuild
const fullRefreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId);
console.log(`Full refresh operation ID: ${fullRefreshResult.operation_id}`);
```
### CLI
```bash
# Section 'clear-mental-model' not found in api/mental-models.sh
```
### Go
```go
# Section 'clear-mental-model' not found in api/mental-models.go
```
The clear operation is synchronous and resets the content to an empty string. The model's configuration (name, source query, trigger settings) is preserved. Since the content is now empty, the next `/refresh` call will always perform a full regeneration — even if the model's trigger mode is set to `delta`.
:::tip
For long-lived delta-mode mental models, consider scheduling a periodic clear + refresh (e.g. every 48 hours) to keep the content accurate while still benefiting from incremental delta updates in between.
---
## Update a Mental Model
Update the mental model's name:
@@ -1690,6 +1690,74 @@
}
}
},
"/v1/default/banks/{bank_id}/mental-models/{mental_model_id}/clear": {
"post": {
"tags": [
"Mental Models"
],
"summary": "Clear mental model content",
"description": "Clear a mental model's content so the next refresh performs a full re-synthesis. This is useful for delta-mode models that have accumulated drift over many incremental refreshes. After clearing, call the /refresh endpoint to trigger a clean full rebuild.",
"operationId": "clear_mental_model",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "mental_model_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Mental Model Id"
}
},
{
"name": "authorization",
"in": "header",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Authorization"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MentalModelResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/directives": {
"get": {
"tags": [