feat: add detail parameter to list/get mental models (#846)

* feat: add detail parameter to list/get mental models (#825)

Add a `detail` query parameter (metadata|content|full) to both list and get
mental model endpoints (HTTP + MCP) to control response size. This reduces
payload for agent boot flows and MCP clients where context budget is limited.

Closes #825

* fix: update Rust CLI for optional mental model fields

The generated Rust client now has content/source_query as Option<String>
after the detail parameter was added. Update CLI code to handle optionals.
This commit is contained in:
Nicolò Boschi 2026-04-02 11:52:45 +02:00 committed by GitHub
parent 7d6c570a3a
commit 8d1bfbbd2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 594 additions and 154 deletions

View file

@ -1534,13 +1534,14 @@ class MentalModelResponse(BaseModel):
id: str
bank_id: str
name: str
source_query: str
content: str = Field(
description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)"
source_query: str | None = None
content: str | None = Field(
default=None,
description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)",
)
tags: list[str] = FieldWithDefault(list)
max_tokens: int = Field(default=2048)
trigger: MentalModelTrigger = FieldWithDefault(MentalModelTrigger)
max_tokens: int | None = Field(default=None)
trigger: MentalModelTrigger | None = Field(default=None)
last_refreshed_at: str | None = None
created_at: str | None = None
reflect_response: dict | None = Field(
@ -3041,6 +3042,10 @@ def _register_routes(app: FastAPI):
bank_id: str,
tags_filter: list[str] | None = Query(None, alias="tags", description="Filter by tags"),
tags_match: Literal["any", "all", "exact"] = Query("any", description="How to match tags"),
detail: Literal["metadata", "content", "full"] = Query(
"full",
description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
),
limit: int = Query(100, ge=1, le=1000),
offset: int = Query(0, ge=0),
request_context: RequestContext = Depends(get_request_context),
@ -3051,6 +3056,7 @@ def _register_routes(app: FastAPI):
bank_id=bank_id,
tags=tags_filter,
tags_match=tags_match,
detail=detail,
limit=limit,
offset=offset,
request_context=request_context,
@ -3078,6 +3084,10 @@ def _register_routes(app: FastAPI):
async def api_get_mental_model(
bank_id: str,
mental_model_id: str,
detail: Literal["metadata", "content", "full"] = Query(
"full",
description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
),
request_context: RequestContext = Depends(get_request_context),
):
"""Get a mental model by ID."""
@ -3085,6 +3095,7 @@ def _register_routes(app: FastAPI):
mental_model = await app.state.memory.get_mental_model(
bank_id=bank_id,
mental_model_id=mental_model_id,
detail=detail,
request_context=request_context,
)
if mental_model is None:

View file

@ -6351,6 +6351,7 @@ class MemoryEngine(MemoryEngineInterface):
*,
tags: list[str] | None = None,
tags_match: str = "any",
detail: str = "full",
limit: int = 100,
offset: int = 0,
request_context: "RequestContext",
@ -6361,6 +6362,7 @@ class MemoryEngine(MemoryEngineInterface):
bank_id: Bank identifier
tags: Optional tags to filter by
tags_match: How to match tags - 'any', 'all', or 'exact'
detail: Detail level - 'metadata', 'content', or 'full'
limit: Maximum number of results
offset: Offset for pagination
request_context: Request context for authentication
@ -6402,13 +6404,14 @@ class MemoryEngine(MemoryEngineInterface):
*params,
)
return [self._row_to_mental_model(row) for row in rows]
return [self._row_to_mental_model(row, detail=detail) for row in rows]
async def get_mental_model(
self,
bank_id: str,
mental_model_id: str,
*,
detail: str = "full",
request_context: "RequestContext",
) -> dict[str, Any] | None:
"""Get a single pinned mental model by ID.
@ -6416,6 +6419,7 @@ class MemoryEngine(MemoryEngineInterface):
Args:
bank_id: Bank identifier
mental_model_id: Pinned mental model UUID
detail: Detail level - 'metadata', 'content', or 'full'
request_context: Request context for authentication
Returns:
@ -6449,7 +6453,7 @@ class MemoryEngine(MemoryEngineInterface):
mental_model_id,
)
result = self._row_to_mental_model(row) if row else None
result = self._row_to_mental_model(row, detail=detail) if row else None
# Post-operation hook (usage recording)
if result and self._operation_validator:
@ -6847,34 +6851,47 @@ class MemoryEngine(MemoryEngineInterface):
return result == "DELETE 1"
def _row_to_mental_model(self, row) -> dict[str, Any]:
"""Convert a database row to a mental model dict."""
reflect_response = row.get("reflect_response")
# Parse JSON string to dict if needed (asyncpg may return JSONB as string)
if isinstance(reflect_response, str):
try:
reflect_response = json.loads(reflect_response)
except json.JSONDecodeError:
reflect_response = None
_MENTAL_MODEL_METADATA_FIELDS = frozenset({"id", "bank_id", "name", "tags", "last_refreshed_at", "created_at"})
def _row_to_mental_model(self, row, *, detail: str = "full") -> dict[str, Any]:
"""Convert a database row to a mental model dict.
Args:
row: Database row
detail: Detail level - 'metadata', 'content', or 'full'
"""
result: dict[str, Any] = {
"id": str(row["id"]),
"bank_id": row["bank_id"],
"name": row["name"],
"tags": row["tags"] or [],
"last_refreshed_at": row["last_refreshed_at"].isoformat() if row["last_refreshed_at"] else None,
"created_at": row["created_at"].isoformat() if row["created_at"] else None,
}
if detail == "metadata":
return result
trigger = row.get("trigger")
if isinstance(trigger, str):
try:
trigger = json.loads(trigger)
except json.JSONDecodeError:
trigger = None
return {
"id": str(row["id"]),
"bank_id": row["bank_id"],
"name": row["name"],
"source_query": row["source_query"],
"content": row["content"],
"tags": row["tags"] or [],
"max_tokens": row.get("max_tokens"),
"trigger": trigger,
"last_refreshed_at": row["last_refreshed_at"].isoformat() if row["last_refreshed_at"] else None,
"created_at": row["created_at"].isoformat() if row["created_at"] else None,
"reflect_response": reflect_response,
}
result["source_query"] = row["source_query"]
result["content"] = row["content"]
result["max_tokens"] = row.get("max_tokens")
result["trigger"] = trigger
if detail == "full":
reflect_response = row.get("reflect_response")
if isinstance(reflect_response, str):
try:
reflect_response = json.loads(reflect_response)
except json.JSONDecodeError:
reflect_response = None
result["reflect_response"] = reflect_response
return result
# =========================================================================
# Directives - Hard rules injected into prompts

View file

@ -1002,6 +1002,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
@mcp.tool()
async def list_mental_models(
tags: list[str] | None = None,
detail: str = "full",
bank_id: str | None = None,
) -> str:
"""
@ -1013,6 +1014,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
Args:
tags: Optional tags to filter by (returns models matching any tag)
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
bank_id: Optional bank to list from (defaults to session bank). Use for cross-bank operations.
"""
try:
@ -1023,6 +1025,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
models = await memory.list_mental_models(
bank_id=target_bank,
tags=tags,
detail=detail,
request_context=_get_request_context(config),
)
return json.dumps({"items": models}, indent=2, default=str)
@ -1038,6 +1041,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
@mcp.tool()
async def list_mental_models(
tags: list[str] | None = None,
detail: str = "full",
) -> dict:
"""
List mental models (pinned reflections) for this memory bank.
@ -1048,6 +1052,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
Args:
tags: Optional tags to filter by (returns models matching any tag)
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
"""
try:
target_bank = config.bank_id_resolver()
@ -1057,6 +1062,7 @@ def _register_list_mental_models(mcp: FastMCP, memory: MemoryEngine, config: MCP
models = await memory.list_mental_models(
bank_id=target_bank,
tags=tags,
detail=detail,
request_context=_get_request_context(config),
)
return {"items": models}
@ -1076,16 +1082,18 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
@mcp.tool()
async def get_mental_model(
mental_model_id: str,
detail: str = "full",
bank_id: str | None = None,
) -> str:
"""
Get a specific mental model by ID.
Returns the full mental model including its generated content, source query,
and metadata. Use list_mental_models first to discover available model IDs.
Returns the mental model with the requested detail level. Use list_mental_models
first to discover available model IDs.
Args:
mental_model_id: The ID of the mental model to retrieve
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
bank_id: Optional bank (defaults to session bank). Use for cross-bank operations.
"""
try:
@ -1096,6 +1104,7 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
model = await memory.get_mental_model(
bank_id=target_bank,
mental_model_id=mental_model_id,
detail=detail,
request_context=_get_request_context(config),
)
if model is None:
@ -1113,15 +1122,17 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
@mcp.tool()
async def get_mental_model(
mental_model_id: str,
detail: str = "full",
) -> dict:
"""
Get a specific mental model by ID.
Returns the full mental model including its generated content, source query,
and metadata. Use list_mental_models first to discover available model IDs.
Returns the mental model with the requested detail level. Use list_mental_models
first to discover available model IDs.
Args:
mental_model_id: The ID of the mental model to retrieve
detail: Detail level - 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response). Default: 'full'
"""
try:
target_bank = config.bank_id_resolver()
@ -1131,6 +1142,7 @@ def _register_get_mental_model(mcp: FastMCP, memory: MemoryEngine, config: MCPTo
model = await memory.get_mental_model(
bank_id=target_bank,
mental_model_id=mental_model_id,
detail=detail,
request_context=_get_request_context(config),
)
if model is None:

View file

@ -1,5 +1,6 @@
"""Tests for the shared MCP tools module."""
import json
from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock
@ -75,25 +76,63 @@ class TestBuildContentDict:
# =========================================================================
_MENTAL_MODEL_METADATA_FIELDS = frozenset({"id", "bank_id", "name", "tags", "last_refreshed_at", "created_at"})
_FULL_MENTAL_MODELS = [
{
"id": "mm-1",
"bank_id": "test-bank",
"name": "Coding Prefs",
"source_query": "coding preferences?",
"content": "Prefers Python",
"tags": ["coding"],
"max_tokens": 2048,
"trigger": {"interval": "daily"},
"last_refreshed_at": "2026-01-01T00:00:00",
"created_at": "2026-01-01T00:00:00",
"reflect_response": {"text": "Prefers Python", "based_on": {"world_facts": [{"id": "f1", "text": "Python is popular"}]}},
},
{
"id": "mm-2",
"bank_id": "test-bank",
"name": "Goals",
"source_query": "current goals?",
"content": "Ship v2",
"tags": [],
"max_tokens": 2048,
"trigger": None,
"last_refreshed_at": "2026-01-01T00:00:00",
"created_at": "2026-01-01T00:00:00",
"reflect_response": {"text": "Ship v2", "based_on": {}},
},
]
def _apply_detail(model: dict, detail: str) -> dict:
"""Simulate engine detail filtering for mocks."""
if detail == "metadata":
return {k: v for k, v in model.items() if k in _MENTAL_MODEL_METADATA_FIELDS}
if detail == "content":
return {k: v for k, v in model.items() if k != "reflect_response"}
return model
@pytest.fixture
def mock_memory():
"""Create a mock MemoryEngine with all MCP tool methods."""
memory = MagicMock()
# Mental model methods
memory.list_mental_models = AsyncMock(
return_value=[
{"id": "mm-1", "name": "Coding Prefs", "source_query": "coding preferences?", "content": "Prefers Python"},
{"id": "mm-2", "name": "Goals", "source_query": "current goals?", "content": "Ship v2"},
]
)
memory.get_mental_model = AsyncMock(
return_value={
"id": "mm-1",
"name": "Coding Prefs",
"source_query": "coding preferences?",
"content": "Prefers Python",
}
)
# Mental model methods — simulate engine detail filtering
async def _list_mental_models(**kwargs):
detail = kwargs.get("detail", "full")
return [_apply_detail(m, detail) for m in _FULL_MENTAL_MODELS]
async def _get_mental_model(**kwargs):
detail = kwargs.get("detail", "full")
return _apply_detail(_FULL_MENTAL_MODELS[0], detail)
memory.list_mental_models = AsyncMock(side_effect=_list_mental_models)
memory.get_mental_model = AsyncMock(side_effect=_get_mental_model)
memory.create_mental_model = AsyncMock(return_value={"id": "mm-new"})
memory.submit_async_refresh_mental_model = AsyncMock(return_value={"operation_id": "op-123"})
memory.update_mental_model = AsyncMock(
@ -390,12 +429,12 @@ class TestGetMentalModel:
assert mock_memory.get_mental_model.call_args.kwargs["bank_id"] == "other-bank"
async def test_get_not_found_multi_bank(self, mcp_server_with_mental_models, mock_memory):
mock_memory.get_mental_model.return_value = None
mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing")
assert "not found" in result
async def test_get_not_found_single_bank(self, mcp_server_single_bank, mock_memory):
mock_memory.get_mental_model.return_value = None
mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing")
assert isinstance(result, dict)
assert "not found" in result["error"]
@ -415,6 +454,97 @@ class TestGetMentalModel:
assert "error" in result
@pytest.mark.asyncio
class TestListMentalModelsDetail:
"""Test the detail parameter for list_mental_models."""
async def test_list_detail_full_includes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="full")
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" in item
assert "content" in item
assert "source_query" in item
async def test_list_detail_content_excludes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="content")
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" not in item
assert "content" in item
assert "source_query" in item
assert "trigger" in item
async def test_list_detail_metadata_only_has_core_fields(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn(detail="metadata")
parsed = json.loads(result)
item = parsed["items"][0]
assert item["id"] == "mm-1"
assert item["name"] == "Coding Prefs"
assert "tags" in item
assert "content" not in item
assert "source_query" not in item
assert "reflect_response" not in item
assert "trigger" not in item
async def test_list_detail_default_is_full(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["list_mental_models"].fn()
parsed = json.loads(result)
item = parsed["items"][0]
assert "reflect_response" in item
async def test_list_detail_single_bank_metadata(self, mcp_server_single_bank, mock_memory):
result = await _tools(mcp_server_single_bank)["list_mental_models"].fn(detail="metadata")
assert isinstance(result, dict)
item = result["items"][0]
assert "id" in item
assert "name" in item
assert "content" not in item
assert "reflect_response" not in item
@pytest.mark.asyncio
class TestGetMentalModelDetail:
"""Test the detail parameter for get_mental_model."""
async def test_get_detail_full_includes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="full"
)
parsed = json.loads(result)
assert "reflect_response" in parsed
assert "content" in parsed
async def test_get_detail_content_excludes_reflect_response(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="content"
)
parsed = json.loads(result)
assert "reflect_response" not in parsed
assert "content" in parsed
assert "source_query" in parsed
async def test_get_detail_metadata_only_has_core_fields(self, mcp_server_with_mental_models, mock_memory):
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(
mental_model_id="mm-1", detail="metadata"
)
parsed = json.loads(result)
assert parsed["id"] == "mm-1"
assert parsed["name"] == "Coding Prefs"
assert "tags" in parsed
assert "content" not in parsed
assert "reflect_response" not in parsed
assert "trigger" not in parsed
async def test_get_detail_single_bank_content(self, mcp_server_single_bank, mock_memory):
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(
mental_model_id="mm-1", detail="content"
)
assert isinstance(result, dict)
assert "content" in result
assert "reflect_response" not in result
@pytest.mark.asyncio
class TestCreateMentalModel:
async def test_create_multi_bank(self, mcp_server_with_mental_models, mock_memory):
@ -702,12 +832,12 @@ class TestMentalModelInputValidation:
mock_memory.update_mental_model.assert_not_called()
async def test_not_found_error_includes_bank_id_multi_bank(self, mcp_server_with_mental_models, mock_memory):
mock_memory.get_mental_model.return_value = None
mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_with_mental_models)["get_mental_model"].fn(mental_model_id="missing")
assert "test-bank" in result
async def test_not_found_error_includes_bank_id_single_bank(self, mcp_server_single_bank, mock_memory):
mock_memory.get_mental_model.return_value = None
mock_memory.get_mental_model.side_effect = AsyncMock(return_value=None)
result = await _tools(mcp_server_single_bank)["get_mental_model"].fn(mental_model_id="missing")
assert isinstance(result, dict)
assert "fixed-bank" in result["error"]

View file

@ -550,14 +550,14 @@ impl ApiClient {
pub fn list_mental_models(&self, bank_id: &str, _verbose: bool) -> Result<types::MentalModelListResponse> {
self.runtime.block_on(async {
let response = self.client.list_mental_models(bank_id, None, None, None, None, None).await?;
let response = self.client.list_mental_models(bank_id, None, None, None, None, None, None).await?;
Ok(response.into_inner())
})
}
pub fn get_mental_model(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result<types::MentalModelResponse> {
self.runtime.block_on(async {
let response = self.client.get_mental_model(bank_id, mental_model_id, None).await?;
let response = self.client.get_mental_model(bank_id, mental_model_id, None, None).await?;
Ok(response.into_inner())
})
}

View file

@ -43,9 +43,11 @@ pub fn list(
);
// Show content preview
let preview: String = mental_model.content.chars().take(80).collect();
let ellipsis = if mental_model.content.len() > 80 { "..." } else { "" };
if let Some(ref content) = mental_model.content {
let preview: String = content.chars().take(80).collect();
let ellipsis = if content.len() > 80 { "..." } else { "" };
println!(" {}{}", ui::dim(&preview), ellipsis);
}
println!();
}
@ -326,11 +328,15 @@ fn print_mental_model_detail(mental_model: &types::MentalModelResponse) {
ui::print_section_header(&mental_model.name);
println!(" {} {}", ui::dim("ID:"), ui::gradient_start(&mental_model.id));
println!(" {} {}", ui::dim("Source Query:"), &mental_model.source_query);
if let Some(ref source_query) = mental_model.source_query {
println!(" {} {}", ui::dim("Source Query:"), source_query);
}
if let Some(ref content) = mental_model.content {
println!();
println!("{}", ui::gradient_text("─── Content ───"));
println!();
println!("{}", &mental_model.content);
println!("{}", content);
println!();
}
}

View file

@ -656,6 +656,23 @@ paths:
title: Tags Match
type: string
style: form
- description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
explode: true
in: query
name: detail
required: false
schema:
default: full
description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
enum:
- metadata
- content
- full
title: Detail
type: string
style: form
- explode: true
in: query
name: limit
@ -810,6 +827,23 @@ paths:
title: Mental Model Id
type: string
style: simple
- description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
explode: true
in: query
name: detail
required: false
schema:
default: full
description: "Detail level: 'metadata' (names/tags only), 'content' (adds\
\ content/config), 'full' (includes reflect_response)"
enum:
- metadata
- content
- full
title: Detail
type: string
style: form
- explode: false
in: header
name: authorization
@ -4534,12 +4568,10 @@ components:
title: Name
type: string
source_query:
title: Source Query
nullable: true
type: string
content:
description: The mental model content as well-formatted markdown (auto-generated
from reflect endpoint)
title: Content
nullable: true
type: string
tags:
default: []
@ -4547,8 +4579,7 @@ components:
type: string
type: array
max_tokens:
default: 2048
title: Max Tokens
nullable: true
type: integer
trigger:
$ref: '#/components/schemas/MentalModelTrigger-Output'
@ -4563,10 +4594,8 @@ components:
nullable: true
required:
- bank_id
- content
- id
- name
- source_query
title: MentalModelResponse
MentalModelTrigger-Input:
description: Trigger settings for a mental model.

View file

@ -288,9 +288,16 @@ type ApiGetMentalModelRequest struct {
ApiService *MentalModelsAPIService
bankId string
mentalModelId string
detail *string
authorization *string
}
// Detail level: &#39;metadata&#39; (names/tags only), &#39;content&#39; (adds content/config), &#39;full&#39; (includes reflect_response)
func (r ApiGetMentalModelRequest) Detail(detail string) ApiGetMentalModelRequest {
r.detail = &detail
return r
}
func (r ApiGetMentalModelRequest) Authorization(authorization string) ApiGetMentalModelRequest {
r.authorization = &authorization
return r
@ -342,6 +349,12 @@ func (a *MentalModelsAPIService) GetMentalModelExecute(r ApiGetMentalModelReques
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.detail != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "detail", r.detail, "form", "")
} else {
var defaultValue string = "full"
r.detail = &defaultValue
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}
@ -541,6 +554,7 @@ type ApiListMentalModelsRequest struct {
bankId string
tags *[]string
tagsMatch *string
detail *string
limit *int32
offset *int32
authorization *string
@ -558,6 +572,12 @@ func (r ApiListMentalModelsRequest) TagsMatch(tagsMatch string) ApiListMentalMod
return r
}
// Detail level: &#39;metadata&#39; (names/tags only), &#39;content&#39; (adds content/config), &#39;full&#39; (includes reflect_response)
func (r ApiListMentalModelsRequest) Detail(detail string) ApiListMentalModelsRequest {
r.detail = &detail
return r
}
func (r ApiListMentalModelsRequest) Limit(limit int32) ApiListMentalModelsRequest {
r.limit = &limit
return r
@ -633,6 +653,12 @@ func (a *MentalModelsAPIService) ListMentalModelsExecute(r ApiListMentalModelsRe
var defaultValue string = "any"
r.tagsMatch = &defaultValue
}
if r.detail != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "detail", r.detail, "form", "")
} else {
var defaultValue string = "full"
r.detail = &defaultValue
}
if r.limit != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "limit", r.limit, "form", "")
} else {

View file

@ -24,12 +24,11 @@ type MentalModelResponse struct {
Id string `json:"id"`
BankId string `json:"bank_id"`
Name string `json:"name"`
SourceQuery string `json:"source_query"`
// The mental model content as well-formatted markdown (auto-generated from reflect endpoint)
Content string `json:"content"`
SourceQuery NullableString `json:"source_query,omitempty"`
Content NullableString `json:"content,omitempty"`
Tags []string `json:"tags,omitempty"`
MaxTokens *int32 `json:"max_tokens,omitempty"`
Trigger *MentalModelTriggerOutput `json:"trigger,omitempty"`
MaxTokens NullableInt32 `json:"max_tokens,omitempty"`
Trigger NullableMentalModelTriggerOutput `json:"trigger,omitempty"`
LastRefreshedAt NullableString `json:"last_refreshed_at,omitempty"`
CreatedAt NullableString `json:"created_at,omitempty"`
ReflectResponse map[string]interface{} `json:"reflect_response,omitempty"`
@ -41,15 +40,11 @@ type _MentalModelResponse MentalModelResponse
// This constructor will assign default values to properties that have it defined,
// and makes sure properties required by API are set, but the set of arguments
// will change when the set of required properties is changed
func NewMentalModelResponse(id string, bankId string, name string, sourceQuery string, content string) *MentalModelResponse {
func NewMentalModelResponse(id string, bankId string, name string) *MentalModelResponse {
this := MentalModelResponse{}
this.Id = id
this.BankId = bankId
this.Name = name
this.SourceQuery = sourceQuery
this.Content = content
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this
}
@ -58,8 +53,6 @@ func NewMentalModelResponse(id string, bankId string, name string, sourceQuery s
// but it doesn't guarantee that properties required by API are set
func NewMentalModelResponseWithDefaults() *MentalModelResponse {
this := MentalModelResponse{}
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this
}
@ -135,52 +128,88 @@ func (o *MentalModelResponse) SetName(v string) {
o.Name = v
}
// GetSourceQuery returns the SourceQuery field value
// GetSourceQuery returns the SourceQuery field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetSourceQuery() string {
if o == nil {
if o == nil || IsNil(o.SourceQuery.Get()) {
var ret string
return ret
}
return o.SourceQuery
return *o.SourceQuery.Get()
}
// GetSourceQueryOk returns a tuple with the SourceQuery field value
// GetSourceQueryOk returns a tuple with the SourceQuery field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetSourceQueryOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.SourceQuery, true
return o.SourceQuery.Get(), o.SourceQuery.IsSet()
}
// SetSourceQuery sets field value
// HasSourceQuery returns a boolean if a field has been set.
func (o *MentalModelResponse) HasSourceQuery() bool {
if o != nil && o.SourceQuery.IsSet() {
return true
}
return false
}
// SetSourceQuery gets a reference to the given NullableString and assigns it to the SourceQuery field.
func (o *MentalModelResponse) SetSourceQuery(v string) {
o.SourceQuery = v
o.SourceQuery.Set(&v)
}
// SetSourceQueryNil sets the value for SourceQuery to be an explicit nil
func (o *MentalModelResponse) SetSourceQueryNil() {
o.SourceQuery.Set(nil)
}
// GetContent returns the Content field value
// UnsetSourceQuery ensures that no value is present for SourceQuery, not even an explicit nil
func (o *MentalModelResponse) UnsetSourceQuery() {
o.SourceQuery.Unset()
}
// GetContent returns the Content field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetContent() string {
if o == nil {
if o == nil || IsNil(o.Content.Get()) {
var ret string
return ret
}
return o.Content
return *o.Content.Get()
}
// GetContentOk returns a tuple with the Content field value
// GetContentOk returns a tuple with the Content field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetContentOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Content, true
return o.Content.Get(), o.Content.IsSet()
}
// SetContent sets field value
// HasContent returns a boolean if a field has been set.
func (o *MentalModelResponse) HasContent() bool {
if o != nil && o.Content.IsSet() {
return true
}
return false
}
// SetContent gets a reference to the given NullableString and assigns it to the Content field.
func (o *MentalModelResponse) SetContent(v string) {
o.Content = v
o.Content.Set(&v)
}
// SetContentNil sets the value for Content to be an explicit nil
func (o *MentalModelResponse) SetContentNil() {
o.Content.Set(nil)
}
// UnsetContent ensures that no value is present for Content, not even an explicit nil
func (o *MentalModelResponse) UnsetContent() {
o.Content.Unset()
}
// GetTags returns the Tags field value if set, zero value otherwise.
@ -215,68 +244,88 @@ func (o *MentalModelResponse) SetTags(v []string) {
o.Tags = v
}
// GetMaxTokens returns the MaxTokens field value if set, zero value otherwise.
// GetMaxTokens returns the MaxTokens field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetMaxTokens() int32 {
if o == nil || IsNil(o.MaxTokens) {
if o == nil || IsNil(o.MaxTokens.Get()) {
var ret int32
return ret
}
return *o.MaxTokens
return *o.MaxTokens.Get()
}
// GetMaxTokensOk returns a tuple with the MaxTokens field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetMaxTokensOk() (*int32, bool) {
if o == nil || IsNil(o.MaxTokens) {
if o == nil {
return nil, false
}
return o.MaxTokens, true
return o.MaxTokens.Get(), o.MaxTokens.IsSet()
}
// HasMaxTokens returns a boolean if a field has been set.
func (o *MentalModelResponse) HasMaxTokens() bool {
if o != nil && !IsNil(o.MaxTokens) {
if o != nil && o.MaxTokens.IsSet() {
return true
}
return false
}
// SetMaxTokens gets a reference to the given int32 and assigns it to the MaxTokens field.
// SetMaxTokens gets a reference to the given NullableInt32 and assigns it to the MaxTokens field.
func (o *MentalModelResponse) SetMaxTokens(v int32) {
o.MaxTokens = &v
o.MaxTokens.Set(&v)
}
// SetMaxTokensNil sets the value for MaxTokens to be an explicit nil
func (o *MentalModelResponse) SetMaxTokensNil() {
o.MaxTokens.Set(nil)
}
// GetTrigger returns the Trigger field value if set, zero value otherwise.
// UnsetMaxTokens ensures that no value is present for MaxTokens, not even an explicit nil
func (o *MentalModelResponse) UnsetMaxTokens() {
o.MaxTokens.Unset()
}
// GetTrigger returns the Trigger field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *MentalModelResponse) GetTrigger() MentalModelTriggerOutput {
if o == nil || IsNil(o.Trigger) {
if o == nil || IsNil(o.Trigger.Get()) {
var ret MentalModelTriggerOutput
return ret
}
return *o.Trigger
return *o.Trigger.Get()
}
// GetTriggerOk returns a tuple with the Trigger field value if set, nil otherwise
// and a boolean to check if the value has been set.
// NOTE: If the value is an explicit nil, `nil, true` will be returned
func (o *MentalModelResponse) GetTriggerOk() (*MentalModelTriggerOutput, bool) {
if o == nil || IsNil(o.Trigger) {
if o == nil {
return nil, false
}
return o.Trigger, true
return o.Trigger.Get(), o.Trigger.IsSet()
}
// HasTrigger returns a boolean if a field has been set.
func (o *MentalModelResponse) HasTrigger() bool {
if o != nil && !IsNil(o.Trigger) {
if o != nil && o.Trigger.IsSet() {
return true
}
return false
}
// SetTrigger gets a reference to the given MentalModelTriggerOutput and assigns it to the Trigger field.
// SetTrigger gets a reference to the given NullableMentalModelTriggerOutput and assigns it to the Trigger field.
func (o *MentalModelResponse) SetTrigger(v MentalModelTriggerOutput) {
o.Trigger = &v
o.Trigger.Set(&v)
}
// SetTriggerNil sets the value for Trigger to be an explicit nil
func (o *MentalModelResponse) SetTriggerNil() {
o.Trigger.Set(nil)
}
// UnsetTrigger ensures that no value is present for Trigger, not even an explicit nil
func (o *MentalModelResponse) UnsetTrigger() {
o.Trigger.Unset()
}
// GetLastRefreshedAt returns the LastRefreshedAt field value if set, zero value otherwise (both if not set or set to explicit null).
@ -409,16 +458,20 @@ func (o MentalModelResponse) ToMap() (map[string]interface{}, error) {
toSerialize["id"] = o.Id
toSerialize["bank_id"] = o.BankId
toSerialize["name"] = o.Name
toSerialize["source_query"] = o.SourceQuery
toSerialize["content"] = o.Content
if o.SourceQuery.IsSet() {
toSerialize["source_query"] = o.SourceQuery.Get()
}
if o.Content.IsSet() {
toSerialize["content"] = o.Content.Get()
}
if !IsNil(o.Tags) {
toSerialize["tags"] = o.Tags
}
if !IsNil(o.MaxTokens) {
toSerialize["max_tokens"] = o.MaxTokens
if o.MaxTokens.IsSet() {
toSerialize["max_tokens"] = o.MaxTokens.Get()
}
if !IsNil(o.Trigger) {
toSerialize["trigger"] = o.Trigger
if o.Trigger.IsSet() {
toSerialize["trigger"] = o.Trigger.Get()
}
if o.LastRefreshedAt.IsSet() {
toSerialize["last_refreshed_at"] = o.LastRefreshedAt.Get()
@ -440,8 +493,6 @@ func (o *MentalModelResponse) UnmarshalJSON(data []byte) (err error) {
"id",
"bank_id",
"name",
"source_query",
"content",
}
allProperties := make(map[string]interface{})

View file

@ -648,6 +648,7 @@ class MentalModelsApi:
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
@ -670,6 +671,8 @@ class MentalModelsApi:
:type bank_id: str
:param mental_model_id: (required)
:type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization:
:type authorization: str
:param _request_timeout: timeout setting for this request. If one
@ -697,6 +700,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
detail=detail,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
@ -724,6 +728,7 @@ class MentalModelsApi:
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
@ -746,6 +751,8 @@ class MentalModelsApi:
:type bank_id: str
:param mental_model_id: (required)
:type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization:
:type authorization: str
:param _request_timeout: timeout setting for this request. If one
@ -773,6 +780,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
detail=detail,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
@ -800,6 +808,7 @@ class MentalModelsApi:
self,
bank_id: StrictStr,
mental_model_id: StrictStr,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
authorization: Optional[StrictStr] = None,
_request_timeout: Union[
None,
@ -822,6 +831,8 @@ class MentalModelsApi:
:type bank_id: str
:param mental_model_id: (required)
:type mental_model_id: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param authorization:
:type authorization: str
:param _request_timeout: timeout setting for this request. If one
@ -849,6 +860,7 @@ class MentalModelsApi:
_param = self._get_mental_model_serialize(
bank_id=bank_id,
mental_model_id=mental_model_id,
detail=detail,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
@ -871,6 +883,7 @@ class MentalModelsApi:
self,
bank_id,
mental_model_id,
detail,
authorization,
_request_auth,
_content_type,
@ -898,6 +911,10 @@ class MentalModelsApi:
if mental_model_id is not None:
_path_params['mental_model_id'] = mental_model_id
# process the query parameters
if detail is not None:
_query_params.append(('detail', detail))
# process the header parameters
if authorization is not None:
_header_params['authorization'] = authorization
@ -1235,6 +1252,7 @@ class MentalModelsApi:
bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None,
@ -1261,6 +1279,8 @@ class MentalModelsApi:
:type tags: List[str]
:param tags_match: How to match tags
:type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit:
:type limit: int
:param offset:
@ -1293,6 +1313,7 @@ class MentalModelsApi:
bank_id=bank_id,
tags=tags,
tags_match=tags_match,
detail=detail,
limit=limit,
offset=offset,
authorization=authorization,
@ -1323,6 +1344,7 @@ class MentalModelsApi:
bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None,
@ -1349,6 +1371,8 @@ class MentalModelsApi:
:type tags: List[str]
:param tags_match: How to match tags
:type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit:
:type limit: int
:param offset:
@ -1381,6 +1405,7 @@ class MentalModelsApi:
bank_id=bank_id,
tags=tags,
tags_match=tags_match,
detail=detail,
limit=limit,
offset=offset,
authorization=authorization,
@ -1411,6 +1436,7 @@ class MentalModelsApi:
bank_id: StrictStr,
tags: Annotated[Optional[List[StrictStr]], Field(description="Filter by tags")] = None,
tags_match: Annotated[Optional[StrictStr], Field(description="How to match tags")] = None,
detail: Annotated[Optional[StrictStr], Field(description="Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)")] = None,
limit: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = None,
offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = None,
authorization: Optional[StrictStr] = None,
@ -1437,6 +1463,8 @@ class MentalModelsApi:
:type tags: List[str]
:param tags_match: How to match tags
:type tags_match: str
:param detail: Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
:type detail: str
:param limit:
:type limit: int
:param offset:
@ -1469,6 +1497,7 @@ class MentalModelsApi:
bank_id=bank_id,
tags=tags,
tags_match=tags_match,
detail=detail,
limit=limit,
offset=offset,
authorization=authorization,
@ -1494,6 +1523,7 @@ class MentalModelsApi:
bank_id,
tags,
tags_match,
detail,
limit,
offset,
authorization,
@ -1530,6 +1560,10 @@ class MentalModelsApi:
_query_params.append(('tags_match', tags_match))
if detail is not None:
_query_params.append(('detail', detail))
if limit is not None:
_query_params.append(('limit', limit))

View file

@ -17,7 +17,7 @@ import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
from pydantic import BaseModel, ConfigDict, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.mental_model_trigger_output import MentalModelTriggerOutput
from typing import Optional, Set
@ -30,10 +30,10 @@ class MentalModelResponse(BaseModel):
id: StrictStr
bank_id: StrictStr
name: StrictStr
source_query: StrictStr
content: StrictStr = Field(description="The mental model content as well-formatted markdown (auto-generated from reflect endpoint)")
source_query: Optional[StrictStr] = None
content: Optional[StrictStr] = None
tags: Optional[List[StrictStr]] = None
max_tokens: Optional[StrictInt] = 2048
max_tokens: Optional[StrictInt] = None
trigger: Optional[MentalModelTriggerOutput] = None
last_refreshed_at: Optional[StrictStr] = None
created_at: Optional[StrictStr] = None
@ -82,6 +82,26 @@ class MentalModelResponse(BaseModel):
# override the default output from pydantic by calling `to_dict()` of trigger
if self.trigger:
_dict['trigger'] = self.trigger.to_dict()
# set to None if source_query (nullable) is None
# and model_fields_set contains the field
if self.source_query is None and "source_query" in self.model_fields_set:
_dict['source_query'] = None
# set to None if content (nullable) is None
# and model_fields_set contains the field
if self.content is None and "content" in self.model_fields_set:
_dict['content'] = None
# set to None if max_tokens (nullable) is None
# and model_fields_set contains the field
if self.max_tokens is None and "max_tokens" in self.model_fields_set:
_dict['max_tokens'] = None
# set to None if trigger (nullable) is None
# and model_fields_set contains the field
if self.trigger is None and "trigger" in self.model_fields_set:
_dict['trigger'] = None
# set to None if last_refreshed_at (nullable) is None
# and model_fields_set contains the field
if self.last_refreshed_at is None and "last_refreshed_at" in self.model_fields_set:
@ -115,7 +135,7 @@ class MentalModelResponse(BaseModel):
"source_query": obj.get("source_query"),
"content": obj.get("content"),
"tags": obj.get("tags"),
"max_tokens": obj.get("max_tokens") if obj.get("max_tokens") is not None else 2048,
"max_tokens": obj.get("max_tokens"),
"trigger": MentalModelTriggerOutput.from_dict(obj["trigger"]) if obj.get("trigger") is not None else None,
"last_refreshed_at": obj.get("last_refreshed_at"),
"created_at": obj.get("created_at"),

View file

@ -1435,13 +1435,13 @@ export type MentalModelResponse = {
/**
* Source Query
*/
source_query: string;
source_query?: string | null;
/**
* Content
*
* The mental model content as well-formatted markdown (auto-generated from reflect endpoint)
*/
content: string;
content?: string | null;
/**
* Tags
*/
@ -1449,8 +1449,8 @@ export type MentalModelResponse = {
/**
* Max Tokens
*/
max_tokens?: number;
trigger?: MentalModelTriggerOutput;
max_tokens?: number | null;
trigger?: MentalModelTriggerOutput | null;
/**
* Last Refreshed At
*/
@ -3331,6 +3331,12 @@ export type ListMentalModelsData = {
* How to match tags
*/
tags_match?: "any" | "all" | "exact";
/**
* Detail
*
* Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
*/
detail?: "metadata" | "content" | "full";
/**
* Limit
*/
@ -3458,7 +3464,14 @@ export type GetMentalModelData = {
*/
mental_model_id: string;
};
query?: never;
query?: {
/**
* Detail
*
* Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)
*/
detail?: "metadata" | "content" | "full";
};
url: "/v1/default/banks/{bank_id}/mental-models/{mental_model_id}";
};

View file

@ -186,19 +186,52 @@ Enable automatic refresh for mental models that need to stay current. Disable it
</TabItem>
</Tabs>
### Detail Levels
Both **List** and **Get** endpoints accept an optional `detail` query parameter that controls how much data is returned. This is useful for reducing response size, especially in agent boot flows or MCP clients where context budget is limited.
| Level | Fields Returned | Use Case |
|-------|----------------|----------|
| `metadata` | `id`, `bank_id`, `name`, `tags`, `last_refreshed_at`, `created_at` | Inventory — "what models exist?" |
| `content` | All metadata fields + `source_query`, `content`, `max_tokens`, `trigger` | Agent boot — "what do the models say?" |
| `full` (default) | All fields including `reflect_response` | Deep inspection — "what evidence backs this model?" |
```bash
# List only names and tags (smallest response)
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models?detail=metadata"
# List with content but without provenance chains
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models?detail=content"
# Get full detail (default behavior)
curl "$BASE_URL/v1/default/banks/$BANK_ID/mental-models/$MODEL_ID?detail=full"
```
The `detail` parameter is also available in the MCP tools:
```json
{"bank_id": "my-bank", "detail": "metadata"}
```
:::tip
Use `detail=content` for agent orientation flows. It includes everything the agent needs to understand the models without the heavyweight `reflect_response` provenance chains, which can exceed 200KB for banks with many models.
:::
### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique mental model ID |
| `bank_id` | string | Memory bank ID |
| `name` | string | Human-readable name |
| `source_query` | string | The query used to generate content |
| `content` | string | The generated mental model text |
| `tags` | list | Tags for filtering |
| `last_refreshed_at` | string | When the mental model was last updated |
| `created_at` | string | When the mental model was created |
| `reflect_response` | object | Full reflect response including `based_on` facts |
| Field | Type | Detail Level | Description |
|-------|------|-------------|-------------|
| `id` | string | metadata | Unique mental model ID |
| `bank_id` | string | metadata | Memory bank ID |
| `name` | string | metadata | Human-readable name |
| `tags` | list | metadata | Tags for filtering |
| `last_refreshed_at` | string | metadata | When the mental model was last updated |
| `created_at` | string | metadata | When the mental model was created |
| `source_query` | string | content | The query used to generate content |
| `content` | string | content | The generated mental model text |
| `max_tokens` | int | content | Maximum tokens for the mental model content |
| `trigger` | object | content | Trigger settings (see [Automatic Refresh](#automatic-refresh)) |
| `reflect_response` | object | full | Full reflect response including `based_on` provenance facts |
---

View file

@ -971,6 +971,23 @@
},
"description": "How to match tags"
},
{
"name": "detail",
"in": "query",
"required": false,
"schema": {
"enum": [
"metadata",
"content",
"full"
],
"type": "string",
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
"default": "full",
"title": "Detail"
},
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)"
},
{
"name": "limit",
"in": "query",
@ -1129,6 +1146,23 @@
"title": "Mental Model Id"
}
},
{
"name": "detail",
"in": "query",
"required": false,
"schema": {
"enum": [
"metadata",
"content",
"full"
],
"type": "string",
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)",
"default": "full",
"title": "Detail"
},
"description": "Detail level: 'metadata' (names/tags only), 'content' (adds content/config), 'full' (includes reflect_response)"
},
{
"name": "authorization",
"in": "header",
@ -6706,11 +6740,25 @@
"title": "Name"
},
"source_query": {
"type": "string",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Source Query"
},
"content": {
"type": "string",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Content",
"description": "The mental model content as well-formatted markdown (auto-generated from reflect endpoint)"
},
@ -6723,13 +6771,25 @@
"default": []
},
"max_tokens": {
"type": "integer",
"title": "Max Tokens",
"default": 2048
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Max Tokens"
},
"trigger": {
"$ref": "#/components/schemas/MentalModelTrigger-Output",
"default": {}
"anyOf": [
{
"$ref": "#/components/schemas/MentalModelTrigger-Output"
},
{
"type": "null"
}
]
},
"last_refreshed_at": {
"anyOf": [
@ -6771,9 +6831,7 @@
"required": [
"id",
"bank_id",
"name",
"source_query",
"content"
"name"
],
"title": "MentalModelResponse",
"description": "Response model for a mental model (stored reflect response)."