feat: bank template import/export with Template Hub (#819)

* feat(api): add bank template import/export endpoints

Add POST /banks/{bank_id}/import and GET /banks/{bank_id}/export
endpoints for declarative bank setup via JSON manifests.

A template manifest (version 1) can include bank config overrides
and mental model definitions. Import creates or updates mental
models matched by id, applies config as per-bank overrides, and
returns async operation IDs for content generation.

Export dumps a bank's explicit overrides and mental models as a
manifest that can be re-imported into another bank.

Includes control plane UI: bank creation dialog now accepts an
optional template JSON to pre-configure the bank on creation.

* docs: add Template Gallery page and bank templates reference

- Template Gallery (/templates) with search, category filter, manifest
  preview modal with copy-to-clipboard
- 5 starter templates: Customer Support, Research Assistant, Personal
  Journal, Code Review Buddy, Meeting Notes
- Bank Templates API reference doc (developer/api/bank-templates)
- Sidebar entry under API section

* docs: add Template Gallery links to navbar and sidebar

- Top navbar: "Templates" link between Integrations and Changelog
- Sidebar: "Template Gallery" in Resources section

* fix(docs): remove emoji icons, autofocus search, fix placeholder in template gallery

* docs: rename to Bank Templates, move to Resources sidebar only

* docs: add Bank Templates to Resources navbar dropdown

* feat(api): add directives to bank template import/export

- Add BankTemplateDirective model with name, content, priority, is_active, tags
- Import creates/updates directives matched by name
- Export includes all directives (active and inactive)
- Validation: duplicate names rejected, empty name/content caught
- Tests: 24 tests covering directives create/update, existing vs new
  bank import, validation, export with directives, full round-trip

* docs: add directives to bank templates docs and sample templates

* feat(api): add JSON Schema endpoint for bank template validation

- GET /v1/default/bank-template-schema returns the JSON Schema
  auto-generated from the Pydantic BankTemplateManifest model
- Static schema file at docs/static/bank-template-schema.json
- Docs updated with schema endpoint, static file link, and
  validation examples (Python jsonschema, Node ajv-cli)

* feat(api): live schema validation on import, fix schema endpoint path

- Move schema endpoint to /v1/bank-template-schema (system-level, not per-bank)
- Import endpoint now accepts raw JSON and validates with Pydantic manually,
  returning clean 400 errors instead of raw 422s for all validation failures
- All validation (schema + semantic) returns consistent 400 with detailed messages

* docs: add interactive JSON Schema viewer to Bank Templates page

Renders the Pydantic-generated schema as a collapsible property tree
with types, required badges, defaults, and descriptions. The schema
is imported from the static bank-template-schema.json file.

* ui: add template toggle switch and browse link to bank creation dialog

- Replace always-visible textarea with a switch toggle ("Import from template")
- Textarea only shows when switch is on, keeping the dialog clean by default
- Add "Browse templates" link pointing to hindsight.vectorize.io/templates
- Reset template state when switch is toggled off or dialog is cancelled

* ui: add empty state with Add Document CTA to data view

When a bank has 0 memories, the data view (all tabs: constellation,
graph, table, timeline) shows a centered empty state with a CTA
button that opens the Add Document dialog.

* docs: replace templates with Conversation and Coding Agent

Remove generic placeholder templates. Add two practical templates
based on actual integration patterns:

- Conversation: for chat agents (LiteLLM, LangGraph, Pydantic AI,
  Vercel AI SDK). Tracks user preferences, open threads.
- Coding Agent: for Claude Code/Codex. Tracks technical decisions,
  project context, developer preferences. High literalism.

* docs: rename gallery to Bank Templates Hub, keep API doc as Bank Templates

* docs: register layout-template and file-json icons in navbar and sidebar

* docs: register layout-template icon in DefaultNavbarItem for dropdown items

* docs: show integration icons on template cards

Templates now have an optional `integrations` field referencing
integration IDs from integrations.json. Icons are resolved at render
time and shown in the card header next to the category badge.

* docs: add Personal Assistant template for OpenClaw, Hermes, NemoClaw

* feat: add Export Template to bank actions + map all integrations to templates

- Add "Export Template" to the bank Actions dropdown — exports config,
  mental models, and directives as JSON, copies to clipboard
- Add export API route and client method
- Map remaining integrations to templates: CrewAI, AG2, Agno, Strands,
  LlamaIndex, local-mcp, skills → Conversation; hindclaw → Personal Assistant

* feat: add --template flag to LoCoMo benchmark + remove schema from Hub

- LoCoMo benchmark accepts --template <path> to apply a bank template
  manifest (config, mental models, directives) before ingestion
- Template is applied per-bank in both single-phase and two-phase modes
- BenchmarkRunner.apply_template() reuses the same engine methods as
  the /import API endpoint
- Remove Manifest Schema section from Bank Templates Hub page
  (schema stays in the API reference doc)

* refactor: remove description field from bank template manifest

* docs: remove tags, fact_types, and directives from starter templates

* docs: remove reflect_mission and disposition fields from starter templates

* build: validate template manifests against JSON Schema during docs build

* cleanup: remove unused JsonSchemaViewer component

* docs: remove retain_extraction_mode from starter templates

* ui: enable word wrap in template manifest preview

* docs: add link to Bank Templates reference doc from Hub page

* docs: convert bank templates doc to mdx with multi-language code snippets

- Convert bank-templates.md to .mdx with Tabs/CodeSnippet components
- Add example files: bank-templates.py, .mjs, .sh, .go with doc markers
- Examples cover import, dry-run, export, round-trip, and schema
- Regenerate OpenAPI spec and all client SDKs (Python, TS, Rust, Go)

* fix: migration revision collision + use typed models in benchmark template

- Rename merge migration d6e7f8a9b0c1 -> d6e7f8a9b0c2 to resolve
  revision ID collision with case_insensitive_entities_trgm_index
- Update a4b5c6d7e8f9 down_revision to point to the renamed migration
- Fix f-string lint in case_insensitive migration
- BenchmarkRunner.apply_template() now validates manifest through
  BankTemplateManifest Pydantic model instead of raw dict access
- Remove redundant inline imports (json, Path already at module top)

* fix(docs): add missing Go tab to dry-run code snippet

* ci: retrigger

* fix: sync skills openapi.json + fix bankId null type error in export

- Copy updated openapi.json to skills/hindsight-docs/references/
- Add null guard for bankId in Export Template onClick handler

* fix: sync generated files (memory_engine formatting, docs skill references)

* cleanup: remove obsolete migration collision workaround
This commit is contained in:
Nicolò Boschi 2026-04-02 12:21:53 +02:00 committed by GitHub
parent 9cfdd464a9
commit 30a319a6ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 9698 additions and 113 deletions

View file

@ -4,8 +4,8 @@ The previous GIN trigram index on canonical_name was case-sensitive, causing
"Alice" and "alice" to have different trigram sets. This recreates it on "Alice" and "alice" to have different trigram sets. This recreates it on
LOWER(canonical_name) so the % operator matches case-insensitively. LOWER(canonical_name) so the % operator matches case-insensitively.
Revision ID: 2eee35aa3cfc Revision ID: d6e7f8a9b0c1
Revises: d6e7f8a9b0c1 Revises: c5d6e7f8a9b0
Create Date: 2026-03-31 Create Date: 2026-03-31
""" """
@ -13,8 +13,8 @@ from collections.abc import Sequence
from alembic import context, op from alembic import context, op
revision: str = "2eee35aa3cfc" revision: str = "d6e7f8a9b0c1"
down_revision: str | Sequence[str] | None = "d6e7f8a9b0c1" down_revision: str | Sequence[str] | None = "c5d6e7f8a9b0"
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None
@ -27,7 +27,7 @@ def _get_schema_prefix() -> str:
def upgrade() -> None: def upgrade() -> None:
schema = _get_schema_prefix() schema = _get_schema_prefix()
# Drop the old case-sensitive trigram index # Drop the old case-sensitive trigram index
op.execute(f"DROP INDEX IF EXISTS {schema}entities_canonical_name_trgm_idx") op.execute("DROP INDEX IF EXISTS entities_canonical_name_trgm_idx")
# Create case-insensitive trigram index on LOWER(canonical_name) # Create case-insensitive trigram index on LOWER(canonical_name)
op.execute( op.execute(
f"CREATE INDEX IF NOT EXISTS entities_canonical_name_lower_trgm_idx " f"CREATE INDEX IF NOT EXISTS entities_canonical_name_lower_trgm_idx "
@ -36,8 +36,8 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
op.execute("DROP INDEX IF EXISTS entities_canonical_name_lower_trgm_idx")
schema = _get_schema_prefix() schema = _get_schema_prefix()
op.execute(f"DROP INDEX IF EXISTS {schema}entities_canonical_name_lower_trgm_idx")
# Restore original case-sensitive index # Restore original case-sensitive index
op.execute( op.execute(
f"CREATE INDEX IF NOT EXISTS entities_canonical_name_trgm_idx " f"CREATE INDEX IF NOT EXISTS entities_canonical_name_trgm_idx "

View file

@ -1,7 +1,7 @@
"""Fix per-bank vector indexes to match configured extension """Fix per-bank vector indexes to match configured extension
Revision ID: a4b5c6d7e8f9 Revision ID: a4b5c6d7e8f9
Revises: 2eee35aa3cfc Revises: c2d3e4f5g6h7, c5d6e7f8a9b0
Create Date: 2026-04-01 Create Date: 2026-04-01
Migration d5e6f7a8b9c0 hardcoded HNSW when creating per-bank partial vector Migration d5e6f7a8b9c0 hardcoded HNSW when creating per-bank partial vector
@ -21,7 +21,10 @@ from alembic import context, op
from sqlalchemy import text from sqlalchemy import text
revision: str = "a4b5c6d7e8f9" revision: str = "a4b5c6d7e8f9"
down_revision: str | Sequence[str] | None = "2eee35aa3cfc" # Updated: the merge migration d6e7f8a9b0c1 was renamed to d6e7f8a9b0c2
# to avoid colliding with the case_insensitive_entities_trgm_index migration
# that shares the same revision ID.
down_revision: str | Sequence[str] | None = "d6e7f8a9b0c2"
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None

View file

@ -1,34 +0,0 @@
"""Drop unused metadata column from documents table
Revision ID: d6e7f8a9b0c1
Revises: c2d3e4f5g6h7, c5d6e7f8a9b0
Create Date: 2026-03-30
The metadata column on documents was always stored as an empty dict {}.
Actual document metadata is stored inside retain_params.metadata.
"""
from collections.abc import Sequence
from alembic import context, op
revision: str = "d6e7f8a9b0c1"
down_revision: str | Sequence[str] | None = ("c2d3e4f5g6h7", "c5d6e7f8a9b0")
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _get_schema_prefix() -> str:
"""Get schema prefix for table names (required for multi-tenant support)."""
schema = context.config.get_main_option("target_schema")
return f'"{schema}".' if schema else ""
def upgrade() -> None:
schema = _get_schema_prefix()
op.execute(f"ALTER TABLE {schema}documents DROP COLUMN IF EXISTS metadata")
def downgrade() -> None:
schema = _get_schema_prefix()
op.execute(f"ALTER TABLE {schema}documents ADD COLUMN IF NOT EXISTS metadata jsonb DEFAULT '{{}}'")

View file

@ -8,12 +8,13 @@ the FastAPI application with all API endpoints.
import asyncio import asyncio
import json import json
import logging import logging
import re
import uuid import uuid
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Literal from typing import Any, Literal
from fastapi import Depends, FastAPI, File, Form, Header, HTTPException, Query, UploadFile from fastapi import Depends, FastAPI, File, Form, Header, HTTPException, Query, Request, UploadFile
from hindsight_api.engine.audit import AuditEntry, AuditLogger from hindsight_api.engine.audit import AuditEntry, AuditLogger
from hindsight_api.extensions import AuthenticationError from hindsight_api.extensions import AuthenticationError
@ -1611,6 +1612,186 @@ class UpdateMentalModelRequest(BaseModel):
trigger: MentalModelTrigger | None = Field(default=None, description="Trigger settings") trigger: MentalModelTrigger | None = Field(default=None, description="Trigger settings")
# =========================================================================
# Bank Templates (import/export)
# =========================================================================
# Current manifest schema version. Bump when making breaking changes.
BANK_TEMPLATE_CURRENT_VERSION = "1"
class BankTemplateMentalModel(BaseModel):
"""A mental model definition within a bank template manifest."""
id: str = Field(description="Unique ID for the mental model (alphanumeric lowercase with hyphens)")
name: str = Field(description="Human-readable name for the mental model")
source_query: str = Field(description="The query to run to generate content")
tags: list[str] = FieldWithDefault(list, description="Tags for scoped visibility")
max_tokens: int = Field(default=2048, ge=256, le=8192, description="Maximum tokens for generated content")
trigger: MentalModelTrigger = FieldWithDefault(MentalModelTrigger, description="Trigger settings")
@field_validator("id")
@classmethod
def validate_id(cls, v: str) -> str:
if not re.match(r"^[a-z0-9][a-z0-9-]*$", v):
raise ValueError(
f"Mental model id '{v}' must be alphanumeric lowercase with hyphens, starting with a letter or digit."
)
return v
class BankTemplateConfig(BaseModel):
"""Bank configuration fields within a template manifest.
Only includes configurable (per-bank) fields. Credential fields
(API keys, base URLs) are intentionally excluded for security.
"""
reflect_mission: str | None = Field(default=None, description="Mission/context for Reflect operations")
retain_mission: str | None = Field(default=None, description="Steers what gets extracted during retain")
retain_extraction_mode: str | None = Field(
default=None, description="Fact extraction mode: 'concise' (default), 'verbose', or 'custom'"
)
retain_custom_instructions: str | None = Field(
default=None, description="Custom extraction prompt (when mode='custom')"
)
retain_chunk_size: int | None = Field(default=None, description="Max token size for each content chunk")
enable_observations: bool | None = Field(default=None, description="Toggle observation consolidation")
observations_mission: str | None = Field(default=None, description="Controls what gets synthesised")
disposition_skepticism: int | None = Field(default=None, ge=1, le=5, description="Skepticism trait (1-5)")
disposition_literalism: int | None = Field(default=None, ge=1, le=5, description="Literalism trait (1-5)")
disposition_empathy: int | None = Field(default=None, ge=1, le=5, description="Empathy trait (1-5)")
entity_labels: list[str] | None = Field(default=None, description="Controlled vocabulary for entity labels")
entities_allow_free_form: bool | None = Field(
default=None, description="Allow entities outside the label vocabulary"
)
def get_config_updates(self) -> dict[str, Any]:
"""Return only the fields that were explicitly set (non-None)."""
return {k: v for k, v in self.model_dump().items() if v is not None}
class BankTemplateDirective(BaseModel):
"""A directive definition within a bank template manifest.
Directives are matched by name on re-import: existing directives
with the same name are updated, new ones are created.
"""
name: str = Field(description="Human-readable name for the directive (used as match key on re-import)")
content: str = Field(description="The directive text to inject into prompts")
priority: int = Field(default=0, description="Higher priority directives are injected first")
is_active: bool = Field(default=True, description="Whether this directive is active")
tags: list[str] = FieldWithDefault(list, description="Tags for filtering")
class BankTemplateManifest(BaseModel):
"""A bank template manifest for import/export.
Version field enables forward-compatible schema evolution: the API
auto-upgrades older manifest versions to the current schema on import.
"""
model_config = ConfigDict(
json_schema_extra={
"example": {
"version": "1",
"bank": {
"reflect_mission": "You are helping a support agent remember customer interactions.",
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"disposition_empathy": 5,
"enable_observations": True,
},
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {"refresh_after_consolidation": True},
}
],
"directives": [
{
"name": "Always be empathetic",
"content": "Always respond with empathy and understanding.",
"priority": 10,
}
],
}
}
)
version: str = Field(description="Manifest schema version (currently '1')")
bank: BankTemplateConfig | None = Field(
default=None, description="Bank configuration to apply. Omit to leave config unchanged."
)
mental_models: list[BankTemplateMentalModel] | None = Field(
default=None, description="Mental models to create or update (matched by id). Omit to leave unchanged."
)
directives: list[BankTemplateDirective] | None = Field(
default=None, description="Directives to create or update (matched by name). Omit to leave unchanged."
)
@field_validator("version")
@classmethod
def validate_version(cls, v: str) -> str:
try:
ver = int(v)
except ValueError:
raise ValueError(f"version must be a numeric string, got '{v}'")
if ver < 1:
raise ValueError("version must be >= 1")
if ver > int(BANK_TEMPLATE_CURRENT_VERSION):
raise ValueError(
f"version '{v}' is not supported by this server "
f"(max supported: {BANK_TEMPLATE_CURRENT_VERSION}). Please upgrade Hindsight."
)
return v
@field_validator("mental_models")
@classmethod
def validate_unique_mental_model_ids(
cls,
v: list[BankTemplateMentalModel] | None,
) -> list[BankTemplateMentalModel] | None:
if v is None:
return v
ids = [m.id for m in v]
duplicates = [mid for mid in ids if ids.count(mid) > 1]
if duplicates:
raise ValueError(f"Duplicate mental model ids: {sorted(set(duplicates))}")
return v
@field_validator("directives")
@classmethod
def validate_unique_directive_names(
cls,
v: list[BankTemplateDirective] | None,
) -> list[BankTemplateDirective] | None:
if v is None:
return v
names = [d.name for d in v]
duplicates = [n for n in names if names.count(n) > 1]
if duplicates:
raise ValueError(f"Duplicate directive names: {sorted(set(duplicates))}")
return v
class BankTemplateImportResponse(BaseModel):
"""Response model for the bank template import endpoint."""
bank_id: str = Field(description="Bank that was imported into")
config_applied: bool = Field(description="Whether bank config was updated")
mental_models_created: list[str] = FieldWithDefault(list, description="IDs of newly created mental models")
mental_models_updated: list[str] = FieldWithDefault(list, description="IDs of updated mental models")
directives_created: list[str] = FieldWithDefault(list, description="Names of newly created directives")
directives_updated: list[str] = FieldWithDefault(list, description="Names of updated directives")
operation_ids: list[str] = FieldWithDefault(
list, description="Operation IDs for mental model content generation (async)"
)
dry_run: bool = Field(default=False, description="True if this was a validation-only run")
class OperationResponse(BaseModel): class OperationResponse(BaseModel):
"""Response model for a single async operation.""" """Response model for a single async operation."""
@ -4176,6 +4357,304 @@ def _register_routes(app: FastAPI):
logger.error(f"Error in DELETE /v1/default/banks/{bank_id}: {error_detail}") logger.error(f"Error in DELETE /v1/default/banks/{bank_id}: {error_detail}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
# =====================================================================
# Bank Template Import / Export
# =====================================================================
def _validate_template(manifest: BankTemplateManifest) -> list[str]:
"""Validate a parsed manifest beyond Pydantic's structural checks.
Returns a list of human-readable error strings (e.g. invalid
extraction mode values, conflicting settings).
"""
errors: list[str] = []
if manifest.bank:
bank = manifest.bank
if bank.retain_extraction_mode is not None:
valid_modes = ("concise", "verbose", "custom", "chunks")
if bank.retain_extraction_mode not in valid_modes:
errors.append(
f"bank.retain_extraction_mode: must be one of {valid_modes}, "
f"got '{bank.retain_extraction_mode}'"
)
if bank.retain_custom_instructions and bank.retain_extraction_mode != "custom":
errors.append("bank.retain_custom_instructions: requires retain_extraction_mode='custom'")
if manifest.mental_models:
for i, mm in enumerate(manifest.mental_models):
if not mm.name.strip():
errors.append(f"mental_models[{i}].name: must not be empty")
if not mm.source_query.strip():
errors.append(f"mental_models[{i}].source_query: must not be empty")
if manifest.directives:
for i, d in enumerate(manifest.directives):
if not d.name.strip():
errors.append(f"directives[{i}].name: must not be empty")
if not d.content.strip():
errors.append(f"directives[{i}].content: must not be empty")
return errors
@app.post(
"/v1/default/banks/{bank_id}/import",
response_model=BankTemplateImportResponse,
summary="Import bank template",
description="Import a bank template manifest to create or update a bank's configuration, mental models, "
"and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. "
"Mental models are matched by id, directives by name — existing ones are updated, new ones are created. "
"Use dry_run=true to validate the manifest without applying changes.",
operation_id="import_bank_template",
tags=["Bank Templates"],
)
@audited("import_bank_template", request_param=None)
async def api_import_bank_template(
bank_id: str,
request: Request,
dry_run: bool = Query(default=False, description="Validate only, do not apply changes"),
request_context: RequestContext = Depends(get_request_context),
):
"""Import a bank template manifest."""
try:
# Parse raw JSON and validate against the Pydantic model manually
# so we can return clean error messages instead of raw 422s.
raw_body = await request.json()
from pydantic import ValidationError
try:
body = BankTemplateManifest.model_validate(raw_body)
except ValidationError as e:
errors = [f"{'.'.join(str(loc) for loc in err['loc'])}: {err['msg']}" for err in e.errors()]
raise HTTPException(
status_code=400,
detail=f"Template schema validation failed: {'; '.join(errors)}",
)
# Semantic validation beyond Pydantic structural checks
validation_errors = _validate_template(body)
if validation_errors:
raise HTTPException(
status_code=400,
detail=f"Template validation failed: {'; '.join(validation_errors)}",
)
if dry_run:
return BankTemplateImportResponse(
bank_id=bank_id,
config_applied=body.bank is not None,
mental_models_created=[m.id for m in (body.mental_models or [])],
directives_created=[d.name for d in (body.directives or [])],
dry_run=True,
)
# Ensure bank exists (auto-creates with defaults if needed)
await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
config_applied = False
if body.bank:
config_updates = body.bank.get_config_updates()
if config_updates:
await app.state.memory._config_resolver.update_bank_config(bank_id, config_updates, request_context)
config_applied = True
created_ids: list[str] = []
updated_ids: list[str] = []
operation_ids: list[str] = []
if body.mental_models:
# Fetch existing mental models to decide create vs update
existing = await app.state.memory.list_mental_models(bank_id=bank_id, request_context=request_context)
existing_by_id = {m["id"]: m for m in existing}
for mm in body.mental_models:
if mm.id in existing_by_id:
# Update existing mental model metadata
await app.state.memory.update_mental_model(
bank_id=bank_id,
mental_model_id=mm.id,
name=mm.name,
source_query=mm.source_query,
max_tokens=mm.max_tokens,
tags=mm.tags if mm.tags else None,
trigger=mm.trigger.model_dump() if mm.trigger else None,
request_context=request_context,
)
# Schedule a refresh to regenerate content with updated query
result = await app.state.memory.submit_async_refresh_mental_model(
bank_id=bank_id,
mental_model_id=mm.id,
request_context=request_context,
)
operation_ids.append(result["operation_id"])
updated_ids.append(mm.id)
else:
# Create new mental model
mental_model = await app.state.memory.create_mental_model(
bank_id=bank_id,
name=mm.name,
source_query=mm.source_query,
content="Generating content...",
mental_model_id=mm.id,
tags=mm.tags if mm.tags else None,
max_tokens=mm.max_tokens,
trigger=mm.trigger.model_dump() if mm.trigger else None,
request_context=request_context,
)
result = await app.state.memory.submit_async_refresh_mental_model(
bank_id=bank_id,
mental_model_id=mental_model["id"],
request_context=request_context,
)
operation_ids.append(result["operation_id"])
created_ids.append(mm.id)
directives_created: list[str] = []
directives_updated: list[str] = []
if body.directives:
# Fetch existing directives to decide create vs update (matched by name)
existing_directives = await app.state.memory.list_directives(
bank_id=bank_id, active_only=False, request_context=request_context
)
existing_by_name = {d["name"]: d for d in existing_directives}
for directive in body.directives:
if directive.name in existing_by_name:
await app.state.memory.update_directive(
bank_id=bank_id,
directive_id=existing_by_name[directive.name]["id"],
content=directive.content,
priority=directive.priority,
is_active=directive.is_active,
tags=directive.tags if directive.tags else None,
request_context=request_context,
)
directives_updated.append(directive.name)
else:
await app.state.memory.create_directive(
bank_id=bank_id,
name=directive.name,
content=directive.content,
priority=directive.priority,
is_active=directive.is_active,
tags=directive.tags if directive.tags else None,
request_context=request_context,
)
directives_created.append(directive.name)
return BankTemplateImportResponse(
bank_id=bank_id,
config_applied=config_applied,
mental_models_created=created_ids,
mental_models_updated=updated_ids,
directives_created=directives_created,
directives_updated=directives_updated,
operation_ids=operation_ids,
dry_run=False,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
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}/import: {error_detail}")
raise HTTPException(status_code=500, detail=str(e))
@app.get(
"/v1/default/banks/{bank_id}/export",
response_model=BankTemplateManifest,
summary="Export bank template",
description="Export a bank's current configuration, mental models, and directives as a template manifest. "
"The exported manifest can be imported into another bank to replicate the setup.",
operation_id="export_bank_template",
tags=["Bank Templates"],
)
async def api_export_bank_template(
bank_id: str,
request_context: RequestContext = Depends(get_request_context),
):
"""Export a bank's config and mental models as a template manifest."""
try:
# Authenticate and ensure bank exists
profile = await app.state.memory.get_bank_profile(bank_id, request_context=request_context)
if profile is None:
raise HTTPException(status_code=404, detail=f"Bank '{bank_id}' not found")
# Get bank-specific config overrides (not the fully resolved config,
# so the template only contains what was explicitly set on this bank)
await app.state.memory._authenticate_tenant(request_context)
bank_overrides = await app.state.memory._config_resolver._load_bank_config(bank_id)
# Filter to only BankTemplateConfig fields (exclude credentials, static fields)
template_config_fields = set(BankTemplateConfig.model_fields.keys())
filtered_overrides = {k: v for k, v in bank_overrides.items() if k in template_config_fields}
bank_config = BankTemplateConfig(**filtered_overrides) if filtered_overrides else None
# Get mental models
mental_models_raw = await app.state.memory.list_mental_models(
bank_id=bank_id, request_context=request_context
)
template_mental_models: list[BankTemplateMentalModel] = []
for mm in mental_models_raw:
trigger_data = mm.get("trigger", {})
trigger = MentalModelTrigger(**trigger_data) if trigger_data else MentalModelTrigger()
template_mental_models.append(
BankTemplateMentalModel(
id=mm["id"],
name=mm["name"],
source_query=mm["source_query"],
tags=mm.get("tags", []),
max_tokens=mm.get("max_tokens", 2048),
trigger=trigger,
)
)
# Get directives
directives_raw = await app.state.memory.list_directives(
bank_id=bank_id, active_only=False, request_context=request_context
)
template_directives: list[BankTemplateDirective] = []
for d in directives_raw:
template_directives.append(
BankTemplateDirective(
name=d["name"],
content=d["content"],
priority=d.get("priority", 0),
is_active=d.get("is_active", True),
tags=d.get("tags", []),
)
)
return BankTemplateManifest(
version=BANK_TEMPLATE_CURRENT_VERSION,
bank=bank_config,
mental_models=template_mental_models if template_mental_models else None,
directives=template_directives if template_directives else None,
)
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 GET /v1/default/banks/{bank_id}/export: {error_detail}")
raise HTTPException(status_code=500, detail=str(e))
@app.get(
"/v1/bank-template-schema",
summary="Get bank template JSON Schema",
description="Returns the JSON Schema for the bank template manifest format. "
"Use this to validate template manifests before importing.",
operation_id="get_bank_template_schema",
tags=["Bank Templates"],
)
async def api_get_bank_template_schema():
"""Return the JSON Schema for the bank template manifest."""
return BankTemplateManifest.model_json_schema()
@app.delete( @app.delete(
"/v1/default/banks/{bank_id}/observations", "/v1/default/banks/{bank_id}/observations",
response_model=DeleteResponse, response_model=DeleteResponse,

View file

@ -3830,7 +3830,6 @@ class MemoryEngine(MemoryEngineInterface):
bank_id: str, bank_id: str,
fact_type: str | None = None, fact_type: str | None = None,
*, *,
delete_bank_profile: bool = True,
request_context: "RequestContext", request_context: "RequestContext",
) -> dict[str, int]: ) -> dict[str, int]:
""" """
@ -3917,20 +3916,19 @@ class MemoryEngine(MemoryEngineInterface):
# Delete entities (cascades to unit_entities, entity_cooccurrences, memory_links with entity_id) # Delete entities (cascades to unit_entities, entity_cooccurrences, memory_links with entity_id)
await conn.execute(f"DELETE FROM {fq_table('entities')} WHERE bank_id = $1", bank_id) await conn.execute(f"DELETE FROM {fq_table('entities')} WHERE bank_id = $1", bank_id)
result = {
"memory_units_deleted": units_count,
"entities_deleted": entities_count,
"documents_deleted": documents_count,
}
if delete_bank_profile:
# Delete the bank profile and retrieve internal_id for HNSW index cleanup # Delete the bank profile and retrieve internal_id for HNSW index cleanup
internal_id = await conn.fetchval( internal_id = await conn.fetchval(
f"DELETE FROM {fq_table('banks')} WHERE bank_id = $1 RETURNING internal_id", bank_id f"DELETE FROM {fq_table('banks')} WHERE bank_id = $1 RETURNING internal_id", bank_id
) )
if internal_id: if internal_id:
bank_internal_id = str(internal_id) bank_internal_id = str(internal_id)
result["bank_deleted"] = True
result = {
"memory_units_deleted": units_count,
"entities_deleted": entities_count,
"documents_deleted": documents_count,
"bank_deleted": True,
}
except Exception as e: except Exception as e:
raise Exception(f"Failed to delete agent data: {str(e)}") raise Exception(f"Failed to delete agent data: {str(e)}")
@ -4332,9 +4330,8 @@ class MemoryEngine(MemoryEngineInterface):
link for link in links if link["from_unit_id"] in unit_id_set and link["to_unit_id"] in unit_id_set link for link in links if link["from_unit_id"] in unit_id_set and link["to_unit_id"] in unit_id_set
] ]
# Get entity information — for visible units AND their source memories # Get entity information — only for visible units
# (observations inherit entities from source memories) if unit_ids:
if all_relevant_ids:
unit_entities = await conn.fetch( unit_entities = await conn.fetch(
f""" f"""
SELECT ue.unit_id, e.canonical_name SELECT ue.unit_id, e.canonical_name
@ -4343,7 +4340,7 @@ class MemoryEngine(MemoryEngineInterface):
WHERE ue.unit_id = ANY($1::uuid[]) WHERE ue.unit_id = ANY($1::uuid[])
ORDER BY ue.unit_id ORDER BY ue.unit_id
""", """,
all_relevant_ids, unit_ids,
) )
else: else:
unit_entities = [] unit_entities = []
@ -6343,7 +6340,6 @@ class MemoryEngine(MemoryEngineInterface):
*, *,
tags: list[str] | None = None, tags: list[str] | None = None,
tags_match: str = "any", tags_match: str = "any",
detail: str = "full",
limit: int = 100, limit: int = 100,
offset: int = 0, offset: int = 0,
request_context: "RequestContext", request_context: "RequestContext",
@ -6354,7 +6350,6 @@ class MemoryEngine(MemoryEngineInterface):
bank_id: Bank identifier bank_id: Bank identifier
tags: Optional tags to filter by tags: Optional tags to filter by
tags_match: How to match tags - 'any', 'all', or 'exact' tags_match: How to match tags - 'any', 'all', or 'exact'
detail: Detail level - 'metadata', 'content', or 'full'
limit: Maximum number of results limit: Maximum number of results
offset: Offset for pagination offset: Offset for pagination
request_context: Request context for authentication request_context: Request context for authentication
@ -6396,14 +6391,13 @@ class MemoryEngine(MemoryEngineInterface):
*params, *params,
) )
return [self._row_to_mental_model(row, detail=detail) for row in rows] return [self._row_to_mental_model(row) for row in rows]
async def get_mental_model( async def get_mental_model(
self, self,
bank_id: str, bank_id: str,
mental_model_id: str, mental_model_id: str,
*, *,
detail: str = "full",
request_context: "RequestContext", request_context: "RequestContext",
) -> dict[str, Any] | None: ) -> dict[str, Any] | None:
"""Get a single pinned mental model by ID. """Get a single pinned mental model by ID.
@ -6411,7 +6405,6 @@ class MemoryEngine(MemoryEngineInterface):
Args: Args:
bank_id: Bank identifier bank_id: Bank identifier
mental_model_id: Pinned mental model UUID mental_model_id: Pinned mental model UUID
detail: Detail level - 'metadata', 'content', or 'full'
request_context: Request context for authentication request_context: Request context for authentication
Returns: Returns:
@ -6445,7 +6438,7 @@ class MemoryEngine(MemoryEngineInterface):
mental_model_id, mental_model_id,
) )
result = self._row_to_mental_model(row, detail=detail) if row else None result = self._row_to_mental_model(row) if row else None
# Post-operation hook (usage recording) # Post-operation hook (usage recording)
if result and self._operation_validator: if result and self._operation_validator:
@ -6843,47 +6836,34 @@ class MemoryEngine(MemoryEngineInterface):
return result == "DELETE 1" return result == "DELETE 1"
_MENTAL_MODEL_METADATA_FIELDS = frozenset({"id", "bank_id", "name", "tags", "last_refreshed_at", "created_at"}) def _row_to_mental_model(self, row) -> dict[str, Any]:
"""Convert a database row to a mental model dict."""
def _row_to_mental_model(self, row, *, detail: str = "full") -> dict[str, Any]: reflect_response = row.get("reflect_response")
"""Convert a database row to a mental model dict. # Parse JSON string to dict if needed (asyncpg may return JSONB as string)
if isinstance(reflect_response, str):
Args: try:
row: Database row reflect_response = json.loads(reflect_response)
detail: Detail level - 'metadata', 'content', or 'full' except json.JSONDecodeError:
""" reflect_response = None
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") trigger = row.get("trigger")
if isinstance(trigger, str): if isinstance(trigger, str):
try: try:
trigger = json.loads(trigger) trigger = json.loads(trigger)
except json.JSONDecodeError: except json.JSONDecodeError:
trigger = None trigger = None
result["source_query"] = row["source_query"] return {
result["content"] = row["content"] "id": str(row["id"]),
result["max_tokens"] = row.get("max_tokens") "bank_id": row["bank_id"],
result["trigger"] = trigger "name": row["name"],
"source_query": row["source_query"],
if detail == "full": "content": row["content"],
reflect_response = row.get("reflect_response") "tags": row["tags"] or [],
if isinstance(reflect_response, str): "max_tokens": row.get("max_tokens"),
try: "trigger": trigger,
reflect_response = json.loads(reflect_response) "last_refreshed_at": row["last_refreshed_at"].isoformat() if row["last_refreshed_at"] else None,
except json.JSONDecodeError: "created_at": row["created_at"].isoformat() if row["created_at"] else None,
reflect_response = None "reflect_response": reflect_response,
result["reflect_response"] = reflect_response }
return result
# ========================================================================= # =========================================================================
# Directives - Hard rules injected into prompts # Directives - Hard rules injected into prompts

View file

@ -0,0 +1,600 @@
"""Integration tests for bank template import/export endpoints."""
import pytest
import pytest_asyncio
import httpx
from datetime import datetime
from hindsight_api.api import create_app
@pytest_asyncio.fixture
async def api_client(memory):
"""Create an async test client for the FastAPI app."""
app = create_app(memory, initialize_memory=False)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
yield client
@pytest.fixture
def bank_id():
return f"template_test_{datetime.now().timestamp()}"
@pytest.fixture
def sample_template():
return {
"version": "1",
"bank": {
"reflect_mission": "Test mission for reflect",
"retain_mission": "Extract test data carefully",
"retain_extraction_mode": "verbose",
"disposition_empathy": 5,
"disposition_skepticism": 2,
"enable_observations": True,
"observations_mission": "Track test patterns",
},
"mental_models": [
{
"id": "test-model-one",
"name": "Test Model One",
"source_query": "What are the key patterns?",
"tags": ["test"],
"max_tokens": 1024,
"trigger": {"refresh_after_consolidation": True},
},
{
"id": "test-model-two",
"name": "Test Model Two",
"source_query": "What are the common issues?",
},
],
"directives": [
{
"name": "Be concise",
"content": "Always respond concisely.",
"priority": 10,
},
{
"name": "Use examples",
"content": "Include examples when explaining concepts.",
"tags": ["style"],
},
],
}
class TestImportValidation:
"""Test template manifest validation."""
@pytest.mark.asyncio
async def test_import_dry_run_valid(self, api_client, bank_id, sample_template):
"""dry_run=true with a valid manifest returns what would happen."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import?dry_run=true",
json=sample_template,
)
assert resp.status_code == 200
data = resp.json()
assert data["dry_run"] is True
assert data["config_applied"] is True
assert set(data["mental_models_created"]) == {"test-model-one", "test-model-two"}
assert set(data["directives_created"]) == {"Be concise", "Use examples"}
@pytest.mark.asyncio
async def test_import_invalid_version(self, api_client, bank_id):
"""Reject manifest with unsupported version."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={"version": "999"},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_import_invalid_extraction_mode(self, api_client, bank_id):
"""Semantic validation catches bad extraction mode."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {"retain_extraction_mode": "invalid_mode"},
},
)
assert resp.status_code == 400
assert "retain_extraction_mode" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_import_custom_instructions_without_custom_mode(self, api_client, bank_id):
"""Validate that custom_instructions requires extraction_mode=custom."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {
"retain_extraction_mode": "verbose",
"retain_custom_instructions": "some custom prompt",
},
},
)
assert resp.status_code == 400
assert "retain_custom_instructions" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_import_duplicate_mental_model_ids(self, api_client, bank_id):
"""Reject manifest with duplicate mental model IDs."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{"id": "dup-id", "name": "First", "source_query": "q1"},
{"id": "dup-id", "name": "Second", "source_query": "q2"},
],
},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_import_duplicate_directive_names(self, api_client, bank_id):
"""Reject manifest with duplicate directive names."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{"name": "Same Name", "content": "First"},
{"name": "Same Name", "content": "Second"},
],
},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_import_missing_mental_model_id(self, api_client, bank_id):
"""Mental model without id is rejected."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{"name": "No ID Model", "source_query": "test query"},
],
},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_import_invalid_mental_model_id_format(self, api_client, bank_id):
"""Mental model with invalid ID format is rejected."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{"id": "UPPERCASE-NOT-ALLOWED", "name": "Bad", "source_query": "q"},
],
},
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_import_empty_manifest(self, api_client, bank_id):
"""Import with no bank or mental_models is valid (no-op)."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={"version": "1"},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is False
assert data["mental_models_created"] == []
assert data["directives_created"] == []
@pytest.mark.asyncio
async def test_import_empty_mental_model_name(self, api_client, bank_id):
"""Semantic validation catches empty mental model name."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{"id": "test-mm", "name": " ", "source_query": "q"},
],
},
)
assert resp.status_code == 400
assert "name" in resp.json()["detail"]
@pytest.mark.asyncio
async def test_import_empty_directive_content(self, api_client, bank_id):
"""Semantic validation catches empty directive content."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{"name": "Bad Directive", "content": " "},
],
},
)
assert resp.status_code == 400
assert "content" in resp.json()["detail"]
class TestImportApply:
"""Test that import actually applies config, mental models, and directives."""
@pytest.mark.asyncio
async def test_import_applies_config(self, api_client, bank_id):
"""Import with bank config applies config overrides on a new bank."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {
"reflect_mission": "Imported mission",
"disposition_empathy": 4,
},
},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is True
assert data["dry_run"] is False
# Verify config was actually applied
config_resp = await api_client.get(f"/v1/default/banks/{bank_id}/config")
assert config_resp.status_code == 200
config = config_resp.json()
assert config["overrides"]["reflect_mission"] == "Imported mission"
assert config["overrides"]["disposition_empathy"] == 4
@pytest.mark.asyncio
async def test_import_into_existing_bank(self, api_client, bank_id):
"""Import into an already-existing bank applies config and creates resources."""
# Pre-create the bank
await api_client.put(f"/v1/default/banks/{bank_id}", json={})
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {"reflect_mission": "Existing bank mission"},
"mental_models": [
{"id": "existing-bank-mm", "name": "MM", "source_query": "q"},
],
"directives": [
{"name": "Existing Bank Directive", "content": "Be helpful"},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is True
assert "existing-bank-mm" in data["mental_models_created"]
assert "Existing Bank Directive" in data["directives_created"]
# Verify everything exists
config_resp = await api_client.get(f"/v1/default/banks/{bank_id}/config")
assert config_resp.json()["overrides"]["reflect_mission"] == "Existing bank mission"
mm_resp = await api_client.get(f"/v1/default/banks/{bank_id}/mental-models/existing-bank-mm")
assert mm_resp.status_code == 200
dir_resp = await api_client.get(f"/v1/default/banks/{bank_id}/directives")
assert dir_resp.status_code == 200
names = [d["name"] for d in dir_resp.json()["items"]]
assert "Existing Bank Directive" in names
@pytest.mark.asyncio
async def test_import_creates_mental_models(self, api_client, bank_id):
"""Import creates mental models and returns operation IDs."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{
"id": "import-mm-1",
"name": "Imported Model",
"source_query": "What patterns exist?",
"tags": ["imported"],
},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert "import-mm-1" in data["mental_models_created"]
assert len(data["operation_ids"]) == 1
# Verify mental model exists
mm_resp = await api_client.get(f"/v1/default/banks/{bank_id}/mental-models/import-mm-1")
assert mm_resp.status_code == 200
mm = mm_resp.json()
assert mm["name"] == "Imported Model"
assert mm["source_query"] == "What patterns exist?"
assert mm["tags"] == ["imported"]
@pytest.mark.asyncio
async def test_import_updates_existing_mental_models(self, api_client, bank_id):
"""Re-importing updates existing mental models matched by ID."""
# First import
await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{
"id": "reusable-mm",
"name": "Original Name",
"source_query": "Original query",
},
],
},
)
# Second import with same ID but different content
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{
"id": "reusable-mm",
"name": "Updated Name",
"source_query": "Updated query",
},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert "reusable-mm" in data["mental_models_updated"]
assert data["mental_models_created"] == []
# Verify update
mm_resp = await api_client.get(f"/v1/default/banks/{bank_id}/mental-models/reusable-mm")
assert mm_resp.status_code == 200
mm = mm_resp.json()
assert mm["name"] == "Updated Name"
assert mm["source_query"] == "Updated query"
@pytest.mark.asyncio
async def test_import_creates_directives(self, api_client, bank_id):
"""Import creates directives."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{
"name": "Test Directive",
"content": "Always be helpful and precise.",
"priority": 5,
"tags": ["test"],
},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert "Test Directive" in data["directives_created"]
assert data["directives_updated"] == []
# Verify directive exists
dir_resp = await api_client.get(f"/v1/default/banks/{bank_id}/directives")
assert dir_resp.status_code == 200
items = dir_resp.json()["items"]
assert len(items) == 1
assert items[0]["name"] == "Test Directive"
assert items[0]["content"] == "Always be helpful and precise."
assert items[0]["priority"] == 5
assert items[0]["tags"] == ["test"]
@pytest.mark.asyncio
async def test_import_updates_existing_directives(self, api_client, bank_id):
"""Re-importing updates existing directives matched by name."""
# First import
await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{"name": "Reusable Directive", "content": "Original content", "priority": 1},
],
},
)
# Second import with same name but different content
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{"name": "Reusable Directive", "content": "Updated content", "priority": 10},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert "Reusable Directive" in data["directives_updated"]
assert data["directives_created"] == []
# Verify update
dir_resp = await api_client.get(f"/v1/default/banks/{bank_id}/directives")
items = dir_resp.json()["items"]
directive = [d for d in items if d["name"] == "Reusable Directive"][0]
assert directive["content"] == "Updated content"
assert directive["priority"] == 10
@pytest.mark.asyncio
async def test_import_config_only(self, api_client, bank_id):
"""Import with only bank config (no mental_models or directives) works."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {"retain_extraction_mode": "verbose"},
},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is True
assert data["mental_models_created"] == []
assert data["directives_created"] == []
assert data["operation_ids"] == []
@pytest.mark.asyncio
async def test_import_mental_models_only(self, api_client, bank_id):
"""Import with only mental_models works."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"mental_models": [
{"id": "mm-only", "name": "MM Only", "source_query": "test"},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is False
assert "mm-only" in data["mental_models_created"]
assert data["directives_created"] == []
@pytest.mark.asyncio
async def test_import_directives_only(self, api_client, bank_id):
"""Import with only directives works."""
resp = await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"directives": [
{"name": "Dir Only", "content": "test directive"},
],
},
)
assert resp.status_code == 200
data = resp.json()
assert data["config_applied"] is False
assert data["mental_models_created"] == []
assert "Dir Only" in data["directives_created"]
class TestExport:
"""Test bank template export."""
@pytest.mark.asyncio
async def test_export_empty_bank(self, api_client, bank_id):
"""Export a bank with no overrides returns minimal manifest."""
# Create bank
await api_client.put(f"/v1/default/banks/{bank_id}", json={})
resp = await api_client.get(f"/v1/default/banks/{bank_id}/export")
assert resp.status_code == 200
data = resp.json()
assert data["version"] == "1"
assert data["bank"] is None
assert data["mental_models"] is None
assert data["directives"] is None
@pytest.mark.asyncio
async def test_export_after_import(self, api_client, bank_id):
"""Export after import returns the imported config, mental models, and directives."""
template = {
"version": "1",
"bank": {
"reflect_mission": "Roundtrip mission",
"disposition_empathy": 3,
},
"mental_models": [
{
"id": "roundtrip-mm",
"name": "Roundtrip Model",
"source_query": "What happened?",
"tags": ["roundtrip"],
"max_tokens": 512,
},
],
"directives": [
{
"name": "Roundtrip Directive",
"content": "Be thorough.",
"priority": 3,
"tags": ["roundtrip"],
},
],
}
# Import
import_resp = await api_client.post(f"/v1/default/banks/{bank_id}/import", json=template)
assert import_resp.status_code == 200
# Export
resp = await api_client.get(f"/v1/default/banks/{bank_id}/export")
assert resp.status_code == 200
data = resp.json()
assert data["version"] == "1"
assert data["bank"]["reflect_mission"] == "Roundtrip mission"
assert data["bank"]["disposition_empathy"] == 3
assert len(data["mental_models"]) == 1
mm = data["mental_models"][0]
assert mm["id"] == "roundtrip-mm"
assert mm["name"] == "Roundtrip Model"
assert mm["source_query"] == "What happened?"
assert mm["tags"] == ["roundtrip"]
assert mm["max_tokens"] == 512
assert len(data["directives"]) == 1
d = data["directives"][0]
assert d["name"] == "Roundtrip Directive"
assert d["content"] == "Be thorough."
assert d["priority"] == 3
assert d["tags"] == ["roundtrip"]
@pytest.mark.asyncio
async def test_export_reimport_roundtrip(self, api_client, bank_id):
"""Exported manifest can be re-imported into a new bank."""
# Set up source bank
await api_client.post(
f"/v1/default/banks/{bank_id}/import",
json={
"version": "1",
"bank": {"retain_mission": "Roundtrip test"},
"mental_models": [
{"id": "rt-mm", "name": "RT Model", "source_query": "test query"},
],
"directives": [
{"name": "RT Directive", "content": "test directive"},
],
},
)
# Export
export_resp = await api_client.get(f"/v1/default/banks/{bank_id}/export")
assert export_resp.status_code == 200
exported = export_resp.json()
# Import into a new bank
new_bank_id = f"{bank_id}_clone"
import_resp = await api_client.post(
f"/v1/default/banks/{new_bank_id}/import",
json=exported,
)
assert import_resp.status_code == 200
data = import_resp.json()
assert data["config_applied"] is True
assert "rt-mm" in data["mental_models_created"]
assert "RT Directive" in data["directives_created"]
@pytest.mark.asyncio
async def test_export_nonexistent_bank(self, api_client):
"""Export from a nonexistent bank returns the bank with defaults (auto-created)."""
resp = await api_client.get("/v1/default/banks/nonexistent-export-test/export")
# get_bank_profile auto-creates, so this returns a valid empty manifest
assert resp.status_code == 200
data = resp.json()
assert data["version"] == "1"

View file

@ -2102,6 +2102,111 @@ paths:
summary: Create or update memory bank summary: Create or update memory bank
tags: tags:
- Banks - Banks
/v1/default/banks/{bank_id}/import:
post:
description: "Import a bank template manifest to create or update a bank's configuration,\
\ mental models, and directives. If the bank does not exist it is created.\
\ Config fields are applied as per-bank overrides. Mental models are matched\
\ by id, directives by name — existing ones are updated, new ones are created.\
\ Use dry_run=true to validate the manifest without applying changes."
operationId: import_bank_template
parameters:
- explode: false
in: path
name: bank_id
required: true
schema:
title: Bank Id
type: string
style: simple
- description: "Validate only, do not apply changes"
explode: true
in: query
name: dry_run
required: false
schema:
default: false
description: "Validate only, do not apply changes"
title: Dry Run
type: boolean
style: form
- explode: false
in: header
name: authorization
required: false
schema:
nullable: true
type: string
style: simple
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/BankTemplateImportResponse'
description: Successful Response
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Import bank template
tags:
- Bank Templates
/v1/default/banks/{bank_id}/export:
get:
description: "Export a bank's current configuration, mental models, and directives\
\ as a template manifest. The exported manifest can be imported into another\
\ bank to replicate the setup."
operationId: export_bank_template
parameters:
- explode: false
in: path
name: bank_id
required: true
schema:
title: Bank 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/BankTemplateManifest'
description: Successful Response
"422":
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
description: Validation Error
summary: Export bank template
tags:
- Bank Templates
/v1/bank-template-schema:
get:
description: Returns the JSON Schema for the bank template manifest format.
Use this to validate template manifests before importing.
operationId: get_bank_template_schema
responses:
"200":
content:
application/json:
schema: {}
description: Successful Response
summary: Get bank template JSON Schema
tags:
- Bank Templates
/v1/default/banks/{bank_id}/observations: /v1/default/banks/{bank_id}/observations:
delete: delete:
description: Delete all observations for a memory bank. This is useful for resetting description: Delete all observations for a memory bank. This is useful for resetting
@ -3420,6 +3525,242 @@ components:
- total_links - total_links
- total_nodes - total_nodes
title: BankStatsResponse title: BankStatsResponse
BankTemplateConfig:
description: |-
Bank configuration fields within a template manifest.
Only includes configurable (per-bank) fields. Credential fields
(API keys, base URLs) are intentionally excluded for security.
properties:
reflect_mission:
nullable: true
type: string
retain_mission:
nullable: true
type: string
retain_extraction_mode:
nullable: true
type: string
retain_custom_instructions:
nullable: true
type: string
retain_chunk_size:
nullable: true
type: integer
enable_observations:
nullable: true
type: boolean
observations_mission:
nullable: true
type: string
disposition_skepticism:
maximum: 5.0
minimum: 1.0
nullable: true
type: integer
disposition_literalism:
maximum: 5.0
minimum: 1.0
nullable: true
type: integer
disposition_empathy:
maximum: 5.0
minimum: 1.0
nullable: true
type: integer
entity_labels:
items:
type: string
nullable: true
type: array
entities_allow_free_form:
nullable: true
type: boolean
title: BankTemplateConfig
BankTemplateDirective:
description: |-
A directive definition within a bank template manifest.
Directives are matched by name on re-import: existing directives
with the same name are updated, new ones are created.
properties:
name:
description: Human-readable name for the directive (used as match key on
re-import)
title: Name
type: string
content:
description: The directive text to inject into prompts
title: Content
type: string
priority:
default: 0
description: Higher priority directives are injected first
title: Priority
type: integer
is_active:
default: true
description: Whether this directive is active
title: Is Active
type: boolean
tags:
default: []
description: Tags for filtering
items:
type: string
type: array
required:
- content
- name
title: BankTemplateDirective
BankTemplateImportResponse:
description: Response model for the bank template import endpoint.
example:
operation_ids:
- operation_ids
- operation_ids
directives_created:
- directives_created
- directives_created
bank_id: bank_id
mental_models_updated:
- mental_models_updated
- mental_models_updated
directives_updated:
- directives_updated
- directives_updated
config_applied: true
mental_models_created:
- mental_models_created
- mental_models_created
dry_run: false
properties:
bank_id:
description: Bank that was imported into
title: Bank Id
type: string
config_applied:
description: Whether bank config was updated
title: Config Applied
type: boolean
mental_models_created:
default: []
description: IDs of newly created mental models
items:
type: string
type: array
mental_models_updated:
default: []
description: IDs of updated mental models
items:
type: string
type: array
directives_created:
default: []
description: Names of newly created directives
items:
type: string
type: array
directives_updated:
default: []
description: Names of updated directives
items:
type: string
type: array
operation_ids:
default: []
description: Operation IDs for mental model content generation (async)
items:
type: string
type: array
dry_run:
default: false
description: True if this was a validation-only run
title: Dry Run
type: boolean
required:
- bank_id
- config_applied
title: BankTemplateImportResponse
BankTemplateManifest:
description: |-
A bank template manifest for import/export.
Version field enables forward-compatible schema evolution: the API
auto-upgrades older manifest versions to the current schema on import.
example:
bank:
disposition_empathy: 5
enable_observations: true
reflect_mission: You are helping a support agent remember customer interactions.
retain_mission: "Extract customer issues, resolutions, and sentiment."
directives:
- content: Always respond with empathy and understanding.
name: Always be empathetic
priority: 10
mental_models:
- id: sentiment-overview
name: Customer Sentiment Overview
source_query: What is the overall sentiment trend?
trigger:
refresh_after_consolidation: true
version: "1"
properties:
version:
description: Manifest schema version (currently '1')
title: Version
type: string
bank:
$ref: '#/components/schemas/BankTemplateConfig'
mental_models:
items:
$ref: '#/components/schemas/BankTemplateMentalModel'
nullable: true
type: array
directives:
items:
$ref: '#/components/schemas/BankTemplateDirective'
nullable: true
type: array
required:
- version
title: BankTemplateManifest
BankTemplateMentalModel:
description: A mental model definition within a bank template manifest.
properties:
id:
description: Unique ID for the mental model (alphanumeric lowercase with
hyphens)
title: Id
type: string
name:
description: Human-readable name for the mental model
title: Name
type: string
source_query:
description: The query to run to generate content
title: Source Query
type: string
tags:
default: []
description: Tags for scoped visibility
items:
type: string
type: array
max_tokens:
default: 2048
description: Maximum tokens for generated content
maximum: 8192.0
minimum: 256.0
title: Max Tokens
type: integer
trigger:
$ref: '#/components/schemas/MentalModelTrigger-Output'
required:
- id
- name
- source_query
title: BankTemplateMentalModel
Body_file_retain: Body_file_retain:
properties: properties:
files: files:

View file

@ -0,0 +1,380 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"bytes"
"context"
"io"
"net/http"
"net/url"
"strings"
)
// BankTemplatesAPIService BankTemplatesAPI service
type BankTemplatesAPIService service
type ApiExportBankTemplateRequest struct {
ctx context.Context
ApiService *BankTemplatesAPIService
bankId string
authorization *string
}
func (r ApiExportBankTemplateRequest) Authorization(authorization string) ApiExportBankTemplateRequest {
r.authorization = &authorization
return r
}
func (r ApiExportBankTemplateRequest) Execute() (*BankTemplateManifest, *http.Response, error) {
return r.ApiService.ExportBankTemplateExecute(r)
}
/*
ExportBankTemplate Export bank template
Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param bankId
@return ApiExportBankTemplateRequest
*/
func (a *BankTemplatesAPIService) ExportBankTemplate(ctx context.Context, bankId string) ApiExportBankTemplateRequest {
return ApiExportBankTemplateRequest{
ApiService: a,
ctx: ctx,
bankId: bankId,
}
}
// Execute executes the request
// @return BankTemplateManifest
func (a *BankTemplatesAPIService) ExportBankTemplateExecute(r ApiExportBankTemplateRequest) (*BankTemplateManifest, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *BankTemplateManifest
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "BankTemplatesAPIService.ExportBankTemplate")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/v1/default/banks/{bank_id}/export"
localVarPath = strings.Replace(localVarPath, "{"+"bank_id"+"}", url.PathEscape(parameterValueToString(r.bankId, "bankId")), -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 ApiGetBankTemplateSchemaRequest struct {
ctx context.Context
ApiService *BankTemplatesAPIService
}
func (r ApiGetBankTemplateSchemaRequest) Execute() (interface{}, *http.Response, error) {
return r.ApiService.GetBankTemplateSchemaExecute(r)
}
/*
GetBankTemplateSchema Get bank template JSON Schema
Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@return ApiGetBankTemplateSchemaRequest
*/
func (a *BankTemplatesAPIService) GetBankTemplateSchema(ctx context.Context) ApiGetBankTemplateSchemaRequest {
return ApiGetBankTemplateSchemaRequest{
ApiService: a,
ctx: ctx,
}
}
// Execute executes the request
// @return interface{}
func (a *BankTemplatesAPIService) GetBankTemplateSchemaExecute(r ApiGetBankTemplateSchemaRequest) (interface{}, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodGet
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue interface{}
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "BankTemplatesAPIService.GetBankTemplateSchema")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/v1/bank-template-schema"
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
}
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,
}
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 ApiImportBankTemplateRequest struct {
ctx context.Context
ApiService *BankTemplatesAPIService
bankId string
dryRun *bool
authorization *string
}
// Validate only, do not apply changes
func (r ApiImportBankTemplateRequest) DryRun(dryRun bool) ApiImportBankTemplateRequest {
r.dryRun = &dryRun
return r
}
func (r ApiImportBankTemplateRequest) Authorization(authorization string) ApiImportBankTemplateRequest {
r.authorization = &authorization
return r
}
func (r ApiImportBankTemplateRequest) Execute() (*BankTemplateImportResponse, *http.Response, error) {
return r.ApiService.ImportBankTemplateExecute(r)
}
/*
ImportBankTemplate Import bank template
Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param bankId
@return ApiImportBankTemplateRequest
*/
func (a *BankTemplatesAPIService) ImportBankTemplate(ctx context.Context, bankId string) ApiImportBankTemplateRequest {
return ApiImportBankTemplateRequest{
ApiService: a,
ctx: ctx,
bankId: bankId,
}
}
// Execute executes the request
// @return BankTemplateImportResponse
func (a *BankTemplatesAPIService) ImportBankTemplateExecute(r ApiImportBankTemplateRequest) (*BankTemplateImportResponse, *http.Response, error) {
var (
localVarHTTPMethod = http.MethodPost
localVarPostBody interface{}
formFiles []formFile
localVarReturnValue *BankTemplateImportResponse
)
localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "BankTemplatesAPIService.ImportBankTemplate")
if err != nil {
return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()}
}
localVarPath := localBasePath + "/v1/default/banks/{bank_id}/import"
localVarPath = strings.Replace(localVarPath, "{"+"bank_id"+"}", url.PathEscape(parameterValueToString(r.bankId, "bankId")), -1)
localVarHeaderParams := make(map[string]string)
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.dryRun != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "dry_run", r.dryRun, "form", "")
} else {
var defaultValue bool = false
r.dryRun = &defaultValue
}
// 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
}

View file

@ -51,6 +51,8 @@ type APIClient struct {
AuditAPI *AuditAPIService AuditAPI *AuditAPIService
BankTemplatesAPI *BankTemplatesAPIService
BanksAPI *BanksAPIService BanksAPI *BanksAPIService
DirectivesAPI *DirectivesAPIService DirectivesAPI *DirectivesAPIService
@ -89,6 +91,7 @@ func NewAPIClient(cfg *Configuration) *APIClient {
// API Services // API Services
c.AuditAPI = (*AuditAPIService)(&c.common) c.AuditAPI = (*AuditAPIService)(&c.common)
c.BankTemplatesAPI = (*BankTemplatesAPIService)(&c.common)
c.BanksAPI = (*BanksAPIService)(&c.common) c.BanksAPI = (*BanksAPIService)(&c.common)
c.DirectivesAPI = (*DirectivesAPIService)(&c.common) c.DirectivesAPI = (*DirectivesAPIService)(&c.common)
c.DocumentsAPI = (*DocumentsAPIService)(&c.common) c.DocumentsAPI = (*DocumentsAPIService)(&c.common)

View file

@ -0,0 +1,633 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"encoding/json"
)
// checks if the BankTemplateConfig type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &BankTemplateConfig{}
// BankTemplateConfig Bank configuration fields within a template manifest. Only includes configurable (per-bank) fields. Credential fields (API keys, base URLs) are intentionally excluded for security.
type BankTemplateConfig struct {
ReflectMission NullableString `json:"reflect_mission,omitempty"`
RetainMission NullableString `json:"retain_mission,omitempty"`
RetainExtractionMode NullableString `json:"retain_extraction_mode,omitempty"`
RetainCustomInstructions NullableString `json:"retain_custom_instructions,omitempty"`
RetainChunkSize NullableInt32 `json:"retain_chunk_size,omitempty"`
EnableObservations NullableBool `json:"enable_observations,omitempty"`
ObservationsMission NullableString `json:"observations_mission,omitempty"`
DispositionSkepticism NullableInt32 `json:"disposition_skepticism,omitempty"`
DispositionLiteralism NullableInt32 `json:"disposition_literalism,omitempty"`
DispositionEmpathy NullableInt32 `json:"disposition_empathy,omitempty"`
EntityLabels []string `json:"entity_labels,omitempty"`
EntitiesAllowFreeForm NullableBool `json:"entities_allow_free_form,omitempty"`
}
// NewBankTemplateConfig instantiates a new BankTemplateConfig object
// 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 NewBankTemplateConfig() *BankTemplateConfig {
this := BankTemplateConfig{}
return &this
}
// NewBankTemplateConfigWithDefaults instantiates a new BankTemplateConfig object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewBankTemplateConfigWithDefaults() *BankTemplateConfig {
this := BankTemplateConfig{}
return &this
}
// GetReflectMission returns the ReflectMission field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetReflectMission() string {
if o == nil || IsNil(o.ReflectMission.Get()) {
var ret string
return ret
}
return *o.ReflectMission.Get()
}
// GetReflectMissionOk returns a tuple with the ReflectMission 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 *BankTemplateConfig) GetReflectMissionOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.ReflectMission.Get(), o.ReflectMission.IsSet()
}
// HasReflectMission returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasReflectMission() bool {
if o != nil && o.ReflectMission.IsSet() {
return true
}
return false
}
// SetReflectMission gets a reference to the given NullableString and assigns it to the ReflectMission field.
func (o *BankTemplateConfig) SetReflectMission(v string) {
o.ReflectMission.Set(&v)
}
// SetReflectMissionNil sets the value for ReflectMission to be an explicit nil
func (o *BankTemplateConfig) SetReflectMissionNil() {
o.ReflectMission.Set(nil)
}
// UnsetReflectMission ensures that no value is present for ReflectMission, not even an explicit nil
func (o *BankTemplateConfig) UnsetReflectMission() {
o.ReflectMission.Unset()
}
// GetRetainMission returns the RetainMission field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainMission() string {
if o == nil || IsNil(o.RetainMission.Get()) {
var ret string
return ret
}
return *o.RetainMission.Get()
}
// GetRetainMissionOk returns a tuple with the RetainMission 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 *BankTemplateConfig) GetRetainMissionOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.RetainMission.Get(), o.RetainMission.IsSet()
}
// HasRetainMission returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainMission() bool {
if o != nil && o.RetainMission.IsSet() {
return true
}
return false
}
// SetRetainMission gets a reference to the given NullableString and assigns it to the RetainMission field.
func (o *BankTemplateConfig) SetRetainMission(v string) {
o.RetainMission.Set(&v)
}
// SetRetainMissionNil sets the value for RetainMission to be an explicit nil
func (o *BankTemplateConfig) SetRetainMissionNil() {
o.RetainMission.Set(nil)
}
// UnsetRetainMission ensures that no value is present for RetainMission, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainMission() {
o.RetainMission.Unset()
}
// GetRetainExtractionMode returns the RetainExtractionMode field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainExtractionMode() string {
if o == nil || IsNil(o.RetainExtractionMode.Get()) {
var ret string
return ret
}
return *o.RetainExtractionMode.Get()
}
// GetRetainExtractionModeOk returns a tuple with the RetainExtractionMode 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 *BankTemplateConfig) GetRetainExtractionModeOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.RetainExtractionMode.Get(), o.RetainExtractionMode.IsSet()
}
// HasRetainExtractionMode returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainExtractionMode() bool {
if o != nil && o.RetainExtractionMode.IsSet() {
return true
}
return false
}
// SetRetainExtractionMode gets a reference to the given NullableString and assigns it to the RetainExtractionMode field.
func (o *BankTemplateConfig) SetRetainExtractionMode(v string) {
o.RetainExtractionMode.Set(&v)
}
// SetRetainExtractionModeNil sets the value for RetainExtractionMode to be an explicit nil
func (o *BankTemplateConfig) SetRetainExtractionModeNil() {
o.RetainExtractionMode.Set(nil)
}
// UnsetRetainExtractionMode ensures that no value is present for RetainExtractionMode, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainExtractionMode() {
o.RetainExtractionMode.Unset()
}
// GetRetainCustomInstructions returns the RetainCustomInstructions field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainCustomInstructions() string {
if o == nil || IsNil(o.RetainCustomInstructions.Get()) {
var ret string
return ret
}
return *o.RetainCustomInstructions.Get()
}
// GetRetainCustomInstructionsOk returns a tuple with the RetainCustomInstructions 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 *BankTemplateConfig) GetRetainCustomInstructionsOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.RetainCustomInstructions.Get(), o.RetainCustomInstructions.IsSet()
}
// HasRetainCustomInstructions returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainCustomInstructions() bool {
if o != nil && o.RetainCustomInstructions.IsSet() {
return true
}
return false
}
// SetRetainCustomInstructions gets a reference to the given NullableString and assigns it to the RetainCustomInstructions field.
func (o *BankTemplateConfig) SetRetainCustomInstructions(v string) {
o.RetainCustomInstructions.Set(&v)
}
// SetRetainCustomInstructionsNil sets the value for RetainCustomInstructions to be an explicit nil
func (o *BankTemplateConfig) SetRetainCustomInstructionsNil() {
o.RetainCustomInstructions.Set(nil)
}
// UnsetRetainCustomInstructions ensures that no value is present for RetainCustomInstructions, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainCustomInstructions() {
o.RetainCustomInstructions.Unset()
}
// GetRetainChunkSize returns the RetainChunkSize field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetRetainChunkSize() int32 {
if o == nil || IsNil(o.RetainChunkSize.Get()) {
var ret int32
return ret
}
return *o.RetainChunkSize.Get()
}
// GetRetainChunkSizeOk returns a tuple with the RetainChunkSize 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 *BankTemplateConfig) GetRetainChunkSizeOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.RetainChunkSize.Get(), o.RetainChunkSize.IsSet()
}
// HasRetainChunkSize returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasRetainChunkSize() bool {
if o != nil && o.RetainChunkSize.IsSet() {
return true
}
return false
}
// SetRetainChunkSize gets a reference to the given NullableInt32 and assigns it to the RetainChunkSize field.
func (o *BankTemplateConfig) SetRetainChunkSize(v int32) {
o.RetainChunkSize.Set(&v)
}
// SetRetainChunkSizeNil sets the value for RetainChunkSize to be an explicit nil
func (o *BankTemplateConfig) SetRetainChunkSizeNil() {
o.RetainChunkSize.Set(nil)
}
// UnsetRetainChunkSize ensures that no value is present for RetainChunkSize, not even an explicit nil
func (o *BankTemplateConfig) UnsetRetainChunkSize() {
o.RetainChunkSize.Unset()
}
// GetEnableObservations returns the EnableObservations field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetEnableObservations() bool {
if o == nil || IsNil(o.EnableObservations.Get()) {
var ret bool
return ret
}
return *o.EnableObservations.Get()
}
// GetEnableObservationsOk returns a tuple with the EnableObservations 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 *BankTemplateConfig) GetEnableObservationsOk() (*bool, bool) {
if o == nil {
return nil, false
}
return o.EnableObservations.Get(), o.EnableObservations.IsSet()
}
// HasEnableObservations returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasEnableObservations() bool {
if o != nil && o.EnableObservations.IsSet() {
return true
}
return false
}
// SetEnableObservations gets a reference to the given NullableBool and assigns it to the EnableObservations field.
func (o *BankTemplateConfig) SetEnableObservations(v bool) {
o.EnableObservations.Set(&v)
}
// SetEnableObservationsNil sets the value for EnableObservations to be an explicit nil
func (o *BankTemplateConfig) SetEnableObservationsNil() {
o.EnableObservations.Set(nil)
}
// UnsetEnableObservations ensures that no value is present for EnableObservations, not even an explicit nil
func (o *BankTemplateConfig) UnsetEnableObservations() {
o.EnableObservations.Unset()
}
// GetObservationsMission returns the ObservationsMission field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetObservationsMission() string {
if o == nil || IsNil(o.ObservationsMission.Get()) {
var ret string
return ret
}
return *o.ObservationsMission.Get()
}
// GetObservationsMissionOk returns a tuple with the ObservationsMission 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 *BankTemplateConfig) GetObservationsMissionOk() (*string, bool) {
if o == nil {
return nil, false
}
return o.ObservationsMission.Get(), o.ObservationsMission.IsSet()
}
// HasObservationsMission returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasObservationsMission() bool {
if o != nil && o.ObservationsMission.IsSet() {
return true
}
return false
}
// SetObservationsMission gets a reference to the given NullableString and assigns it to the ObservationsMission field.
func (o *BankTemplateConfig) SetObservationsMission(v string) {
o.ObservationsMission.Set(&v)
}
// SetObservationsMissionNil sets the value for ObservationsMission to be an explicit nil
func (o *BankTemplateConfig) SetObservationsMissionNil() {
o.ObservationsMission.Set(nil)
}
// UnsetObservationsMission ensures that no value is present for ObservationsMission, not even an explicit nil
func (o *BankTemplateConfig) UnsetObservationsMission() {
o.ObservationsMission.Unset()
}
// GetDispositionSkepticism returns the DispositionSkepticism field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetDispositionSkepticism() int32 {
if o == nil || IsNil(o.DispositionSkepticism.Get()) {
var ret int32
return ret
}
return *o.DispositionSkepticism.Get()
}
// GetDispositionSkepticismOk returns a tuple with the DispositionSkepticism 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 *BankTemplateConfig) GetDispositionSkepticismOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.DispositionSkepticism.Get(), o.DispositionSkepticism.IsSet()
}
// HasDispositionSkepticism returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasDispositionSkepticism() bool {
if o != nil && o.DispositionSkepticism.IsSet() {
return true
}
return false
}
// SetDispositionSkepticism gets a reference to the given NullableInt32 and assigns it to the DispositionSkepticism field.
func (o *BankTemplateConfig) SetDispositionSkepticism(v int32) {
o.DispositionSkepticism.Set(&v)
}
// SetDispositionSkepticismNil sets the value for DispositionSkepticism to be an explicit nil
func (o *BankTemplateConfig) SetDispositionSkepticismNil() {
o.DispositionSkepticism.Set(nil)
}
// UnsetDispositionSkepticism ensures that no value is present for DispositionSkepticism, not even an explicit nil
func (o *BankTemplateConfig) UnsetDispositionSkepticism() {
o.DispositionSkepticism.Unset()
}
// GetDispositionLiteralism returns the DispositionLiteralism field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetDispositionLiteralism() int32 {
if o == nil || IsNil(o.DispositionLiteralism.Get()) {
var ret int32
return ret
}
return *o.DispositionLiteralism.Get()
}
// GetDispositionLiteralismOk returns a tuple with the DispositionLiteralism 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 *BankTemplateConfig) GetDispositionLiteralismOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.DispositionLiteralism.Get(), o.DispositionLiteralism.IsSet()
}
// HasDispositionLiteralism returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasDispositionLiteralism() bool {
if o != nil && o.DispositionLiteralism.IsSet() {
return true
}
return false
}
// SetDispositionLiteralism gets a reference to the given NullableInt32 and assigns it to the DispositionLiteralism field.
func (o *BankTemplateConfig) SetDispositionLiteralism(v int32) {
o.DispositionLiteralism.Set(&v)
}
// SetDispositionLiteralismNil sets the value for DispositionLiteralism to be an explicit nil
func (o *BankTemplateConfig) SetDispositionLiteralismNil() {
o.DispositionLiteralism.Set(nil)
}
// UnsetDispositionLiteralism ensures that no value is present for DispositionLiteralism, not even an explicit nil
func (o *BankTemplateConfig) UnsetDispositionLiteralism() {
o.DispositionLiteralism.Unset()
}
// GetDispositionEmpathy returns the DispositionEmpathy field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetDispositionEmpathy() int32 {
if o == nil || IsNil(o.DispositionEmpathy.Get()) {
var ret int32
return ret
}
return *o.DispositionEmpathy.Get()
}
// GetDispositionEmpathyOk returns a tuple with the DispositionEmpathy 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 *BankTemplateConfig) GetDispositionEmpathyOk() (*int32, bool) {
if o == nil {
return nil, false
}
return o.DispositionEmpathy.Get(), o.DispositionEmpathy.IsSet()
}
// HasDispositionEmpathy returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasDispositionEmpathy() bool {
if o != nil && o.DispositionEmpathy.IsSet() {
return true
}
return false
}
// SetDispositionEmpathy gets a reference to the given NullableInt32 and assigns it to the DispositionEmpathy field.
func (o *BankTemplateConfig) SetDispositionEmpathy(v int32) {
o.DispositionEmpathy.Set(&v)
}
// SetDispositionEmpathyNil sets the value for DispositionEmpathy to be an explicit nil
func (o *BankTemplateConfig) SetDispositionEmpathyNil() {
o.DispositionEmpathy.Set(nil)
}
// UnsetDispositionEmpathy ensures that no value is present for DispositionEmpathy, not even an explicit nil
func (o *BankTemplateConfig) UnsetDispositionEmpathy() {
o.DispositionEmpathy.Unset()
}
// GetEntityLabels returns the EntityLabels field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetEntityLabels() []string {
if o == nil {
var ret []string
return ret
}
return o.EntityLabels
}
// GetEntityLabelsOk returns a tuple with the EntityLabels 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 *BankTemplateConfig) GetEntityLabelsOk() ([]string, bool) {
if o == nil || IsNil(o.EntityLabels) {
return nil, false
}
return o.EntityLabels, true
}
// HasEntityLabels returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasEntityLabels() bool {
if o != nil && !IsNil(o.EntityLabels) {
return true
}
return false
}
// SetEntityLabels gets a reference to the given []string and assigns it to the EntityLabels field.
func (o *BankTemplateConfig) SetEntityLabels(v []string) {
o.EntityLabels = v
}
// GetEntitiesAllowFreeForm returns the EntitiesAllowFreeForm field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateConfig) GetEntitiesAllowFreeForm() bool {
if o == nil || IsNil(o.EntitiesAllowFreeForm.Get()) {
var ret bool
return ret
}
return *o.EntitiesAllowFreeForm.Get()
}
// GetEntitiesAllowFreeFormOk returns a tuple with the EntitiesAllowFreeForm 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 *BankTemplateConfig) GetEntitiesAllowFreeFormOk() (*bool, bool) {
if o == nil {
return nil, false
}
return o.EntitiesAllowFreeForm.Get(), o.EntitiesAllowFreeForm.IsSet()
}
// HasEntitiesAllowFreeForm returns a boolean if a field has been set.
func (o *BankTemplateConfig) HasEntitiesAllowFreeForm() bool {
if o != nil && o.EntitiesAllowFreeForm.IsSet() {
return true
}
return false
}
// SetEntitiesAllowFreeForm gets a reference to the given NullableBool and assigns it to the EntitiesAllowFreeForm field.
func (o *BankTemplateConfig) SetEntitiesAllowFreeForm(v bool) {
o.EntitiesAllowFreeForm.Set(&v)
}
// SetEntitiesAllowFreeFormNil sets the value for EntitiesAllowFreeForm to be an explicit nil
func (o *BankTemplateConfig) SetEntitiesAllowFreeFormNil() {
o.EntitiesAllowFreeForm.Set(nil)
}
// UnsetEntitiesAllowFreeForm ensures that no value is present for EntitiesAllowFreeForm, not even an explicit nil
func (o *BankTemplateConfig) UnsetEntitiesAllowFreeForm() {
o.EntitiesAllowFreeForm.Unset()
}
func (o BankTemplateConfig) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o BankTemplateConfig) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
if o.ReflectMission.IsSet() {
toSerialize["reflect_mission"] = o.ReflectMission.Get()
}
if o.RetainMission.IsSet() {
toSerialize["retain_mission"] = o.RetainMission.Get()
}
if o.RetainExtractionMode.IsSet() {
toSerialize["retain_extraction_mode"] = o.RetainExtractionMode.Get()
}
if o.RetainCustomInstructions.IsSet() {
toSerialize["retain_custom_instructions"] = o.RetainCustomInstructions.Get()
}
if o.RetainChunkSize.IsSet() {
toSerialize["retain_chunk_size"] = o.RetainChunkSize.Get()
}
if o.EnableObservations.IsSet() {
toSerialize["enable_observations"] = o.EnableObservations.Get()
}
if o.ObservationsMission.IsSet() {
toSerialize["observations_mission"] = o.ObservationsMission.Get()
}
if o.DispositionSkepticism.IsSet() {
toSerialize["disposition_skepticism"] = o.DispositionSkepticism.Get()
}
if o.DispositionLiteralism.IsSet() {
toSerialize["disposition_literalism"] = o.DispositionLiteralism.Get()
}
if o.DispositionEmpathy.IsSet() {
toSerialize["disposition_empathy"] = o.DispositionEmpathy.Get()
}
if o.EntityLabels != nil {
toSerialize["entity_labels"] = o.EntityLabels
}
if o.EntitiesAllowFreeForm.IsSet() {
toSerialize["entities_allow_free_form"] = o.EntitiesAllowFreeForm.Get()
}
return toSerialize, nil
}
type NullableBankTemplateConfig struct {
value *BankTemplateConfig
isSet bool
}
func (v NullableBankTemplateConfig) Get() *BankTemplateConfig {
return v.value
}
func (v *NullableBankTemplateConfig) Set(val *BankTemplateConfig) {
v.value = val
v.isSet = true
}
func (v NullableBankTemplateConfig) IsSet() bool {
return v.isSet
}
func (v *NullableBankTemplateConfig) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableBankTemplateConfig(val *BankTemplateConfig) *NullableBankTemplateConfig {
return &NullableBankTemplateConfig{value: val, isSet: true}
}
func (v NullableBankTemplateConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableBankTemplateConfig) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View file

@ -0,0 +1,307 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"encoding/json"
"bytes"
"fmt"
)
// checks if the BankTemplateDirective type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &BankTemplateDirective{}
// BankTemplateDirective A directive definition within a bank template manifest. Directives are matched by name on re-import: existing directives with the same name are updated, new ones are created.
type BankTemplateDirective struct {
// Human-readable name for the directive (used as match key on re-import)
Name string `json:"name"`
// The directive text to inject into prompts
Content string `json:"content"`
// Higher priority directives are injected first
Priority *int32 `json:"priority,omitempty"`
// Whether this directive is active
IsActive *bool `json:"is_active,omitempty"`
// Tags for filtering
Tags []string `json:"tags,omitempty"`
}
type _BankTemplateDirective BankTemplateDirective
// NewBankTemplateDirective instantiates a new BankTemplateDirective object
// 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 NewBankTemplateDirective(name string, content string) *BankTemplateDirective {
this := BankTemplateDirective{}
this.Name = name
this.Content = content
var priority int32 = 0
this.Priority = &priority
var isActive bool = true
this.IsActive = &isActive
return &this
}
// NewBankTemplateDirectiveWithDefaults instantiates a new BankTemplateDirective object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewBankTemplateDirectiveWithDefaults() *BankTemplateDirective {
this := BankTemplateDirective{}
var priority int32 = 0
this.Priority = &priority
var isActive bool = true
this.IsActive = &isActive
return &this
}
// GetName returns the Name field value
func (o *BankTemplateDirective) GetName() string {
if o == nil {
var ret string
return ret
}
return o.Name
}
// GetNameOk returns a tuple with the Name field value
// and a boolean to check if the value has been set.
func (o *BankTemplateDirective) GetNameOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Name, true
}
// SetName sets field value
func (o *BankTemplateDirective) SetName(v string) {
o.Name = v
}
// GetContent returns the Content field value
func (o *BankTemplateDirective) GetContent() string {
if o == nil {
var ret string
return ret
}
return o.Content
}
// GetContentOk returns a tuple with the Content field value
// and a boolean to check if the value has been set.
func (o *BankTemplateDirective) GetContentOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Content, true
}
// SetContent sets field value
func (o *BankTemplateDirective) SetContent(v string) {
o.Content = v
}
// GetPriority returns the Priority field value if set, zero value otherwise.
func (o *BankTemplateDirective) GetPriority() int32 {
if o == nil || IsNil(o.Priority) {
var ret int32
return ret
}
return *o.Priority
}
// GetPriorityOk returns a tuple with the Priority field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateDirective) GetPriorityOk() (*int32, bool) {
if o == nil || IsNil(o.Priority) {
return nil, false
}
return o.Priority, true
}
// HasPriority returns a boolean if a field has been set.
func (o *BankTemplateDirective) HasPriority() bool {
if o != nil && !IsNil(o.Priority) {
return true
}
return false
}
// SetPriority gets a reference to the given int32 and assigns it to the Priority field.
func (o *BankTemplateDirective) SetPriority(v int32) {
o.Priority = &v
}
// GetIsActive returns the IsActive field value if set, zero value otherwise.
func (o *BankTemplateDirective) GetIsActive() bool {
if o == nil || IsNil(o.IsActive) {
var ret bool
return ret
}
return *o.IsActive
}
// GetIsActiveOk returns a tuple with the IsActive field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateDirective) GetIsActiveOk() (*bool, bool) {
if o == nil || IsNil(o.IsActive) {
return nil, false
}
return o.IsActive, true
}
// HasIsActive returns a boolean if a field has been set.
func (o *BankTemplateDirective) HasIsActive() bool {
if o != nil && !IsNil(o.IsActive) {
return true
}
return false
}
// SetIsActive gets a reference to the given bool and assigns it to the IsActive field.
func (o *BankTemplateDirective) SetIsActive(v bool) {
o.IsActive = &v
}
// GetTags returns the Tags field value if set, zero value otherwise.
func (o *BankTemplateDirective) GetTags() []string {
if o == nil || IsNil(o.Tags) {
var ret []string
return ret
}
return o.Tags
}
// GetTagsOk returns a tuple with the Tags field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateDirective) GetTagsOk() ([]string, bool) {
if o == nil || IsNil(o.Tags) {
return nil, false
}
return o.Tags, true
}
// HasTags returns a boolean if a field has been set.
func (o *BankTemplateDirective) HasTags() bool {
if o != nil && !IsNil(o.Tags) {
return true
}
return false
}
// SetTags gets a reference to the given []string and assigns it to the Tags field.
func (o *BankTemplateDirective) SetTags(v []string) {
o.Tags = v
}
func (o BankTemplateDirective) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o BankTemplateDirective) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["name"] = o.Name
toSerialize["content"] = o.Content
if !IsNil(o.Priority) {
toSerialize["priority"] = o.Priority
}
if !IsNil(o.IsActive) {
toSerialize["is_active"] = o.IsActive
}
if !IsNil(o.Tags) {
toSerialize["tags"] = o.Tags
}
return toSerialize, nil
}
func (o *BankTemplateDirective) UnmarshalJSON(data []byte) (err error) {
// This validates that all required properties are included in the JSON object
// by unmarshalling the object into a generic map with string keys and checking
// that every required field exists as a key in the generic map.
requiredProperties := []string{
"name",
"content",
}
allProperties := make(map[string]interface{})
err = json.Unmarshal(data, &allProperties)
if err != nil {
return err;
}
for _, requiredProperty := range(requiredProperties) {
if _, exists := allProperties[requiredProperty]; !exists {
return fmt.Errorf("no value given for required property %v", requiredProperty)
}
}
varBankTemplateDirective := _BankTemplateDirective{}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err = decoder.Decode(&varBankTemplateDirective)
if err != nil {
return err
}
*o = BankTemplateDirective(varBankTemplateDirective)
return err
}
type NullableBankTemplateDirective struct {
value *BankTemplateDirective
isSet bool
}
func (v NullableBankTemplateDirective) Get() *BankTemplateDirective {
return v.value
}
func (v *NullableBankTemplateDirective) Set(val *BankTemplateDirective) {
v.value = val
v.isSet = true
}
func (v NullableBankTemplateDirective) IsSet() bool {
return v.isSet
}
func (v *NullableBankTemplateDirective) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableBankTemplateDirective(val *BankTemplateDirective) *NullableBankTemplateDirective {
return &NullableBankTemplateDirective{value: val, isSet: true}
}
func (v NullableBankTemplateDirective) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableBankTemplateDirective) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View file

@ -0,0 +1,414 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"encoding/json"
"bytes"
"fmt"
)
// checks if the BankTemplateImportResponse type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &BankTemplateImportResponse{}
// BankTemplateImportResponse Response model for the bank template import endpoint.
type BankTemplateImportResponse struct {
// Bank that was imported into
BankId string `json:"bank_id"`
// Whether bank config was updated
ConfigApplied bool `json:"config_applied"`
// IDs of newly created mental models
MentalModelsCreated []string `json:"mental_models_created,omitempty"`
// IDs of updated mental models
MentalModelsUpdated []string `json:"mental_models_updated,omitempty"`
// Names of newly created directives
DirectivesCreated []string `json:"directives_created,omitempty"`
// Names of updated directives
DirectivesUpdated []string `json:"directives_updated,omitempty"`
// Operation IDs for mental model content generation (async)
OperationIds []string `json:"operation_ids,omitempty"`
// True if this was a validation-only run
DryRun *bool `json:"dry_run,omitempty"`
}
type _BankTemplateImportResponse BankTemplateImportResponse
// NewBankTemplateImportResponse instantiates a new BankTemplateImportResponse object
// 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 NewBankTemplateImportResponse(bankId string, configApplied bool) *BankTemplateImportResponse {
this := BankTemplateImportResponse{}
this.BankId = bankId
this.ConfigApplied = configApplied
var dryRun bool = false
this.DryRun = &dryRun
return &this
}
// NewBankTemplateImportResponseWithDefaults instantiates a new BankTemplateImportResponse object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewBankTemplateImportResponseWithDefaults() *BankTemplateImportResponse {
this := BankTemplateImportResponse{}
var dryRun bool = false
this.DryRun = &dryRun
return &this
}
// GetBankId returns the BankId field value
func (o *BankTemplateImportResponse) GetBankId() string {
if o == nil {
var ret string
return ret
}
return o.BankId
}
// GetBankIdOk returns a tuple with the BankId field value
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetBankIdOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.BankId, true
}
// SetBankId sets field value
func (o *BankTemplateImportResponse) SetBankId(v string) {
o.BankId = v
}
// GetConfigApplied returns the ConfigApplied field value
func (o *BankTemplateImportResponse) GetConfigApplied() bool {
if o == nil {
var ret bool
return ret
}
return o.ConfigApplied
}
// GetConfigAppliedOk returns a tuple with the ConfigApplied field value
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetConfigAppliedOk() (*bool, bool) {
if o == nil {
return nil, false
}
return &o.ConfigApplied, true
}
// SetConfigApplied sets field value
func (o *BankTemplateImportResponse) SetConfigApplied(v bool) {
o.ConfigApplied = v
}
// GetMentalModelsCreated returns the MentalModelsCreated field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetMentalModelsCreated() []string {
if o == nil || IsNil(o.MentalModelsCreated) {
var ret []string
return ret
}
return o.MentalModelsCreated
}
// GetMentalModelsCreatedOk returns a tuple with the MentalModelsCreated field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetMentalModelsCreatedOk() ([]string, bool) {
if o == nil || IsNil(o.MentalModelsCreated) {
return nil, false
}
return o.MentalModelsCreated, true
}
// HasMentalModelsCreated returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasMentalModelsCreated() bool {
if o != nil && !IsNil(o.MentalModelsCreated) {
return true
}
return false
}
// SetMentalModelsCreated gets a reference to the given []string and assigns it to the MentalModelsCreated field.
func (o *BankTemplateImportResponse) SetMentalModelsCreated(v []string) {
o.MentalModelsCreated = v
}
// GetMentalModelsUpdated returns the MentalModelsUpdated field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetMentalModelsUpdated() []string {
if o == nil || IsNil(o.MentalModelsUpdated) {
var ret []string
return ret
}
return o.MentalModelsUpdated
}
// GetMentalModelsUpdatedOk returns a tuple with the MentalModelsUpdated field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetMentalModelsUpdatedOk() ([]string, bool) {
if o == nil || IsNil(o.MentalModelsUpdated) {
return nil, false
}
return o.MentalModelsUpdated, true
}
// HasMentalModelsUpdated returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasMentalModelsUpdated() bool {
if o != nil && !IsNil(o.MentalModelsUpdated) {
return true
}
return false
}
// SetMentalModelsUpdated gets a reference to the given []string and assigns it to the MentalModelsUpdated field.
func (o *BankTemplateImportResponse) SetMentalModelsUpdated(v []string) {
o.MentalModelsUpdated = v
}
// GetDirectivesCreated returns the DirectivesCreated field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetDirectivesCreated() []string {
if o == nil || IsNil(o.DirectivesCreated) {
var ret []string
return ret
}
return o.DirectivesCreated
}
// GetDirectivesCreatedOk returns a tuple with the DirectivesCreated field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetDirectivesCreatedOk() ([]string, bool) {
if o == nil || IsNil(o.DirectivesCreated) {
return nil, false
}
return o.DirectivesCreated, true
}
// HasDirectivesCreated returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasDirectivesCreated() bool {
if o != nil && !IsNil(o.DirectivesCreated) {
return true
}
return false
}
// SetDirectivesCreated gets a reference to the given []string and assigns it to the DirectivesCreated field.
func (o *BankTemplateImportResponse) SetDirectivesCreated(v []string) {
o.DirectivesCreated = v
}
// GetDirectivesUpdated returns the DirectivesUpdated field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetDirectivesUpdated() []string {
if o == nil || IsNil(o.DirectivesUpdated) {
var ret []string
return ret
}
return o.DirectivesUpdated
}
// GetDirectivesUpdatedOk returns a tuple with the DirectivesUpdated field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetDirectivesUpdatedOk() ([]string, bool) {
if o == nil || IsNil(o.DirectivesUpdated) {
return nil, false
}
return o.DirectivesUpdated, true
}
// HasDirectivesUpdated returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasDirectivesUpdated() bool {
if o != nil && !IsNil(o.DirectivesUpdated) {
return true
}
return false
}
// SetDirectivesUpdated gets a reference to the given []string and assigns it to the DirectivesUpdated field.
func (o *BankTemplateImportResponse) SetDirectivesUpdated(v []string) {
o.DirectivesUpdated = v
}
// GetOperationIds returns the OperationIds field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetOperationIds() []string {
if o == nil || IsNil(o.OperationIds) {
var ret []string
return ret
}
return o.OperationIds
}
// GetOperationIdsOk returns a tuple with the OperationIds field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetOperationIdsOk() ([]string, bool) {
if o == nil || IsNil(o.OperationIds) {
return nil, false
}
return o.OperationIds, true
}
// HasOperationIds returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasOperationIds() bool {
if o != nil && !IsNil(o.OperationIds) {
return true
}
return false
}
// SetOperationIds gets a reference to the given []string and assigns it to the OperationIds field.
func (o *BankTemplateImportResponse) SetOperationIds(v []string) {
o.OperationIds = v
}
// GetDryRun returns the DryRun field value if set, zero value otherwise.
func (o *BankTemplateImportResponse) GetDryRun() bool {
if o == nil || IsNil(o.DryRun) {
var ret bool
return ret
}
return *o.DryRun
}
// GetDryRunOk returns a tuple with the DryRun field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateImportResponse) GetDryRunOk() (*bool, bool) {
if o == nil || IsNil(o.DryRun) {
return nil, false
}
return o.DryRun, true
}
// HasDryRun returns a boolean if a field has been set.
func (o *BankTemplateImportResponse) HasDryRun() bool {
if o != nil && !IsNil(o.DryRun) {
return true
}
return false
}
// SetDryRun gets a reference to the given bool and assigns it to the DryRun field.
func (o *BankTemplateImportResponse) SetDryRun(v bool) {
o.DryRun = &v
}
func (o BankTemplateImportResponse) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o BankTemplateImportResponse) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["bank_id"] = o.BankId
toSerialize["config_applied"] = o.ConfigApplied
if !IsNil(o.MentalModelsCreated) {
toSerialize["mental_models_created"] = o.MentalModelsCreated
}
if !IsNil(o.MentalModelsUpdated) {
toSerialize["mental_models_updated"] = o.MentalModelsUpdated
}
if !IsNil(o.DirectivesCreated) {
toSerialize["directives_created"] = o.DirectivesCreated
}
if !IsNil(o.DirectivesUpdated) {
toSerialize["directives_updated"] = o.DirectivesUpdated
}
if !IsNil(o.OperationIds) {
toSerialize["operation_ids"] = o.OperationIds
}
if !IsNil(o.DryRun) {
toSerialize["dry_run"] = o.DryRun
}
return toSerialize, nil
}
func (o *BankTemplateImportResponse) UnmarshalJSON(data []byte) (err error) {
// This validates that all required properties are included in the JSON object
// by unmarshalling the object into a generic map with string keys and checking
// that every required field exists as a key in the generic map.
requiredProperties := []string{
"bank_id",
"config_applied",
}
allProperties := make(map[string]interface{})
err = json.Unmarshal(data, &allProperties)
if err != nil {
return err;
}
for _, requiredProperty := range(requiredProperties) {
if _, exists := allProperties[requiredProperty]; !exists {
return fmt.Errorf("no value given for required property %v", requiredProperty)
}
}
varBankTemplateImportResponse := _BankTemplateImportResponse{}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err = decoder.Decode(&varBankTemplateImportResponse)
if err != nil {
return err
}
*o = BankTemplateImportResponse(varBankTemplateImportResponse)
return err
}
type NullableBankTemplateImportResponse struct {
value *BankTemplateImportResponse
isSet bool
}
func (v NullableBankTemplateImportResponse) Get() *BankTemplateImportResponse {
return v.value
}
func (v *NullableBankTemplateImportResponse) Set(val *BankTemplateImportResponse) {
v.value = val
v.isSet = true
}
func (v NullableBankTemplateImportResponse) IsSet() bool {
return v.isSet
}
func (v *NullableBankTemplateImportResponse) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableBankTemplateImportResponse(val *BankTemplateImportResponse) *NullableBankTemplateImportResponse {
return &NullableBankTemplateImportResponse{value: val, isSet: true}
}
func (v NullableBankTemplateImportResponse) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableBankTemplateImportResponse) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View file

@ -0,0 +1,279 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"encoding/json"
"bytes"
"fmt"
)
// checks if the BankTemplateManifest type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &BankTemplateManifest{}
// BankTemplateManifest A bank template manifest for import/export. Version field enables forward-compatible schema evolution: the API auto-upgrades older manifest versions to the current schema on import.
type BankTemplateManifest struct {
// Manifest schema version (currently '1')
Version string `json:"version"`
Bank NullableBankTemplateConfig `json:"bank,omitempty"`
MentalModels []BankTemplateMentalModel `json:"mental_models,omitempty"`
Directives []BankTemplateDirective `json:"directives,omitempty"`
}
type _BankTemplateManifest BankTemplateManifest
// NewBankTemplateManifest instantiates a new BankTemplateManifest object
// 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 NewBankTemplateManifest(version string) *BankTemplateManifest {
this := BankTemplateManifest{}
this.Version = version
return &this
}
// NewBankTemplateManifestWithDefaults instantiates a new BankTemplateManifest object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewBankTemplateManifestWithDefaults() *BankTemplateManifest {
this := BankTemplateManifest{}
return &this
}
// GetVersion returns the Version field value
func (o *BankTemplateManifest) GetVersion() string {
if o == nil {
var ret string
return ret
}
return o.Version
}
// GetVersionOk returns a tuple with the Version field value
// and a boolean to check if the value has been set.
func (o *BankTemplateManifest) GetVersionOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Version, true
}
// SetVersion sets field value
func (o *BankTemplateManifest) SetVersion(v string) {
o.Version = v
}
// GetBank returns the Bank field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateManifest) GetBank() BankTemplateConfig {
if o == nil || IsNil(o.Bank.Get()) {
var ret BankTemplateConfig
return ret
}
return *o.Bank.Get()
}
// GetBankOk returns a tuple with the Bank 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 *BankTemplateManifest) GetBankOk() (*BankTemplateConfig, bool) {
if o == nil {
return nil, false
}
return o.Bank.Get(), o.Bank.IsSet()
}
// HasBank returns a boolean if a field has been set.
func (o *BankTemplateManifest) HasBank() bool {
if o != nil && o.Bank.IsSet() {
return true
}
return false
}
// SetBank gets a reference to the given NullableBankTemplateConfig and assigns it to the Bank field.
func (o *BankTemplateManifest) SetBank(v BankTemplateConfig) {
o.Bank.Set(&v)
}
// SetBankNil sets the value for Bank to be an explicit nil
func (o *BankTemplateManifest) SetBankNil() {
o.Bank.Set(nil)
}
// UnsetBank ensures that no value is present for Bank, not even an explicit nil
func (o *BankTemplateManifest) UnsetBank() {
o.Bank.Unset()
}
// GetMentalModels returns the MentalModels field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateManifest) GetMentalModels() []BankTemplateMentalModel {
if o == nil {
var ret []BankTemplateMentalModel
return ret
}
return o.MentalModels
}
// GetMentalModelsOk returns a tuple with the MentalModels 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 *BankTemplateManifest) GetMentalModelsOk() ([]BankTemplateMentalModel, bool) {
if o == nil || IsNil(o.MentalModels) {
return nil, false
}
return o.MentalModels, true
}
// HasMentalModels returns a boolean if a field has been set.
func (o *BankTemplateManifest) HasMentalModels() bool {
if o != nil && !IsNil(o.MentalModels) {
return true
}
return false
}
// SetMentalModels gets a reference to the given []BankTemplateMentalModel and assigns it to the MentalModels field.
func (o *BankTemplateManifest) SetMentalModels(v []BankTemplateMentalModel) {
o.MentalModels = v
}
// GetDirectives returns the Directives field value if set, zero value otherwise (both if not set or set to explicit null).
func (o *BankTemplateManifest) GetDirectives() []BankTemplateDirective {
if o == nil {
var ret []BankTemplateDirective
return ret
}
return o.Directives
}
// GetDirectivesOk returns a tuple with the Directives 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 *BankTemplateManifest) GetDirectivesOk() ([]BankTemplateDirective, bool) {
if o == nil || IsNil(o.Directives) {
return nil, false
}
return o.Directives, true
}
// HasDirectives returns a boolean if a field has been set.
func (o *BankTemplateManifest) HasDirectives() bool {
if o != nil && !IsNil(o.Directives) {
return true
}
return false
}
// SetDirectives gets a reference to the given []BankTemplateDirective and assigns it to the Directives field.
func (o *BankTemplateManifest) SetDirectives(v []BankTemplateDirective) {
o.Directives = v
}
func (o BankTemplateManifest) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o BankTemplateManifest) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["version"] = o.Version
if o.Bank.IsSet() {
toSerialize["bank"] = o.Bank.Get()
}
if o.MentalModels != nil {
toSerialize["mental_models"] = o.MentalModels
}
if o.Directives != nil {
toSerialize["directives"] = o.Directives
}
return toSerialize, nil
}
func (o *BankTemplateManifest) UnmarshalJSON(data []byte) (err error) {
// This validates that all required properties are included in the JSON object
// by unmarshalling the object into a generic map with string keys and checking
// that every required field exists as a key in the generic map.
requiredProperties := []string{
"version",
}
allProperties := make(map[string]interface{})
err = json.Unmarshal(data, &allProperties)
if err != nil {
return err;
}
for _, requiredProperty := range(requiredProperties) {
if _, exists := allProperties[requiredProperty]; !exists {
return fmt.Errorf("no value given for required property %v", requiredProperty)
}
}
varBankTemplateManifest := _BankTemplateManifest{}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err = decoder.Decode(&varBankTemplateManifest)
if err != nil {
return err
}
*o = BankTemplateManifest(varBankTemplateManifest)
return err
}
type NullableBankTemplateManifest struct {
value *BankTemplateManifest
isSet bool
}
func (v NullableBankTemplateManifest) Get() *BankTemplateManifest {
return v.value
}
func (v *NullableBankTemplateManifest) Set(val *BankTemplateManifest) {
v.value = val
v.isSet = true
}
func (v NullableBankTemplateManifest) IsSet() bool {
return v.isSet
}
func (v *NullableBankTemplateManifest) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableBankTemplateManifest(val *BankTemplateManifest) *NullableBankTemplateManifest {
return &NullableBankTemplateManifest{value: val, isSet: true}
}
func (v NullableBankTemplateManifest) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableBankTemplateManifest) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View file

@ -0,0 +1,332 @@
/*
Hindsight HTTP API
HTTP API for Hindsight
API version: 0.4.22
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
package hindsight
import (
"encoding/json"
"bytes"
"fmt"
)
// checks if the BankTemplateMentalModel type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &BankTemplateMentalModel{}
// BankTemplateMentalModel A mental model definition within a bank template manifest.
type BankTemplateMentalModel struct {
// Unique ID for the mental model (alphanumeric lowercase with hyphens)
Id string `json:"id"`
// Human-readable name for the mental model
Name string `json:"name"`
// The query to run to generate content
SourceQuery string `json:"source_query"`
// Tags for scoped visibility
Tags []string `json:"tags,omitempty"`
// Maximum tokens for generated content
MaxTokens *int32 `json:"max_tokens,omitempty"`
// Trigger settings
Trigger *MentalModelTriggerOutput `json:"trigger,omitempty"`
}
type _BankTemplateMentalModel BankTemplateMentalModel
// NewBankTemplateMentalModel instantiates a new BankTemplateMentalModel object
// 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 NewBankTemplateMentalModel(id string, name string, sourceQuery string) *BankTemplateMentalModel {
this := BankTemplateMentalModel{}
this.Id = id
this.Name = name
this.SourceQuery = sourceQuery
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this
}
// NewBankTemplateMentalModelWithDefaults instantiates a new BankTemplateMentalModel object
// This constructor will only assign default values to properties that have it defined,
// but it doesn't guarantee that properties required by API are set
func NewBankTemplateMentalModelWithDefaults() *BankTemplateMentalModel {
this := BankTemplateMentalModel{}
var maxTokens int32 = 2048
this.MaxTokens = &maxTokens
return &this
}
// GetId returns the Id field value
func (o *BankTemplateMentalModel) GetId() string {
if o == nil {
var ret string
return ret
}
return o.Id
}
// GetIdOk returns a tuple with the Id field value
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetIdOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Id, true
}
// SetId sets field value
func (o *BankTemplateMentalModel) SetId(v string) {
o.Id = v
}
// GetName returns the Name field value
func (o *BankTemplateMentalModel) GetName() string {
if o == nil {
var ret string
return ret
}
return o.Name
}
// GetNameOk returns a tuple with the Name field value
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetNameOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.Name, true
}
// SetName sets field value
func (o *BankTemplateMentalModel) SetName(v string) {
o.Name = v
}
// GetSourceQuery returns the SourceQuery field value
func (o *BankTemplateMentalModel) GetSourceQuery() string {
if o == nil {
var ret string
return ret
}
return o.SourceQuery
}
// GetSourceQueryOk returns a tuple with the SourceQuery field value
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetSourceQueryOk() (*string, bool) {
if o == nil {
return nil, false
}
return &o.SourceQuery, true
}
// SetSourceQuery sets field value
func (o *BankTemplateMentalModel) SetSourceQuery(v string) {
o.SourceQuery = v
}
// GetTags returns the Tags field value if set, zero value otherwise.
func (o *BankTemplateMentalModel) GetTags() []string {
if o == nil || IsNil(o.Tags) {
var ret []string
return ret
}
return o.Tags
}
// GetTagsOk returns a tuple with the Tags field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetTagsOk() ([]string, bool) {
if o == nil || IsNil(o.Tags) {
return nil, false
}
return o.Tags, true
}
// HasTags returns a boolean if a field has been set.
func (o *BankTemplateMentalModel) HasTags() bool {
if o != nil && !IsNil(o.Tags) {
return true
}
return false
}
// SetTags gets a reference to the given []string and assigns it to the Tags field.
func (o *BankTemplateMentalModel) SetTags(v []string) {
o.Tags = v
}
// GetMaxTokens returns the MaxTokens field value if set, zero value otherwise.
func (o *BankTemplateMentalModel) GetMaxTokens() int32 {
if o == nil || IsNil(o.MaxTokens) {
var ret int32
return ret
}
return *o.MaxTokens
}
// GetMaxTokensOk returns a tuple with the MaxTokens field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetMaxTokensOk() (*int32, bool) {
if o == nil || IsNil(o.MaxTokens) {
return nil, false
}
return o.MaxTokens, true
}
// HasMaxTokens returns a boolean if a field has been set.
func (o *BankTemplateMentalModel) HasMaxTokens() bool {
if o != nil && !IsNil(o.MaxTokens) {
return true
}
return false
}
// SetMaxTokens gets a reference to the given int32 and assigns it to the MaxTokens field.
func (o *BankTemplateMentalModel) SetMaxTokens(v int32) {
o.MaxTokens = &v
}
// GetTrigger returns the Trigger field value if set, zero value otherwise.
func (o *BankTemplateMentalModel) GetTrigger() MentalModelTriggerOutput {
if o == nil || IsNil(o.Trigger) {
var ret MentalModelTriggerOutput
return ret
}
return *o.Trigger
}
// GetTriggerOk returns a tuple with the Trigger field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *BankTemplateMentalModel) GetTriggerOk() (*MentalModelTriggerOutput, bool) {
if o == nil || IsNil(o.Trigger) {
return nil, false
}
return o.Trigger, true
}
// HasTrigger returns a boolean if a field has been set.
func (o *BankTemplateMentalModel) HasTrigger() bool {
if o != nil && !IsNil(o.Trigger) {
return true
}
return false
}
// SetTrigger gets a reference to the given MentalModelTriggerOutput and assigns it to the Trigger field.
func (o *BankTemplateMentalModel) SetTrigger(v MentalModelTriggerOutput) {
o.Trigger = &v
}
func (o BankTemplateMentalModel) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
return []byte{}, err
}
return json.Marshal(toSerialize)
}
func (o BankTemplateMentalModel) ToMap() (map[string]interface{}, error) {
toSerialize := map[string]interface{}{}
toSerialize["id"] = o.Id
toSerialize["name"] = o.Name
toSerialize["source_query"] = o.SourceQuery
if !IsNil(o.Tags) {
toSerialize["tags"] = o.Tags
}
if !IsNil(o.MaxTokens) {
toSerialize["max_tokens"] = o.MaxTokens
}
if !IsNil(o.Trigger) {
toSerialize["trigger"] = o.Trigger
}
return toSerialize, nil
}
func (o *BankTemplateMentalModel) UnmarshalJSON(data []byte) (err error) {
// This validates that all required properties are included in the JSON object
// by unmarshalling the object into a generic map with string keys and checking
// that every required field exists as a key in the generic map.
requiredProperties := []string{
"id",
"name",
"source_query",
}
allProperties := make(map[string]interface{})
err = json.Unmarshal(data, &allProperties)
if err != nil {
return err;
}
for _, requiredProperty := range(requiredProperties) {
if _, exists := allProperties[requiredProperty]; !exists {
return fmt.Errorf("no value given for required property %v", requiredProperty)
}
}
varBankTemplateMentalModel := _BankTemplateMentalModel{}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
err = decoder.Decode(&varBankTemplateMentalModel)
if err != nil {
return err
}
*o = BankTemplateMentalModel(varBankTemplateMentalModel)
return err
}
type NullableBankTemplateMentalModel struct {
value *BankTemplateMentalModel
isSet bool
}
func (v NullableBankTemplateMentalModel) Get() *BankTemplateMentalModel {
return v.value
}
func (v *NullableBankTemplateMentalModel) Set(val *BankTemplateMentalModel) {
v.value = val
v.isSet = true
}
func (v NullableBankTemplateMentalModel) IsSet() bool {
return v.isSet
}
func (v *NullableBankTemplateMentalModel) Unset() {
v.value = nil
v.isSet = false
}
func NewNullableBankTemplateMentalModel(val *BankTemplateMentalModel) *NullableBankTemplateMentalModel {
return &NullableBankTemplateMentalModel{value: val, isSet: true}
}
func (v NullableBankTemplateMentalModel) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value)
}
func (v *NullableBankTemplateMentalModel) UnmarshalJSON(src []byte) error {
v.isSet = true
return json.Unmarshal(src, &v.value)
}

View file

@ -1,6 +1,7 @@
hindsight_client_api/__init__.py hindsight_client_api/__init__.py
hindsight_client_api/api/__init__.py hindsight_client_api/api/__init__.py
hindsight_client_api/api/audit_api.py hindsight_client_api/api/audit_api.py
hindsight_client_api/api/bank_templates_api.py
hindsight_client_api/api/banks_api.py hindsight_client_api/api/banks_api.py
hindsight_client_api/api/directives_api.py hindsight_client_api/api/directives_api.py
hindsight_client_api/api/documents_api.py hindsight_client_api/api/documents_api.py
@ -29,6 +30,11 @@ hindsight_client_api/models/bank_list_item.py
hindsight_client_api/models/bank_list_response.py hindsight_client_api/models/bank_list_response.py
hindsight_client_api/models/bank_profile_response.py hindsight_client_api/models/bank_profile_response.py
hindsight_client_api/models/bank_stats_response.py hindsight_client_api/models/bank_stats_response.py
hindsight_client_api/models/bank_template_config.py
hindsight_client_api/models/bank_template_directive.py
hindsight_client_api/models/bank_template_import_response.py
hindsight_client_api/models/bank_template_manifest.py
hindsight_client_api/models/bank_template_mental_model.py
hindsight_client_api/models/budget.py hindsight_client_api/models/budget.py
hindsight_client_api/models/cancel_operation_response.py hindsight_client_api/models/cancel_operation_response.py
hindsight_client_api/models/child_operation_status.py hindsight_client_api/models/child_operation_status.py

View file

@ -18,6 +18,7 @@ __version__ = "0.0.7"
# import apis into sdk package # import apis into sdk package
from hindsight_client_api.api.audit_api import AuditApi from hindsight_client_api.api.audit_api import AuditApi
from hindsight_client_api.api.bank_templates_api import BankTemplatesApi
from hindsight_client_api.api.banks_api import BanksApi from hindsight_client_api.api.banks_api import BanksApi
from hindsight_client_api.api.directives_api import DirectivesApi from hindsight_client_api.api.directives_api import DirectivesApi
from hindsight_client_api.api.documents_api import DocumentsApi from hindsight_client_api.api.documents_api import DocumentsApi
@ -54,6 +55,11 @@ from hindsight_client_api.models.bank_list_item import BankListItem
from hindsight_client_api.models.bank_list_response import BankListResponse from hindsight_client_api.models.bank_list_response import BankListResponse
from hindsight_client_api.models.bank_profile_response import BankProfileResponse from hindsight_client_api.models.bank_profile_response import BankProfileResponse
from hindsight_client_api.models.bank_stats_response import BankStatsResponse from hindsight_client_api.models.bank_stats_response import BankStatsResponse
from hindsight_client_api.models.bank_template_config import BankTemplateConfig
from hindsight_client_api.models.bank_template_directive import BankTemplateDirective
from hindsight_client_api.models.bank_template_import_response import BankTemplateImportResponse
from hindsight_client_api.models.bank_template_manifest import BankTemplateManifest
from hindsight_client_api.models.bank_template_mental_model import BankTemplateMentalModel
from hindsight_client_api.models.budget import Budget from hindsight_client_api.models.budget import Budget
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
from hindsight_client_api.models.child_operation_status import ChildOperationStatus from hindsight_client_api.models.child_operation_status import ChildOperationStatus

View file

@ -2,6 +2,7 @@
# import apis into api package # import apis into api package
from hindsight_client_api.api.audit_api import AuditApi from hindsight_client_api.api.audit_api import AuditApi
from hindsight_client_api.api.bank_templates_api import BankTemplatesApi
from hindsight_client_api.api.banks_api import BanksApi from hindsight_client_api.api.banks_api import BanksApi
from hindsight_client_api.api.directives_api import DirectivesApi from hindsight_client_api.api.directives_api import DirectivesApi
from hindsight_client_api.api.documents_api import DocumentsApi from hindsight_client_api.api.documents_api import DocumentsApi

View file

@ -0,0 +1,858 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
import warnings
from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt
from typing import Any, Dict, List, Optional, Tuple, Union
from typing_extensions import Annotated
from pydantic import Field, StrictBool, StrictStr
from typing import Any, Optional
from typing_extensions import Annotated
from hindsight_client_api.models.bank_template_import_response import BankTemplateImportResponse
from hindsight_client_api.models.bank_template_manifest import BankTemplateManifest
from hindsight_client_api.api_client import ApiClient, RequestSerialized
from hindsight_client_api.api_response import ApiResponse
from hindsight_client_api.rest import RESTResponseType
class BankTemplatesApi:
"""NOTE: This class is auto generated by OpenAPI Generator
Ref: https://openapi-generator.tech
Do not edit the class manually.
"""
def __init__(self, api_client=None) -> None:
if api_client is None:
api_client = ApiClient.get_default()
self.api_client = api_client
@validate_call
async def export_bank_template(
self,
bank_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,
) -> BankTemplateManifest:
"""Export bank template
Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.
:param bank_id: (required)
:type bank_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._export_bank_template_serialize(
bank_id=bank_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': "BankTemplateManifest",
'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 export_bank_template_with_http_info(
self,
bank_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[BankTemplateManifest]:
"""Export bank template
Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.
:param bank_id: (required)
:type bank_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._export_bank_template_serialize(
bank_id=bank_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': "BankTemplateManifest",
'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 export_bank_template_without_preload_content(
self,
bank_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:
"""Export bank template
Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.
:param bank_id: (required)
:type bank_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._export_bank_template_serialize(
bank_id=bank_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': "BankTemplateManifest",
'422': "HTTPValidationError",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _export_bank_template_serialize(
self,
bank_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
# 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='GET',
resource_path='/v1/default/banks/{bank_id}/export',
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 get_bank_template_schema(
self,
_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,
) -> object:
"""Get bank template JSON Schema
Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.
: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._get_bank_template_schema_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
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 get_bank_template_schema_with_http_info(
self,
_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[object]:
"""Get bank template JSON Schema
Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.
: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._get_bank_template_schema_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
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 get_bank_template_schema_without_preload_content(
self,
_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:
"""Get bank template JSON Schema
Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.
: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._get_bank_template_schema_serialize(
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "object",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _get_bank_template_schema_serialize(
self,
_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
# process the query parameters
# process the header parameters
# 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='GET',
resource_path='/v1/bank-template-schema',
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 import_bank_template(
self,
bank_id: StrictStr,
dry_run: Annotated[Optional[StrictBool], Field(description="Validate only, do not apply changes")] = None,
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,
) -> BankTemplateImportResponse:
"""Import bank template
Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name — existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.
:param bank_id: (required)
:type bank_id: str
:param dry_run: Validate only, do not apply changes
:type dry_run: bool
: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._import_bank_template_serialize(
bank_id=bank_id,
dry_run=dry_run,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "BankTemplateImportResponse",
'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 import_bank_template_with_http_info(
self,
bank_id: StrictStr,
dry_run: Annotated[Optional[StrictBool], Field(description="Validate only, do not apply changes")] = None,
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[BankTemplateImportResponse]:
"""Import bank template
Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name — existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.
:param bank_id: (required)
:type bank_id: str
:param dry_run: Validate only, do not apply changes
:type dry_run: bool
: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._import_bank_template_serialize(
bank_id=bank_id,
dry_run=dry_run,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "BankTemplateImportResponse",
'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 import_bank_template_without_preload_content(
self,
bank_id: StrictStr,
dry_run: Annotated[Optional[StrictBool], Field(description="Validate only, do not apply changes")] = None,
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:
"""Import bank template
Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name — existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.
:param bank_id: (required)
:type bank_id: str
:param dry_run: Validate only, do not apply changes
:type dry_run: bool
: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._import_bank_template_serialize(
bank_id=bank_id,
dry_run=dry_run,
authorization=authorization,
_request_auth=_request_auth,
_content_type=_content_type,
_headers=_headers,
_host_index=_host_index
)
_response_types_map: Dict[str, Optional[str]] = {
'200': "BankTemplateImportResponse",
'422': "HTTPValidationError",
}
response_data = await self.api_client.call_api(
*_param,
_request_timeout=_request_timeout
)
return response_data.response
def _import_bank_template_serialize(
self,
bank_id,
dry_run,
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
# process the query parameters
if dry_run is not None:
_query_params.append(('dry_run', dry_run))
# 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}/import',
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
)

View file

@ -27,6 +27,11 @@ from hindsight_client_api.models.bank_list_item import BankListItem
from hindsight_client_api.models.bank_list_response import BankListResponse from hindsight_client_api.models.bank_list_response import BankListResponse
from hindsight_client_api.models.bank_profile_response import BankProfileResponse from hindsight_client_api.models.bank_profile_response import BankProfileResponse
from hindsight_client_api.models.bank_stats_response import BankStatsResponse from hindsight_client_api.models.bank_stats_response import BankStatsResponse
from hindsight_client_api.models.bank_template_config import BankTemplateConfig
from hindsight_client_api.models.bank_template_directive import BankTemplateDirective
from hindsight_client_api.models.bank_template_import_response import BankTemplateImportResponse
from hindsight_client_api.models.bank_template_manifest import BankTemplateManifest
from hindsight_client_api.models.bank_template_mental_model import BankTemplateMentalModel
from hindsight_client_api.models.budget import Budget from hindsight_client_api.models.budget import Budget
from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse from hindsight_client_api.models.cancel_operation_response import CancelOperationResponse
from hindsight_client_api.models.child_operation_status import ChildOperationStatus from hindsight_client_api.models.child_operation_status import ChildOperationStatus

View file

@ -0,0 +1,170 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing_extensions import Annotated
from typing import Optional, Set
from typing_extensions import Self
class BankTemplateConfig(BaseModel):
"""
Bank configuration fields within a template manifest. Only includes configurable (per-bank) fields. Credential fields (API keys, base URLs) are intentionally excluded for security.
""" # noqa: E501
reflect_mission: Optional[StrictStr] = None
retain_mission: Optional[StrictStr] = None
retain_extraction_mode: Optional[StrictStr] = None
retain_custom_instructions: Optional[StrictStr] = None
retain_chunk_size: Optional[StrictInt] = None
enable_observations: Optional[StrictBool] = None
observations_mission: Optional[StrictStr] = None
disposition_skepticism: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None
disposition_literalism: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None
disposition_empathy: Optional[Annotated[int, Field(le=5, strict=True, ge=1)]] = None
entity_labels: Optional[List[StrictStr]] = None
entities_allow_free_form: Optional[StrictBool] = None
__properties: ClassVar[List[str]] = ["reflect_mission", "retain_mission", "retain_extraction_mode", "retain_custom_instructions", "retain_chunk_size", "enable_observations", "observations_mission", "disposition_skepticism", "disposition_literalism", "disposition_empathy", "entity_labels", "entities_allow_free_form"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of BankTemplateConfig from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
# set to None if reflect_mission (nullable) is None
# and model_fields_set contains the field
if self.reflect_mission is None and "reflect_mission" in self.model_fields_set:
_dict['reflect_mission'] = None
# set to None if retain_mission (nullable) is None
# and model_fields_set contains the field
if self.retain_mission is None and "retain_mission" in self.model_fields_set:
_dict['retain_mission'] = None
# set to None if retain_extraction_mode (nullable) is None
# and model_fields_set contains the field
if self.retain_extraction_mode is None and "retain_extraction_mode" in self.model_fields_set:
_dict['retain_extraction_mode'] = None
# set to None if retain_custom_instructions (nullable) is None
# and model_fields_set contains the field
if self.retain_custom_instructions is None and "retain_custom_instructions" in self.model_fields_set:
_dict['retain_custom_instructions'] = None
# set to None if retain_chunk_size (nullable) is None
# and model_fields_set contains the field
if self.retain_chunk_size is None and "retain_chunk_size" in self.model_fields_set:
_dict['retain_chunk_size'] = None
# set to None if enable_observations (nullable) is None
# and model_fields_set contains the field
if self.enable_observations is None and "enable_observations" in self.model_fields_set:
_dict['enable_observations'] = None
# set to None if observations_mission (nullable) is None
# and model_fields_set contains the field
if self.observations_mission is None and "observations_mission" in self.model_fields_set:
_dict['observations_mission'] = None
# set to None if disposition_skepticism (nullable) is None
# and model_fields_set contains the field
if self.disposition_skepticism is None and "disposition_skepticism" in self.model_fields_set:
_dict['disposition_skepticism'] = None
# set to None if disposition_literalism (nullable) is None
# and model_fields_set contains the field
if self.disposition_literalism is None and "disposition_literalism" in self.model_fields_set:
_dict['disposition_literalism'] = None
# set to None if disposition_empathy (nullable) is None
# and model_fields_set contains the field
if self.disposition_empathy is None and "disposition_empathy" in self.model_fields_set:
_dict['disposition_empathy'] = None
# set to None if entity_labels (nullable) is None
# and model_fields_set contains the field
if self.entity_labels is None and "entity_labels" in self.model_fields_set:
_dict['entity_labels'] = None
# set to None if entities_allow_free_form (nullable) is None
# and model_fields_set contains the field
if self.entities_allow_free_form is None and "entities_allow_free_form" in self.model_fields_set:
_dict['entities_allow_free_form'] = None
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of BankTemplateConfig from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"reflect_mission": obj.get("reflect_mission"),
"retain_mission": obj.get("retain_mission"),
"retain_extraction_mode": obj.get("retain_extraction_mode"),
"retain_custom_instructions": obj.get("retain_custom_instructions"),
"retain_chunk_size": obj.get("retain_chunk_size"),
"enable_observations": obj.get("enable_observations"),
"observations_mission": obj.get("observations_mission"),
"disposition_skepticism": obj.get("disposition_skepticism"),
"disposition_literalism": obj.get("disposition_literalism"),
"disposition_empathy": obj.get("disposition_empathy"),
"entity_labels": obj.get("entity_labels"),
"entities_allow_free_form": obj.get("entities_allow_free_form")
})
return _obj

View file

@ -0,0 +1,95 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictInt, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self
class BankTemplateDirective(BaseModel):
"""
A directive definition within a bank template manifest. Directives are matched by name on re-import: existing directives with the same name are updated, new ones are created.
""" # noqa: E501
name: StrictStr = Field(description="Human-readable name for the directive (used as match key on re-import)")
content: StrictStr = Field(description="The directive text to inject into prompts")
priority: Optional[StrictInt] = Field(default=0, description="Higher priority directives are injected first")
is_active: Optional[StrictBool] = Field(default=True, description="Whether this directive is active")
tags: Optional[List[StrictStr]] = Field(default=None, description="Tags for filtering")
__properties: ClassVar[List[str]] = ["name", "content", "priority", "is_active", "tags"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of BankTemplateDirective from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of BankTemplateDirective from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"name": obj.get("name"),
"content": obj.get("content"),
"priority": obj.get("priority") if obj.get("priority") is not None else 0,
"is_active": obj.get("is_active") if obj.get("is_active") is not None else True,
"tags": obj.get("tags")
})
return _obj

View file

@ -0,0 +1,101 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set
from typing_extensions import Self
class BankTemplateImportResponse(BaseModel):
"""
Response model for the bank template import endpoint.
""" # noqa: E501
bank_id: StrictStr = Field(description="Bank that was imported into")
config_applied: StrictBool = Field(description="Whether bank config was updated")
mental_models_created: Optional[List[StrictStr]] = Field(default=None, description="IDs of newly created mental models")
mental_models_updated: Optional[List[StrictStr]] = Field(default=None, description="IDs of updated mental models")
directives_created: Optional[List[StrictStr]] = Field(default=None, description="Names of newly created directives")
directives_updated: Optional[List[StrictStr]] = Field(default=None, description="Names of updated directives")
operation_ids: Optional[List[StrictStr]] = Field(default=None, description="Operation IDs for mental model content generation (async)")
dry_run: Optional[StrictBool] = Field(default=False, description="True if this was a validation-only run")
__properties: ClassVar[List[str]] = ["bank_id", "config_applied", "mental_models_created", "mental_models_updated", "directives_created", "directives_updated", "operation_ids", "dry_run"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of BankTemplateImportResponse from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of BankTemplateImportResponse from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"bank_id": obj.get("bank_id"),
"config_applied": obj.get("config_applied"),
"mental_models_created": obj.get("mental_models_created"),
"mental_models_updated": obj.get("mental_models_updated"),
"directives_created": obj.get("directives_created"),
"directives_updated": obj.get("directives_updated"),
"operation_ids": obj.get("operation_ids"),
"dry_run": obj.get("dry_run") if obj.get("dry_run") is not None else False
})
return _obj

View file

@ -0,0 +1,128 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from hindsight_client_api.models.bank_template_config import BankTemplateConfig
from hindsight_client_api.models.bank_template_directive import BankTemplateDirective
from hindsight_client_api.models.bank_template_mental_model import BankTemplateMentalModel
from typing import Optional, Set
from typing_extensions import Self
class BankTemplateManifest(BaseModel):
"""
A bank template manifest for import/export. Version field enables forward-compatible schema evolution: the API auto-upgrades older manifest versions to the current schema on import.
""" # noqa: E501
version: StrictStr = Field(description="Manifest schema version (currently '1')")
bank: Optional[BankTemplateConfig] = None
mental_models: Optional[List[BankTemplateMentalModel]] = None
directives: Optional[List[BankTemplateDirective]] = None
__properties: ClassVar[List[str]] = ["version", "bank", "mental_models", "directives"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of BankTemplateManifest from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
# override the default output from pydantic by calling `to_dict()` of bank
if self.bank:
_dict['bank'] = self.bank.to_dict()
# override the default output from pydantic by calling `to_dict()` of each item in mental_models (list)
_items = []
if self.mental_models:
for _item_mental_models in self.mental_models:
if _item_mental_models:
_items.append(_item_mental_models.to_dict())
_dict['mental_models'] = _items
# override the default output from pydantic by calling `to_dict()` of each item in directives (list)
_items = []
if self.directives:
for _item_directives in self.directives:
if _item_directives:
_items.append(_item_directives.to_dict())
_dict['directives'] = _items
# set to None if bank (nullable) is None
# and model_fields_set contains the field
if self.bank is None and "bank" in self.model_fields_set:
_dict['bank'] = None
# set to None if mental_models (nullable) is None
# and model_fields_set contains the field
if self.mental_models is None and "mental_models" in self.model_fields_set:
_dict['mental_models'] = None
# set to None if directives (nullable) is None
# and model_fields_set contains the field
if self.directives is None and "directives" in self.model_fields_set:
_dict['directives'] = None
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of BankTemplateManifest from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"version": obj.get("version"),
"bank": BankTemplateConfig.from_dict(obj["bank"]) if obj.get("bank") is not None else None,
"mental_models": [BankTemplateMentalModel.from_dict(_item) for _item in obj["mental_models"]] if obj.get("mental_models") is not None else None,
"directives": [BankTemplateDirective.from_dict(_item) for _item in obj["directives"]] if obj.get("directives") is not None else None
})
return _obj

View file

@ -0,0 +1,102 @@
# coding: utf-8
"""
Hindsight HTTP API
HTTP API for Hindsight
The version of the OpenAPI document: 0.4.22
Generated by OpenAPI Generator (https://openapi-generator.tech)
Do not edit the class manually.
""" # noqa: E501
from __future__ import annotations
import pprint
import re # noqa: F401
import json
from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing_extensions import Annotated
from hindsight_client_api.models.mental_model_trigger_output import MentalModelTriggerOutput
from typing import Optional, Set
from typing_extensions import Self
class BankTemplateMentalModel(BaseModel):
"""
A mental model definition within a bank template manifest.
""" # noqa: E501
id: StrictStr = Field(description="Unique ID for the mental model (alphanumeric lowercase with hyphens)")
name: StrictStr = Field(description="Human-readable name for the mental model")
source_query: StrictStr = Field(description="The query to run to generate content")
tags: Optional[List[StrictStr]] = Field(default=None, description="Tags for scoped visibility")
max_tokens: Optional[Annotated[int, Field(le=8192, strict=True, ge=256)]] = Field(default=2048, description="Maximum tokens for generated content")
trigger: Optional[MentalModelTriggerOutput] = Field(default=None, description="Trigger settings")
__properties: ClassVar[List[str]] = ["id", "name", "source_query", "tags", "max_tokens", "trigger"]
model_config = ConfigDict(
populate_by_name=True,
validate_assignment=True,
protected_namespaces=(),
)
def to_str(self) -> str:
"""Returns the string representation of the model using alias"""
return pprint.pformat(self.model_dump(by_alias=True))
def to_json(self) -> str:
"""Returns the JSON representation of the model using alias"""
# TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
return json.dumps(self.to_dict())
@classmethod
def from_json(cls, json_str: str) -> Optional[Self]:
"""Create an instance of BankTemplateMentalModel from a JSON string"""
return cls.from_dict(json.loads(json_str))
def to_dict(self) -> Dict[str, Any]:
"""Return the dictionary representation of the model using alias.
This has the following differences from calling pydantic's
`self.model_dump(by_alias=True)`:
* `None` is only added to the output dict for nullable fields that
were set at model initialization. Other fields with value `None`
are ignored.
"""
excluded_fields: Set[str] = set([
])
_dict = self.model_dump(
by_alias=True,
exclude=excluded_fields,
exclude_none=True,
)
# override the default output from pydantic by calling `to_dict()` of trigger
if self.trigger:
_dict['trigger'] = self.trigger.to_dict()
return _dict
@classmethod
def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"""Create an instance of BankTemplateMentalModel from a dict"""
if obj is None:
return None
if not isinstance(obj, dict):
return cls.model_validate(obj)
_obj = cls.model_validate({
"id": obj.get("id"),
"name": obj.get("name"),
"source_query": obj.get("source_query"),
"tags": obj.get("tags"),
"max_tokens": obj.get("max_tokens") if obj.get("max_tokens") is not None else 2048,
"trigger": MentalModelTriggerOutput.from_dict(obj["trigger"]) if obj.get("trigger") is not None else None
})
return _obj

View file

@ -53,6 +53,9 @@ import type {
DeleteWebhookData, DeleteWebhookData,
DeleteWebhookErrors, DeleteWebhookErrors,
DeleteWebhookResponses, DeleteWebhookResponses,
ExportBankTemplateData,
ExportBankTemplateErrors,
ExportBankTemplateResponses,
FileRetainData, FileRetainData,
FileRetainErrors, FileRetainErrors,
FileRetainResponses, FileRetainResponses,
@ -65,6 +68,8 @@ import type {
GetBankProfileData, GetBankProfileData,
GetBankProfileErrors, GetBankProfileErrors,
GetBankProfileResponses, GetBankProfileResponses,
GetBankTemplateSchemaData,
GetBankTemplateSchemaResponses,
GetChunkData, GetChunkData,
GetChunkErrors, GetChunkErrors,
GetChunkResponses, GetChunkResponses,
@ -99,6 +104,9 @@ import type {
GetVersionResponses, GetVersionResponses,
HealthEndpointHealthGetData, HealthEndpointHealthGetData,
HealthEndpointHealthGetResponses, HealthEndpointHealthGetResponses,
ImportBankTemplateData,
ImportBankTemplateErrors,
ImportBankTemplateResponses,
ListAuditLogsData, ListAuditLogsData,
ListAuditLogsErrors, ListAuditLogsErrors,
ListAuditLogsResponses, ListAuditLogsResponses,
@ -930,6 +938,48 @@ export const createOrUpdateBank = <ThrowOnError extends boolean = false>(
}, },
}); });
/**
* Import bank template
*
* Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.
*/
export const importBankTemplate = <ThrowOnError extends boolean = false>(
options: Options<ImportBankTemplateData, ThrowOnError>,
) =>
(options.client ?? client).post<
ImportBankTemplateResponses,
ImportBankTemplateErrors,
ThrowOnError
>({ url: "/v1/default/banks/{bank_id}/import", ...options });
/**
* Export bank template
*
* Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.
*/
export const exportBankTemplate = <ThrowOnError extends boolean = false>(
options: Options<ExportBankTemplateData, ThrowOnError>,
) =>
(options.client ?? client).get<
ExportBankTemplateResponses,
ExportBankTemplateErrors,
ThrowOnError
>({ url: "/v1/default/banks/{bank_id}/export", ...options });
/**
* Get bank template JSON Schema
*
* Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.
*/
export const getBankTemplateSchema = <ThrowOnError extends boolean = false>(
options?: Options<GetBankTemplateSchemaData, ThrowOnError>,
) =>
(options?.client ?? client).get<
GetBankTemplateSchemaResponses,
unknown,
ThrowOnError
>({ url: "/v1/bank-template-schema", ...options });
/** /**
* Clear all observations * Clear all observations
* *

View file

@ -385,6 +385,261 @@ export type BankStatsResponse = {
total_observations?: number; total_observations?: number;
}; };
/**
* BankTemplateConfig
*
* Bank configuration fields within a template manifest.
*
* Only includes configurable (per-bank) fields. Credential fields
* (API keys, base URLs) are intentionally excluded for security.
*/
export type BankTemplateConfig = {
/**
* Reflect Mission
*
* Mission/context for Reflect operations
*/
reflect_mission?: string | null;
/**
* Retain Mission
*
* Steers what gets extracted during retain
*/
retain_mission?: string | null;
/**
* Retain Extraction Mode
*
* Fact extraction mode: 'concise' (default), 'verbose', or 'custom'
*/
retain_extraction_mode?: string | null;
/**
* Retain Custom Instructions
*
* Custom extraction prompt (when mode='custom')
*/
retain_custom_instructions?: string | null;
/**
* Retain Chunk Size
*
* Max token size for each content chunk
*/
retain_chunk_size?: number | null;
/**
* Enable Observations
*
* Toggle observation consolidation
*/
enable_observations?: boolean | null;
/**
* Observations Mission
*
* Controls what gets synthesised
*/
observations_mission?: string | null;
/**
* Disposition Skepticism
*
* Skepticism trait (1-5)
*/
disposition_skepticism?: number | null;
/**
* Disposition Literalism
*
* Literalism trait (1-5)
*/
disposition_literalism?: number | null;
/**
* Disposition Empathy
*
* Empathy trait (1-5)
*/
disposition_empathy?: number | null;
/**
* Entity Labels
*
* Controlled vocabulary for entity labels
*/
entity_labels?: Array<string> | null;
/**
* Entities Allow Free Form
*
* Allow entities outside the label vocabulary
*/
entities_allow_free_form?: boolean | null;
};
/**
* BankTemplateDirective
*
* A directive definition within a bank template manifest.
*
* Directives are matched by name on re-import: existing directives
* with the same name are updated, new ones are created.
*/
export type BankTemplateDirective = {
/**
* Name
*
* Human-readable name for the directive (used as match key on re-import)
*/
name: string;
/**
* Content
*
* The directive text to inject into prompts
*/
content: string;
/**
* Priority
*
* Higher priority directives are injected first
*/
priority?: number;
/**
* Is Active
*
* Whether this directive is active
*/
is_active?: boolean;
/**
* Tags
*
* Tags for filtering
*/
tags?: Array<string>;
};
/**
* BankTemplateImportResponse
*
* Response model for the bank template import endpoint.
*/
export type BankTemplateImportResponse = {
/**
* Bank Id
*
* Bank that was imported into
*/
bank_id: string;
/**
* Config Applied
*
* Whether bank config was updated
*/
config_applied: boolean;
/**
* Mental Models Created
*
* IDs of newly created mental models
*/
mental_models_created?: Array<string>;
/**
* Mental Models Updated
*
* IDs of updated mental models
*/
mental_models_updated?: Array<string>;
/**
* Directives Created
*
* Names of newly created directives
*/
directives_created?: Array<string>;
/**
* Directives Updated
*
* Names of updated directives
*/
directives_updated?: Array<string>;
/**
* Operation Ids
*
* Operation IDs for mental model content generation (async)
*/
operation_ids?: Array<string>;
/**
* Dry Run
*
* True if this was a validation-only run
*/
dry_run?: boolean;
};
/**
* BankTemplateManifest
*
* A bank template manifest for import/export.
*
* Version field enables forward-compatible schema evolution: the API
* auto-upgrades older manifest versions to the current schema on import.
*/
export type BankTemplateManifest = {
/**
* Version
*
* Manifest schema version (currently '1')
*/
version: string;
/**
* Bank configuration to apply. Omit to leave config unchanged.
*/
bank?: BankTemplateConfig | null;
/**
* Mental Models
*
* Mental models to create or update (matched by id). Omit to leave unchanged.
*/
mental_models?: Array<BankTemplateMentalModel> | null;
/**
* Directives
*
* Directives to create or update (matched by name). Omit to leave unchanged.
*/
directives?: Array<BankTemplateDirective> | null;
};
/**
* BankTemplateMentalModel
*
* A mental model definition within a bank template manifest.
*/
export type BankTemplateMentalModel = {
/**
* Id
*
* Unique ID for the mental model (alphanumeric lowercase with hyphens)
*/
id: string;
/**
* Name
*
* Human-readable name for the mental model
*/
name: string;
/**
* Source Query
*
* The query to run to generate content
*/
source_query: string;
/**
* Tags
*
* Tags for scoped visibility
*/
tags?: Array<string>;
/**
* Max Tokens
*
* Maximum tokens for generated content
*/
max_tokens?: number;
/**
* Trigger settings
*/
trigger?: MentalModelTriggerOutput;
};
/** /**
* Body_file_retain * Body_file_retain
*/ */
@ -4536,6 +4791,103 @@ export type CreateOrUpdateBankResponses = {
export type CreateOrUpdateBankResponse = export type CreateOrUpdateBankResponse =
CreateOrUpdateBankResponses[keyof CreateOrUpdateBankResponses]; CreateOrUpdateBankResponses[keyof CreateOrUpdateBankResponses];
export type ImportBankTemplateData = {
body?: never;
headers?: {
/**
* Authorization
*/
authorization?: string | null;
};
path: {
/**
* Bank Id
*/
bank_id: string;
};
query?: {
/**
* Dry Run
*
* Validate only, do not apply changes
*/
dry_run?: boolean;
};
url: "/v1/default/banks/{bank_id}/import";
};
export type ImportBankTemplateErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type ImportBankTemplateError =
ImportBankTemplateErrors[keyof ImportBankTemplateErrors];
export type ImportBankTemplateResponses = {
/**
* Successful Response
*/
200: BankTemplateImportResponse;
};
export type ImportBankTemplateResponse =
ImportBankTemplateResponses[keyof ImportBankTemplateResponses];
export type ExportBankTemplateData = {
body?: never;
headers?: {
/**
* Authorization
*/
authorization?: string | null;
};
path: {
/**
* Bank Id
*/
bank_id: string;
};
query?: never;
url: "/v1/default/banks/{bank_id}/export";
};
export type ExportBankTemplateErrors = {
/**
* Validation Error
*/
422: HttpValidationError;
};
export type ExportBankTemplateError =
ExportBankTemplateErrors[keyof ExportBankTemplateErrors];
export type ExportBankTemplateResponses = {
/**
* Successful Response
*/
200: BankTemplateManifest;
};
export type ExportBankTemplateResponse =
ExportBankTemplateResponses[keyof ExportBankTemplateResponses];
export type GetBankTemplateSchemaData = {
body?: never;
path?: never;
query?: never;
url: "/v1/bank-template-schema";
};
export type GetBankTemplateSchemaResponses = {
/**
* Successful Response
*/
200: unknown;
};
export type ClearObservationsData = { export type ClearObservationsData = {
body?: never; body?: never;
headers?: { headers?: {

View file

@ -0,0 +1,26 @@
import { NextRequest, NextResponse } from "next/server";
import { DATAPLANE_URL, getDataplaneHeaders } from "@/lib/hindsight-client";
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ bankId: string }> }
) {
try {
const { bankId } = await params;
const url = `${DATAPLANE_URL}/v1/default/banks/${encodeURIComponent(bankId)}/export`;
const response = await fetch(url, {
headers: getDataplaneHeaders(),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}
return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error("Error exporting bank template:", error);
return NextResponse.json({ error: "Failed to export bank template" }, { status: 500 });
}
}

View file

@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from "next/server";
import { DATAPLANE_URL, getDataplaneHeaders } from "@/lib/hindsight-client";
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ bankId: string }> }
) {
try {
const { bankId } = await params;
const body = await request.json();
const dryRun = request.nextUrl.searchParams.get("dry_run") === "true";
// Direct fetch since the SDK doesn't have this operation yet
const url = `${DATAPLANE_URL}/v1/default/banks/${encodeURIComponent(bankId)}/import${dryRun ? "?dry_run=true" : ""}`;
const response = await fetch(url, {
method: "POST",
headers: getDataplaneHeaders({ "Content-Type": "application/json" }),
body: JSON.stringify(body),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}
return NextResponse.json(data, { status: 200 });
} catch (error) {
console.error("Error importing bank template:", error);
return NextResponse.json({ error: "Failed to import bank template" }, { status: 500 });
}
}

View file

@ -38,7 +38,7 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Brain, Trash2, Loader2, MoreVertical, Pencil, RotateCcw } from "lucide-react"; import { Brain, Download, Trash2, Loader2, MoreVertical, Pencil, RotateCcw } from "lucide-react";
type NavItem = "recall" | "reflect" | "data" | "documents" | "entities" | "profile"; type NavItem = "recall" | "reflect" | "data" | "documents" | "entities" | "profile";
type DataSubTab = "world" | "experience" | "observations" | "mental-models"; type DataSubTab = "world" | "experience" | "observations" | "mental-models";
@ -182,6 +182,23 @@ export default function BankPage() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48"> <DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem
onClick={async () => {
if (!bankId) return;
try {
const manifest = await client.exportBankTemplate(bankId);
const json = JSON.stringify(manifest, null, 2);
await navigator.clipboard.writeText(json);
toast.success("Template copied to clipboard");
} catch {
toast.error("Failed to export template");
}
}}
>
<Download className="w-4 h-4 mr-2" />
Export Template
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem
onClick={handleTriggerConsolidation} onClick={handleTriggerConsolidation}
disabled={isConsolidating || !observationsEnabled} disabled={isConsolidating || !observationsEnabled}

View file

@ -49,6 +49,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function BankSelectorInner() { function BankSelectorInner() {
@ -61,6 +62,9 @@ function BankSelectorInner() {
const [newBankId, setNewBankId] = React.useState(""); const [newBankId, setNewBankId] = React.useState("");
const [isCreating, setIsCreating] = React.useState(false); const [isCreating, setIsCreating] = React.useState(false);
const [createError, setCreateError] = React.useState<string | null>(null); const [createError, setCreateError] = React.useState<string | null>(null);
const [useTemplate, setUseTemplate] = React.useState(false);
const [templateJson, setTemplateJson] = React.useState("");
const [templateError, setTemplateError] = React.useState<string | null>(null);
// Document creation state // Document creation state
const [docDialogOpen, setDocDialogOpen] = React.useState(false); const [docDialogOpen, setDocDialogOpen] = React.useState(false);
@ -137,12 +141,39 @@ function BankSelectorInner() {
setIsCreating(true); setIsCreating(true);
setCreateError(null); setCreateError(null);
setTemplateError(null);
try { try {
// Create the bank first
await client.createBank(newBankId.trim()); await client.createBank(newBankId.trim());
// If template JSON is provided, import it
if (templateJson.trim()) {
let manifest: Record<string, unknown>;
try {
manifest = JSON.parse(templateJson.trim());
} catch {
setTemplateError("Invalid JSON. Please check the template syntax.");
setIsCreating(false);
return;
}
try {
await client.importBankTemplate(newBankId.trim(), manifest);
} catch (importError) {
setTemplateError(
importError instanceof Error ? importError.message : "Failed to import template"
);
setIsCreating(false);
return;
}
}
await loadBanks(); await loadBanks();
setCreateDialogOpen(false); setCreateDialogOpen(false);
setNewBankId(""); setNewBankId("");
setTemplateJson("");
setTemplateError(null);
// Navigate to the new bank // Navigate to the new bank
setCurrentBank(newBankId.trim()); setCurrentBank(newBankId.trim());
router.push(`/banks/${newBankId.trim()}?view=data`); router.push(`/banks/${newBankId.trim()}?view=data`);
@ -475,6 +506,7 @@ function BankSelectorInner() {
className="h-9 gap-1.5" className="h-9 gap-1.5"
onClick={() => setDocDialogOpen(true)} onClick={() => setDocDialogOpen(true)}
title="Add document to current bank" title="Add document to current bank"
data-add-document
> >
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" />
<span>Add Document</span> <span>Add Document</span>
@ -511,23 +543,68 @@ function BankSelectorInner() {
</Button> </Button>
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}> <Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[550px]">
<DialogHeader> <DialogHeader>
<DialogTitle>Create New Memory Bank</DialogTitle> <DialogTitle>Create New Memory Bank</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="py-4"> <div className="py-4 space-y-4">
<Input <Input
placeholder="Enter bank ID..." placeholder="Enter bank ID..."
value={newBankId} value={newBankId}
onChange={(e) => setNewBankId(e.target.value)} onChange={(e) => setNewBankId(e.target.value)}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && !isCreating) { if (e.key === "Enter" && !isCreating && !useTemplate) {
handleCreateBank(); handleCreateBank();
} }
}} }}
autoFocus autoFocus
/> />
{createError && <p className="text-sm text-destructive mt-2">{createError}</p>} <div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Switch
checked={useTemplate}
onCheckedChange={(checked) => {
setUseTemplate(checked);
if (!checked) {
setTemplateJson("");
setTemplateError(null);
}
}}
/>
<label className="text-sm font-medium">Import from template</label>
</div>
{useTemplate && (
<a
href="https://hindsight.vectorize.io/templates"
target="_blank"
rel="noopener noreferrer"
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
>
Browse templates &rarr;
</a>
)}
</div>
{useTemplate && (
<div>
<p className="text-xs text-muted-foreground mb-2">
Paste a template manifest JSON to pre-configure the bank with settings, mental
models, and directives.
</p>
<Textarea
placeholder='{"version": "1", "bank": {...}, "mental_models": [...]}'
value={templateJson}
onChange={(e) => {
setTemplateJson(e.target.value);
setTemplateError(null);
}}
className="font-mono text-xs min-h-[120px]"
/>
</div>
)}
{templateError && (
<p className="text-sm text-destructive whitespace-pre-wrap">{templateError}</p>
)}
{createError && <p className="text-sm text-destructive">{createError}</p>}
</div> </div>
<DialogFooter> <DialogFooter>
<Button <Button
@ -535,7 +612,10 @@ function BankSelectorInner() {
onClick={() => { onClick={() => {
setCreateDialogOpen(false); setCreateDialogOpen(false);
setNewBankId(""); setNewBankId("");
setUseTemplate(false);
setTemplateJson("");
setCreateError(null); setCreateError(null);
setTemplateError(null);
}} }}
> >
Cancel Cancel

View file

@ -40,7 +40,7 @@ import { MemoryDetailPanel } from "./memory-detail-panel";
import { MemoryDetailModal } from "./memory-detail-modal"; import { MemoryDetailModal } from "./memory-detail-modal";
import { Graph2D, convertHindsightGraphData, GraphNode } from "./graph-2d"; import { Graph2D, convertHindsightGraphData, GraphNode } from "./graph-2d";
import { Constellation } from "./constellation"; import { Constellation } from "./constellation";
import { ScatterChart } from "lucide-react"; import { ScatterChart, Plus, FileText } from "lucide-react";
type FactType = "world" | "experience" | "observation"; type FactType = "world" | "experience" | "observation";
type ViewMode = "graph" | "table" | "timeline" | "constellation"; type ViewMode = "graph" | "table" | "timeline" | "constellation";
@ -273,6 +273,26 @@ export function DataView({ factType }: DataViewProps) {
<RefreshCw className="w-8 h-8 mx-auto mb-3 text-muted-foreground animate-spin" /> <RefreshCw className="w-8 h-8 mx-auto mb-3 text-muted-foreground animate-spin" />
<p className="text-muted-foreground">Loading memories...</p> <p className="text-muted-foreground">Loading memories...</p>
</div> </div>
) : data && data.total_units === 0 ? (
<div className="text-center py-20">
<FileText className="w-10 h-10 mx-auto mb-4 text-muted-foreground/50" />
<h3 className="text-base font-medium text-foreground mb-1">No memories yet</h3>
<p className="text-sm text-muted-foreground mb-6">
Add a document to start building this memory bank.
</p>
<Button
variant="default"
size="sm"
className="gap-1.5"
onClick={() => {
const btn = document.querySelector<HTMLButtonElement>("[data-add-document]");
btn?.click();
}}
>
<Plus className="w-4 h-4" />
Add Document
</Button>
</div>
) : data ? ( ) : data ? (
<> <>
{/* Always visible filters */} {/* Always visible filters */}

View file

@ -102,6 +102,15 @@ export interface MentalModel {
reflect_response?: any; reflect_response?: any;
} }
export interface BankTemplateImportResponse {
bank_id: string;
config_applied: boolean;
mental_models_created: string[];
mental_models_updated: string[];
operation_ids: string[];
dry_run: boolean;
}
export class ControlPlaneClient { export class ControlPlaneClient {
private async fetchApi<T>(path: string, options?: RequestInit): Promise<T> { private async fetchApi<T>(path: string, options?: RequestInit): Promise<T> {
try { try {
@ -195,6 +204,24 @@ export class ControlPlaneClient {
}); });
} }
/**
* Import a bank template manifest
*/
async importBankTemplate(bankId: string, manifest: Record<string, unknown>, dryRun = false) {
const params = dryRun ? "?dry_run=true" : "";
return this.fetchApi<BankTemplateImportResponse>(`/api/banks/${bankId}/import${params}`, {
method: "POST",
body: JSON.stringify(manifest),
});
}
/**
* Export a bank as a template manifest
*/
async exportBankTemplate(bankId: string) {
return this.fetchApi<Record<string, unknown>>(`/api/banks/${bankId}/export`);
}
/** /**
* Recall memories * Recall memories
*/ */

View file

@ -393,6 +393,7 @@ class BenchmarkRunner:
self.dataset = dataset self.dataset = dataset
self.answer_generator = answer_generator self.answer_generator = answer_generator
self.answer_evaluator = answer_evaluator self.answer_evaluator = answer_evaluator
self.template_path: Optional[str] = None
self.memory = memory or MemoryEngine( self.memory = memory or MemoryEngine(
db_url=os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0"), db_url=os.getenv("HINDSIGHT_API_DATABASE_URL", "pg0"),
memory_llm_provider=os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"), memory_llm_provider=os.getenv("HINDSIGHT_API_LLM_PROVIDER", "groq"),
@ -432,6 +433,58 @@ class BenchmarkRunner:
"max_session_length": max(session_lengths) if session_lengths else 0, "max_session_length": max(session_lengths) if session_lengths else 0,
} }
async def apply_template(self, bank_id: str, manifest_path: str) -> None:
"""Apply a bank template manifest to a bank before ingestion.
Reads the manifest JSON file and applies config overrides, creates
mental models and directives same logic as the /import API endpoint.
"""
from hindsight_api.api.http import BankTemplateManifest
from hindsight_api.models import RequestContext
raw = json.loads(Path(manifest_path).read_text())
manifest = BankTemplateManifest.model_validate(raw)
request_context = RequestContext()
await self.memory.get_bank_profile(bank_id, request_context=request_context)
# Apply bank config overrides
if manifest.bank:
config_updates = manifest.bank.get_config_updates()
if config_updates:
await self.memory._config_resolver.update_bank_config(bank_id, config_updates, request_context)
# Create directives
for directive in manifest.directives or []:
await self.memory.create_directive(
bank_id=bank_id,
name=directive.name,
content=directive.content,
priority=directive.priority,
is_active=directive.is_active,
tags=directive.tags if directive.tags else None,
request_context=request_context,
)
# Create mental models (async content generation)
for mm in manifest.mental_models or []:
mental_model = await self.memory.create_mental_model(
bank_id=bank_id,
name=mm.name,
source_query=mm.source_query,
content="Generating content...",
mental_model_id=mm.id,
tags=mm.tags if mm.tags else None,
max_tokens=mm.max_tokens,
trigger=mm.trigger.model_dump() if mm.trigger else None,
request_context=request_context,
)
await self.memory.submit_async_refresh_mental_model(
bank_id=bank_id,
mental_model_id=mental_model["id"],
request_context=request_context,
)
async def ingest_conversation( async def ingest_conversation(
self, item: Dict[str, Any], agent_id: str, wait_for_consolidation: bool = False self, item: Dict[str, Any], agent_id: str, wait_for_consolidation: bool = False
) -> int: ) -> int:
@ -872,6 +925,13 @@ class BenchmarkRunner:
await self.memory.delete_bank(agent_id, request_context=RequestContext()) await self.memory.delete_bank(agent_id, request_context=RequestContext())
console.print(f" [green]✓[/green] Cleared '{agent_id}' agent data") console.print(f" [green]✓[/green] Cleared '{agent_id}' agent data")
# Apply template if configured
if self.template_path:
step += 1
console.print(f" [{step}] Applying bank template...")
await self.apply_template(agent_id, self.template_path)
console.print(" [green]✓[/green] Template applied")
# Ingest conversation # Ingest conversation
step += 1 step += 1
console.print(f" [{step}] Ingesting conversation (batch mode)...") console.print(f" [{step}] Ingesting conversation (batch mode)...")
@ -930,6 +990,7 @@ class BenchmarkRunner:
output_path: Optional[Path] = None, # Path to save results incrementally output_path: Optional[Path] = None, # Path to save results incrementally
merge_with_existing: bool = False, # Whether to merge with existing results merge_with_existing: bool = False, # Whether to merge with existing results
wait_consolidation: bool = False, # Wait for consolidation to complete before evaluating QA wait_consolidation: bool = False, # Wait for consolidation to complete before evaluating QA
template_path: Optional[str] = None, # Path to a bank template manifest to apply before ingestion
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Run the full benchmark evaluation. Run the full benchmark evaluation.
@ -975,6 +1036,9 @@ class BenchmarkRunner:
# Initialize memory system # Initialize memory system
console.print("\n[2] Initializing memory system...") console.print("\n[2] Initializing memory system...")
if template_path:
self.template_path = template_path
console.print(f" Bank template: {template_path}")
console.print(" [green]✓[/green] Memory system initialized") console.print(" [green]✓[/green] Memory system initialized")
if separate_ingestion_phase: if separate_ingestion_phase:
@ -1288,6 +1352,12 @@ class BenchmarkRunner:
await self.memory.delete_bank(agent_id, request_context=RequestContext()) await self.memory.delete_bank(agent_id, request_context=RequestContext())
console.print(" [green]✓[/green] Cleared agent data") console.print(" [green]✓[/green] Cleared agent data")
# Apply template if configured
if self.template_path:
console.print(" [yellow]Applying bank template...[/yellow]")
await self.apply_template(agent_id, self.template_path)
console.print(" [green]✓[/green] Template applied")
# Collect all sessions and send in one batch (with auto-chunking) # Collect all sessions and send in one batch (with auto-chunking)
console.print(" [yellow]Collecting sessions from all items...[/yellow]") console.print(" [yellow]Collecting sessions from all items...[/yellow]")
all_sessions = [] all_sessions = []

View file

@ -306,6 +306,7 @@ async def run_benchmark(
only_invalid: bool = False, only_invalid: bool = False,
question_index: int = None, question_index: int = None,
wait_consolidation: bool = False, wait_consolidation: bool = False,
template_path: str = None,
): ):
""" """
Run the LoComo benchmark. Run the LoComo benchmark.
@ -473,6 +474,7 @@ async def run_benchmark(
output_path=output_path, # Save results incrementally output_path=output_path, # Save results incrementally
merge_with_existing=merge_with_existing, merge_with_existing=merge_with_existing,
wait_consolidation=wait_consolidation, wait_consolidation=wait_consolidation,
template_path=template_path,
) )
# Display results (final save already happened incrementally) # Display results (final save already happened incrementally)
@ -608,6 +610,12 @@ if __name__ == "__main__":
action="store_true", action="store_true",
help="Wait for consolidation to complete after ingestion (or immediately when using --skip-ingestion) before evaluating QA.", help="Wait for consolidation to complete after ingestion (or immediately when using --skip-ingestion) before evaluating QA.",
) )
parser.add_argument(
"--template",
type=str,
default=None,
help="Path to a bank template manifest JSON to apply before ingestion (sets config, mental models, directives)",
)
args = parser.parse_args() args = parser.parse_args()
@ -628,5 +636,6 @@ if __name__ == "__main__":
only_invalid=args.only_invalid, only_invalid=args.only_invalid,
question_index=args.question_index, question_index=args.question_index,
wait_consolidation=args.wait_consolidation, wait_consolidation=args.wait_consolidation,
template_path=args.template,
) )
) )

View file

@ -0,0 +1,251 @@
---
sidebar_position: 9
---
# Bank Templates
Declarative JSON manifests for creating pre-configured memory banks with a single API call.
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeSnippet from '@site/src/components/CodeSnippet';
{/* Import raw source files */}
import templatesPy from '!!raw-loader!@site/examples/api/bank-templates.py';
import templatesMjs from '!!raw-loader!@site/examples/api/bank-templates.mjs';
import templatesSh from '!!raw-loader!@site/examples/api/bank-templates.sh';
import templatesGo from '!!raw-loader!@site/examples/api/bank-templates.go';
## Overview
A bank template is a JSON manifest that describes a bank's full setup: configuration overrides, mental models, directives, and more. Instead of making multiple API calls to configure a bank, you submit one manifest and the API provisions everything.
Templates are useful for:
- **Replication** — stamp out identically-configured banks for multiple users or agents
- **Onboarding** — new users start with a known-good configuration instead of configuring from scratch
- **Sharing** — distribute recommended setups as portable JSON files
- **Framework integrations** — ship a recommended template alongside your integration
Browse the [Bank Templates Hub](/templates) for ready-to-use templates.
## Manifest Schema
```json
{
"version": "1",
"bank": {
"reflect_mission": "...",
"retain_mission": "...",
"retain_extraction_mode": "concise | verbose | custom | chunks",
"retain_custom_instructions": "...",
"retain_chunk_size": 2048,
"disposition_skepticism": 3,
"disposition_literalism": 3,
"disposition_empathy": 3,
"enable_observations": true,
"observations_mission": "...",
"entity_labels": ["PERSON", "ORGANIZATION"],
"entities_allow_free_form": true
},
"mental_models": [
{
"id": "unique-lowercase-id",
"name": "Human-Readable Name",
"source_query": "The query that generates this mental model's content",
"tags": ["optional", "tags"],
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": false,
"fact_types": ["world", "experience", "observation"],
"exclude_mental_models": false,
"exclude_mental_model_ids": []
}
}
],
"directives": [
{
"name": "directive-name",
"content": "The directive instruction text",
"priority": 0,
"is_active": true,
"tags": ["optional", "tags"]
}
]
}
```
### Fields
| Field | Required | Description |
|-------|----------|-------------|
| `version` | Yes | Schema version. Currently `"1"`. |
| `bank` | No | Bank configuration overrides. Omit to leave config unchanged. |
| `mental_models` | No | Mental models to create or update. Omit to leave unchanged. |
| `directives` | No | Directives to create or update. Omit to leave unchanged. |
All of `bank`, `mental_models`, and `directives` are optional. Omit any section to leave that part of the bank unchanged.
### Bank Config Fields
All fields in `bank` are optional. Only the fields you include will be set as per-bank overrides — everything else inherits from the server/tenant defaults.
| Field | Type | Description |
|-------|------|-------------|
| `reflect_mission` | string | Mission/context for reflect operations |
| `retain_mission` | string | Steers what gets extracted during retain |
| `retain_extraction_mode` | string | `concise`, `verbose`, `custom`, or `chunks` |
| `retain_custom_instructions` | string | Custom extraction prompt (requires `mode=custom`) |
| `retain_chunk_size` | integer | Max token size per content chunk |
| `disposition_skepticism` | integer (1-5) | How skeptical the disposition is |
| `disposition_literalism` | integer (1-5) | How literal the disposition is |
| `disposition_empathy` | integer (1-5) | How empathetic the disposition is |
| `enable_observations` | boolean | Toggle observation consolidation |
| `observations_mission` | string | Controls what gets synthesised into observations |
| `entity_labels` | string[] | Controlled vocabulary for entity labels |
| `entities_allow_free_form` | boolean | Allow entities outside the label vocabulary |
### Mental Model Fields
| Field | Required | Description |
|-------|----------|-------------|
| `id` | Yes | Unique ID (lowercase alphanumeric with hyphens). Used to match on re-import. |
| `name` | Yes | Human-readable name |
| `source_query` | Yes | The query that generates this model's content via reflect |
| `tags` | No | Tags for scoped visibility. Default: `[]` |
| `max_tokens` | No | Max tokens for generated content (256-8192). Default: `2048` |
| `trigger` | No | Trigger settings for auto-refresh |
### Directive Fields
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Directive name. Used as the match key on re-import. |
| `content` | Yes | The directive instruction text. |
| `priority` | No | Priority value (higher = more important). Default: `0` |
| `is_active` | No | Whether the directive is active. Default: `true` |
| `tags` | No | Tags for categorization. Default: `[]` |
## Import
Import a manifest into a bank. If the bank doesn't exist, it's created automatically.
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={templatesPy} section="import-template" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={templatesMjs} section="import-template" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={templatesSh} section="import-template" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={templatesGo} section="import-template" language="go" />
</TabItem>
</Tabs>
### Behavior
- **Config**: all `bank` fields are applied as per-bank config overrides
- **Mental models**: matched by `id` — existing models are updated, new ones are created
- **Directives**: matched by `name` — existing directives are updated, new ones are created
- **Async**: mental model content is generated asynchronously. The response includes `operation_ids` to track progress.
### Dry Run
Validate a manifest without applying changes:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={templatesPy} section="import-dry-run" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={templatesMjs} section="import-dry-run" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={templatesSh} section="import-dry-run" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={templatesGo} section="import-dry-run" language="go" />
</TabItem>
</Tabs>
Returns what *would* happen (which config would be applied, which mental models would be created) without making any changes. Returns HTTP 400 with a detailed error message if the manifest is invalid.
## Export
Export a bank's current config overrides, mental models, and directives as a manifest:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={templatesPy} section="export-template" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={templatesMjs} section="export-template" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={templatesSh} section="export-template" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={templatesGo} section="export-template" language="go" />
</TabItem>
</Tabs>
The exported manifest only includes config fields that were explicitly set as per-bank overrides — not the fully resolved config (which includes server/tenant defaults). This means the exported manifest is portable: importing it into a new bank only overrides the fields that were intentionally customized.
### Round-trip
Export from one bank and import into another to replicate the setup:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={templatesPy} section="export-reimport" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={templatesMjs} section="export-reimport" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={templatesSh} section="export-reimport" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={templatesGo} section="export-reimport" language="go" />
</TabItem>
</Tabs>
## JSON Schema
The manifest format is defined by a JSON Schema. Fetch the live schema from your server:
<Tabs>
<TabItem value="python" label="Python">
<CodeSnippet code={templatesPy} section="get-schema" language="python" />
</TabItem>
<TabItem value="node" label="Node.js">
<CodeSnippet code={templatesMjs} section="get-schema" language="javascript" />
</TabItem>
<TabItem value="cli" label="CLI">
<CodeSnippet code={templatesSh} section="get-schema" language="bash" />
</TabItem>
<TabItem value="go" label="Go">
<CodeSnippet code={templatesGo} section="get-schema" language="go" />
</TabItem>
</Tabs>
The static schema is also available at [bank-template-schema.json](/bank-template-schema.json).
## Control Plane
The control plane bank creation dialog includes an optional "Import from template" toggle. Enable it to paste a manifest JSON and pre-configure the bank on creation.
You can also export any bank's template from the bank Settings page via **Actions → Export Template**, which copies the manifest JSON to your clipboard.
## Versioning
The `version` field enables forward-compatible schema evolution. The current version is `"1"`.
When future versions are released:
- Older manifests are automatically upgraded to the current schema on import
- Export always produces the latest version
- The API rejects manifests with a version newer than what the server supports (with a clear error message suggesting an upgrade)
This means old templates keep working indefinitely — no need to manually update them.

View file

@ -226,6 +226,11 @@ const config: Config = {
position: 'left', position: 'left',
className: 'navbar-item-resources', className: 'navbar-item-resources',
items: [ items: [
{
to: '/templates',
label: 'Bank Templates Hub',
customProps: { icon: 'lu-layout-template' },
},
{ {
to: '/best-practices', to: '/best-practices',
label: 'Best Practices', label: 'Best Practices',

View file

@ -0,0 +1,97 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
apiURL := os.Getenv("HINDSIGHT_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8888"
}
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:import-template]
template := map[string]interface{}{
"version": "1",
"bank": map[string]interface{}{
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"enable_observations": true,
"observations_mission": "Track recurring customer pain points.",
},
"mental_models": []map[string]interface{}{
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": map[string]interface{}{"refresh_after_consolidation": true},
},
},
"directives": []map[string]interface{}{
{
"name": "Acknowledge frustration",
"content": "Always acknowledge frustration before offering solutions.",
"priority": 10,
},
},
}
body, _ := json.Marshal(template)
resp, _ := http.Post(
apiURL+"/v1/default/banks/my-bank/import",
"application/json",
bytes.NewReader(body),
)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
// [/docs:import-template]
// [docs:import-dry-run]
resp, _ = http.Post(
apiURL+"/v1/default/banks/my-bank/import?dry_run=true",
"application/json",
bytes.NewReader(body),
)
defer resp.Body.Close()
dryRunBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(dryRunBody))
// [/docs:import-dry-run]
// [docs:export-template]
resp, _ = http.Get(apiURL + "/v1/default/banks/my-bank/export")
defer resp.Body.Close()
exported, _ := io.ReadAll(resp.Body)
fmt.Println(string(exported))
// [/docs:export-template]
// [docs:export-reimport]
// Export from source bank
resp, _ = http.Get(apiURL + "/v1/default/banks/source-bank/export")
defer resp.Body.Close()
srcExported, _ := io.ReadAll(resp.Body)
// Import into a new bank
resp, _ = http.Post(
apiURL+"/v1/default/banks/new-bank/import",
"application/json",
bytes.NewReader(srcExported),
)
defer resp.Body.Close()
// [/docs:export-reimport]
// [docs:get-schema]
resp, _ = http.Get(apiURL + "/v1/bank-template-schema")
defer resp.Body.Close()
schema, _ := io.ReadAll(resp.Body)
fmt.Println(string(schema))
// [/docs:get-schema]
}

View file

@ -0,0 +1,99 @@
#!/usr/bin/env node
/**
* Bank Templates API examples for Hindsight (Node.js)
* Run: node examples/api/bank-templates.mjs
*/
const HINDSIGHT_URL = process.env.HINDSIGHT_API_URL || 'http://localhost:8888';
// =============================================================================
// Doc Examples
// =============================================================================
// [docs:import-template]
const template = {
version: '1',
bank: {
retain_mission: 'Extract customer issues, resolutions, and sentiment.',
enable_observations: true,
observations_mission: 'Track recurring customer pain points.',
},
mental_models: [
{
id: 'sentiment-overview',
name: 'Customer Sentiment Overview',
source_query: 'What is the overall sentiment trend?',
trigger: { refresh_after_consolidation: true },
},
],
directives: [
{
name: 'Acknowledge frustration',
content: 'Always acknowledge frustration before offering solutions.',
priority: 10,
},
],
};
const importResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/import`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(template),
},
);
const result = await importResponse.json();
console.log('Config applied:', result.config_applied);
console.log('Mental models created:', result.mental_models_created);
console.log('Directives created:', result.directives_created);
// [/docs:import-template]
// [docs:import-dry-run]
const dryRunResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/import?dry_run=true`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(template),
},
);
const dryRunResult = await dryRunResponse.json();
console.log('Dry run:', dryRunResult.dry_run);
console.log('Would apply config:', dryRunResult.config_applied);
// [/docs:import-dry-run]
// [docs:export-template]
const exportResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/export`,
);
const exported = await exportResponse.json();
console.log(JSON.stringify(exported, null, 2));
// [/docs:export-template]
// [docs:export-reimport]
// Export from source bank
const srcResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/source-bank/export`,
);
const srcExported = await srcResponse.json();
// Import into a new bank
await fetch(`${HINDSIGHT_URL}/v1/default/banks/new-bank/import`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(srcExported),
});
// [/docs:export-reimport]
// [docs:get-schema]
const schemaResponse = await fetch(
`${HINDSIGHT_URL}/v1/bank-template-schema`,
);
const schema = await schemaResponse.json();
console.log(JSON.stringify(schema, null, 2));
// [/docs:get-schema]

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
Bank Templates API examples for Hindsight.
Run: python examples/api/bank-templates.py
"""
import json
import os
import requests
HINDSIGHT_URL = os.getenv("HINDSIGHT_API_URL", "http://localhost:8888")
# =============================================================================
# Doc Examples
# =============================================================================
# [docs:import-template]
template = {
"version": "1",
"bank": {
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"enable_observations": True,
"observations_mission": "Track recurring customer pain points.",
},
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {"refresh_after_consolidation": True},
}
],
"directives": [
{
"name": "Acknowledge frustration",
"content": "Always acknowledge frustration before offering solutions.",
"priority": 10,
}
],
}
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/import",
json=template,
)
result = response.json()
print(f"Config applied: {result['config_applied']}")
print(f"Mental models created: {result['mental_models_created']}")
print(f"Directives created: {result['directives_created']}")
# [/docs:import-template]
# [docs:import-dry-run]
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/import",
params={"dry_run": "true"},
json=template,
)
result = response.json()
print(f"Dry run: {result['dry_run']}")
print(f"Would apply config: {result['config_applied']}")
# [/docs:import-dry-run]
# [docs:export-template]
response = requests.get(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/export"
)
exported = response.json()
print(json.dumps(exported, indent=2))
# [/docs:export-template]
# [docs:export-reimport]
# Export from source bank
response = requests.get(
f"{HINDSIGHT_URL}/v1/default/banks/source-bank/export"
)
exported = response.json()
# Import into a new bank
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/new-bank/import",
json=exported,
)
# [/docs:export-reimport]
# [docs:get-schema]
response = requests.get(
f"{HINDSIGHT_URL}/v1/bank-template-schema"
)
schema = response.json()
print(json.dumps(schema, indent=2))
# [/docs:get-schema]

View file

@ -0,0 +1,63 @@
#!/bin/bash
# Bank Templates API examples for Hindsight CLI
# Run: bash examples/api/bank-templates.sh
set -e
HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}"
# =============================================================================
# Doc Examples
# =============================================================================
# [docs:import-template]
curl -X POST "$HINDSIGHT_URL/v1/default/banks/my-bank/import" \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"bank": {
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"enable_observations": true,
"observations_mission": "Track recurring customer pain points."
},
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": { "refresh_after_consolidation": true }
}
],
"directives": [
{
"name": "Acknowledge frustration",
"content": "Always acknowledge frustration before offering solutions.",
"priority": 10
}
]
}'
# [/docs:import-template]
# [docs:import-dry-run]
curl -X POST "$HINDSIGHT_URL/v1/default/banks/my-bank/import?dry_run=true" \
-H "Content-Type: application/json" \
-d @template.json
# [/docs:import-dry-run]
# [docs:export-template]
curl "$HINDSIGHT_URL/v1/default/banks/my-bank/export"
# [/docs:export-template]
# [docs:export-reimport]
# Export from source bank
curl "$HINDSIGHT_URL/v1/default/banks/source-bank/export" > template.json
# Import into a new bank
curl -X POST "$HINDSIGHT_URL/v1/default/banks/new-bank/import" \
-H "Content-Type: application/json" \
-d @template.json
# [/docs:export-reimport]
# [docs:get-schema]
curl "$HINDSIGHT_URL/v1/bank-template-schema"
# [/docs:get-schema]

View file

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
"start": "docusaurus start", "start": "docusaurus start",
"build": "node scripts/check-code-parity.mjs && node scripts/check-integration-seo.mjs && docusaurus build", "build": "node scripts/check-code-parity.mjs && node scripts/check-integration-seo.mjs && node scripts/check-templates.mjs && docusaurus build",
"swizzle": "docusaurus swizzle", "swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy", "deploy": "docusaurus deploy",
"clear": "docusaurus clear", "clear": "docusaurus clear",

View file

@ -0,0 +1,53 @@
#!/usr/bin/env node
/**
* Validates that every manifest in templates.json conforms to the
* bank template JSON Schema (static/bank-template-schema.json).
*
* Run: node scripts/check-templates.mjs
*/
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import Ajv from 'ajv';
const __dirname = dirname(fileURLToPath(import.meta.url));
const docsRoot = join(__dirname, '..');
const templates = JSON.parse(
readFileSync(join(docsRoot, 'src/data/templates.json'), 'utf-8'),
);
const schema = JSON.parse(
readFileSync(join(docsRoot, 'static/bank-template-schema.json'), 'utf-8'),
);
const ajv = new Ajv({ allErrors: true, strict: false });
const validate = ajv.compile(schema);
let failed = 0;
for (const template of templates.templates) {
const valid = validate(template.manifest);
if (!valid) {
failed++;
console.error(`\x1b[31m✗\x1b[0m Template "${template.id}" has invalid manifest:`);
// Filter out noisy anyOf wrapper errors, keep only leaf errors with paths
const meaningful = validate.errors.filter(
(e) => e.keyword !== 'anyOf' && e.keyword !== 'if' && e.keyword !== 'then',
);
const shown = meaningful.length > 0 ? meaningful : validate.errors;
for (const err of shown) {
const path = err.instancePath || err.schemaPath || '(root)';
console.error(` ${path}: ${err.message} ${err.params ? JSON.stringify(err.params) : ''}`);
}
} else {
console.log(`\x1b[32m✓\x1b[0m Template "${template.id}" — valid`);
}
}
if (failed > 0) {
console.error(`\n\x1b[31m${failed} template(s) failed schema validation.\x1b[0m`);
process.exit(1);
} else {
console.log(`\n\x1b[32mAll ${templates.templates.length} templates are valid.\x1b[0m`);
}

View file

@ -122,6 +122,12 @@ const sidebars: SidebarsConfig = {
label: 'Webhooks', label: 'Webhooks',
customProps: { icon: 'lu-webhook' }, customProps: { icon: 'lu-webhook' },
}, },
{
type: 'doc',
id: 'developer/api/bank-templates',
label: 'Bank Templates',
customProps: { icon: 'lu-file-json' },
},
{ {
type: 'link', type: 'link',
href: '/api-reference', href: '/api-reference',
@ -342,6 +348,12 @@ const sidebars: SidebarsConfig = {
label: 'Resources', label: 'Resources',
collapsible: false, collapsible: false,
items: [ items: [
{
type: 'link',
href: '/templates',
label: 'Bank Templates Hub',
customProps: { icon: 'lu-layout-template', iconAfter: 'lu-arrow-up-right' },
},
{ {
type: 'link', type: 'link',
href: '/best-practices', href: '/best-practices',

View file

@ -0,0 +1,130 @@
{
"templates": [
{
"id": "conversation",
"name": "Conversation",
"description": "For chat-based agents and assistants. Tracks user preferences, conversation patterns, and builds a profile over time.",
"category": "chat",
"integrations": [
"litellm",
"langgraph",
"pydantic-ai",
"ai-sdk",
"chat",
"crewai",
"ag2",
"agno",
"strands",
"llamaindex",
"local-mcp",
"skills"
],
"manifest": {
"version": "1",
"bank": {
"retain_mission": "Extract user preferences, stated facts about themselves, requests they've made, topics they care about, and any commitments or follow-ups. Ignore small talk and filler.",
"enable_observations": true,
"observations_mission": "Track stable user preferences, communication style, recurring topics, and how the user's needs evolve over time."
},
"mental_models": [
{
"id": "user-profile",
"name": "User Profile",
"source_query": "What do we know about this user? What are their preferences, background, and how do they like to interact?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "open-threads",
"name": "Open Threads",
"source_query": "What topics, tasks, or follow-ups are still open or unresolved from past conversations?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
]
}
},
{
"id": "coding-agent",
"name": "Coding Agent",
"description": "For coding assistants. Remembers project architecture, technical decisions, coding patterns, and user preferences across sessions. High literalism for precise technical recall.",
"category": "coding",
"integrations": [
"claude-code",
"codex"
],
"manifest": {
"version": "1",
"bank": {
"retain_mission": "Extract technical decisions and their rationale, architectural choices, coding patterns and conventions, project structure facts, library/tool preferences, and recurring issues. Ignore transient debugging output and boilerplate.",
"enable_observations": true,
"observations_mission": "Track stable project facts: tech stack, team conventions, architecture patterns, and how the codebase evolves over time."
},
"mental_models": [
{
"id": "project-context",
"name": "Project Context",
"source_query": "What is the project's tech stack, architecture, and key conventions? What are the main components and how do they fit together?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "developer-preferences",
"name": "Developer Preferences",
"source_query": "What are the developer's preferences for tools, libraries, coding style, and workflow? How do they like code to be written and reviewed?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
]
}
},
{
"id": "personal-assistant",
"name": "Personal Assistant",
"description": "For always-on personal assistants that manage tasks, remember preferences, and maintain context across daily life. Tracks commitments, routines, and personal context.",
"category": "assistant",
"integrations": [
"openclaw",
"hermes",
"nemoclaw",
"hindclaw"
],
"manifest": {
"version": "1",
"bank": {
"retain_mission": "Extract the user's preferences, routines, scheduled events, commitments, people they mention, and any personal context they share. Track what they ask for repeatedly and what they care about.",
"enable_observations": true,
"observations_mission": "Track the user's stable preferences, recurring routines, important people and relationships, and how their priorities shift over time."
},
"mental_models": [
{
"id": "user-profile",
"name": "User Profile",
"source_query": "What do we know about this user? What are their preferences, routines, important people, and how do they like to be helped?",
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": true
}
},
{
"id": "active-tasks",
"name": "Active Tasks & Commitments",
"source_query": "What tasks, commitments, or follow-ups is the user currently tracking? What deadlines or promises have been made?",
"max_tokens": 1024,
"trigger": {
"refresh_after_consolidation": true
}
}
]
}
}
]
}

View file

@ -0,0 +1,569 @@
/* ── Full-width hero ─────────────────────────────────────────────────────── */
.heroSection {
width: 100%;
padding: 5rem 1.5rem 4rem;
text-align: center;
background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(0, 116, 217, 0.12) 0%, transparent 70%),
var(--ifm-background-color);
border-bottom: 1px solid var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .heroSection {
background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(0, 116, 217, 0.18) 0%, transparent 70%),
var(--ifm-background-color);
border-bottom-color: rgba(255, 255, 255, 0.07);
}
.heroTitle {
font-size: 3.2rem;
font-weight: 800;
background: linear-gradient(135deg, #0074d9, #009296);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.03em;
line-height: 1.1;
margin-bottom: 0.75rem;
}
.heroSubtitle {
font-size: 1.05rem;
color: var(--ifm-color-emphasis-600);
max-width: 520px;
margin: 0 auto 2.25rem;
line-height: 1.7;
}
.heroLink {
color: var(--ifm-color-primary);
text-decoration: none;
font-weight: 500;
white-space: nowrap;
}
.heroLink:hover {
text-decoration: underline;
}
/* ── Search ──────────────────────────────────────────────────────────────── */
.searchWrapper {
position: relative;
max-width: 640px;
margin: 0 auto;
}
.searchInput {
width: 100%;
box-sizing: border-box;
padding: 1.05rem 3.5rem 1.05rem 1.5rem;
border-radius: 14px;
border: 2px solid var(--ifm-color-emphasis-300);
background: var(--ifm-background-surface-color);
color: var(--ifm-font-color-base);
font-size: 1.1rem;
font-family: inherit;
outline: none;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
.searchInput::placeholder {
color: var(--ifm-color-emphasis-400);
}
.searchInput:focus {
border-color: #0074d9;
box-shadow: 0 0 0 4px rgba(0, 116, 217, 0.15), 0 4px 24px rgba(0, 0, 0, 0.08);
}
[data-theme='dark'] .searchInput {
background: #242428;
border-color: rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
[data-theme='dark'] .searchInput:focus {
border-color: #4da6ff;
box-shadow: 0 0 0 4px rgba(77, 166, 255, 0.18), 0 4px 24px rgba(0, 0, 0, 0.4);
}
.searchClear {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
background: var(--ifm-color-emphasis-200);
border: none;
color: var(--ifm-color-emphasis-600);
cursor: pointer;
font-size: 1rem;
line-height: 1;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.1s;
}
.searchClear:hover {
background: var(--ifm-color-emphasis-300);
}
/* ── Hero stats ──────────────────────────────────────────────────────────── */
.heroStats {
display: inline-flex;
align-items: center;
gap: 0.6rem;
font-size: 0.82rem;
color: var(--ifm-color-emphasis-500);
margin-top: 1.25rem;
}
.stat strong {
color: var(--ifm-color-emphasis-800);
}
[data-theme='dark'] .stat strong {
color: var(--ifm-color-emphasis-700);
}
.statDivider {
color: var(--ifm-color-emphasis-300);
}
/* ── Page container ──────────────────────────────────────────────────────── */
.page {
max-width: 1200px;
margin: 0 auto;
padding: 2.5rem 1.5rem 5rem;
}
/* ── Toolbar (filters + count) ───────────────────────────────────────────── */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.75rem;
flex-wrap: wrap;
gap: 0.75rem;
}
.filterGroup {
display: flex;
align-items: center;
gap: 0.4rem;
flex-wrap: wrap;
}
.filterPill {
display: inline-flex;
align-items: center;
font-size: 0.82rem;
font-weight: 500;
padding: 0.3rem 0.85rem;
border-radius: 999px;
border: 1px solid var(--ifm-color-emphasis-300);
background: transparent;
color: var(--ifm-color-emphasis-700);
cursor: pointer;
transition: all 0.15s ease;
font-family: inherit;
}
.filterPill:hover {
border-color: var(--ifm-color-primary);
color: var(--ifm-color-primary);
}
.filterPillActive {
background: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
color: #fff !important;
}
[data-theme='dark'] .filterPill {
border-color: rgba(255, 255, 255, 0.15);
color: var(--ifm-color-emphasis-600);
}
.resultCount {
font-size: 0.8rem;
color: var(--ifm-color-emphasis-400);
margin: 0;
}
/* ── Grid ────────────────────────────────────────────────────────────────── */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
margin-bottom: 4rem;
}
@media (max-width: 996px) {
.grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 640px) {
.grid { grid-template-columns: 1fr; }
.heroTitle { font-size: 2.2rem; }
}
/* ── Card ────────────────────────────────────────────────────────────────── */
.card {
display: flex;
flex-direction: column;
border-radius: 12px;
border: 1px solid var(--ifm-color-emphasis-200);
background: var(--ifm-background-surface-color);
text-decoration: none !important;
color: inherit;
text-align: left;
cursor: pointer;
transition: box-shadow 0.2s ease, transform 0.2s ease;
overflow: hidden;
font-family: inherit;
padding: 0;
}
[data-theme='dark'] .card {
background: #1c1c1e;
border-color: rgba(255, 255, 255, 0.07);
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 116, 217, 0.12);
}
.cardHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.1rem 0;
}
.categoryBadge {
font-size: 0.63rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 0.15rem 0.5rem;
border-radius: 999px;
background: rgba(0, 116, 217, 0.12);
color: #0074d9;
}
[data-theme='dark'] .categoryBadge {
background: rgba(0, 116, 217, 0.2);
color: #4da6ff;
}
.integrationIcons {
display: flex;
align-items: center;
gap: 0.35rem;
}
.integrationIcon {
width: 20px;
height: 20px;
object-fit: contain;
border-radius: 4px;
opacity: 0.8;
}
.cardBody {
flex: 1;
padding: 0.85rem 1.1rem 0.75rem;
}
.cardTitle {
font-size: 1rem;
font-weight: 700;
margin: 0 0 0.4rem;
color: var(--ifm-heading-color);
letter-spacing: -0.01em;
border-bottom: none !important;
border-image: none !important;
}
.cardDescription {
font-size: 0.82rem;
color: var(--ifm-color-emphasis-600);
line-height: 1.6;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.cardFooter {
display: flex;
align-items: center;
padding: 0.65rem 1.1rem 0.9rem;
margin-top: 0.25rem;
border-top: 1px solid var(--ifm-color-emphasis-100);
}
[data-theme='dark'] .cardFooter {
border-top-color: rgba(255, 255, 255, 0.06);
}
.viewLabel {
font-size: 0.78rem;
font-weight: 500;
color: var(--ifm-color-primary);
}
/* ── Schema section ──────────────────────────────────────────────────────── */
.schemaSection {
margin-bottom: 3rem;
padding: 2rem;
border-radius: 14px;
border: 1px solid var(--ifm-color-emphasis-200);
background: var(--ifm-background-surface-color);
}
[data-theme='dark'] .schemaSection {
background: #1c1c1e;
border-color: rgba(255, 255, 255, 0.07);
}
.schemaSectionTitle {
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.schemaSectionDescription {
font-size: 0.88rem;
color: var(--ifm-color-emphasis-600);
margin-bottom: 1.5rem;
line-height: 1.6;
}
.schemaSectionDescription code {
font-size: 0.8rem;
padding: 0.1rem 0.35rem;
border-radius: 4px;
background: var(--ifm-color-emphasis-100);
}
[data-theme='dark'] .schemaSectionDescription code {
background: rgba(255, 255, 255, 0.08);
}
/* ── Modal ───────────────────────────────────────────────────────────────── */
.modalOverlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
[data-theme='dark'] .modalOverlay {
background: rgba(0, 0, 0, 0.7);
}
.modal {
background: var(--ifm-background-surface-color);
border-radius: 16px;
max-width: 700px;
width: 100%;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.2);
}
[data-theme='dark'] .modal {
background: #1c1c1e;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.modalHeader {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 1.5rem 1.5rem 0;
gap: 1rem;
}
.modalTitle {
font-size: 1.3rem;
font-weight: 700;
margin: 0 0 0.25rem;
color: var(--ifm-heading-color);
}
.modalDescription {
font-size: 0.85rem;
color: var(--ifm-color-emphasis-600);
margin: 0;
line-height: 1.5;
}
.modalClose {
background: none;
border: none;
font-size: 1.5rem;
color: var(--ifm-color-emphasis-500);
cursor: pointer;
line-height: 1;
padding: 0.25rem;
flex-shrink: 0;
}
.modalClose:hover {
color: var(--ifm-color-emphasis-800);
}
.modalBody {
flex: 1;
overflow-y: auto;
padding: 1rem 1.5rem;
}
.manifestHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.manifestLabel {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--ifm-color-emphasis-500);
}
.copyButton {
font-size: 0.78rem;
font-weight: 600;
padding: 0.3rem 0.75rem;
border-radius: 6px;
border: 1px solid var(--ifm-color-primary);
background: transparent;
color: var(--ifm-color-primary);
cursor: pointer;
font-family: inherit;
transition: all 0.15s ease;
}
.copyButton:hover {
background: var(--ifm-color-primary);
color: #fff;
}
.manifestCode {
background: var(--ifm-color-emphasis-100);
border-radius: 10px;
padding: 1rem 1.25rem;
font-size: 0.78rem;
line-height: 1.6;
overflow-x: auto;
margin: 0;
border: 1px solid var(--ifm-color-emphasis-200);
white-space: pre-wrap;
word-wrap: break-word;
}
[data-theme='dark'] .manifestCode {
background: #0d0d0f;
border-color: rgba(255, 255, 255, 0.08);
}
.modalFooter {
padding: 0.75rem 1.5rem 1.25rem;
border-top: 1px solid var(--ifm-color-emphasis-100);
}
[data-theme='dark'] .modalFooter {
border-top-color: rgba(255, 255, 255, 0.06);
}
.usageHint {
font-size: 0.78rem;
color: var(--ifm-color-emphasis-500);
margin: 0;
line-height: 1.5;
}
.usageHint code {
font-size: 0.72rem;
padding: 0.1rem 0.35rem;
border-radius: 4px;
background: var(--ifm-color-emphasis-100);
}
[data-theme='dark'] .usageHint code {
background: rgba(255, 255, 255, 0.08);
}
/* ── Empty state ─────────────────────────────────────────────────────────── */
.empty {
text-align: center;
padding: 4rem 2rem;
color: var(--ifm-color-emphasis-500);
}
.resetButton {
margin-top: 1rem;
padding: 0.4rem 1rem;
border-radius: 6px;
border: 1px solid var(--ifm-color-primary);
background: transparent;
color: var(--ifm-color-primary);
font-size: 0.85rem;
font-family: inherit;
cursor: pointer;
transition: all 0.15s ease;
}
.resetButton:hover {
background: var(--ifm-color-primary);
color: #fff;
}
/* ── Submit banner ───────────────────────────────────────────────────────── */
.submitBanner {
border-radius: 14px;
border: 1px solid var(--ifm-color-emphasis-200);
background: linear-gradient(135deg, rgba(0, 116, 217, 0.06), rgba(0, 146, 150, 0.06));
padding: 2.5rem 2rem;
text-align: center;
}
[data-theme='dark'] .submitBanner {
border-color: rgba(255, 255, 255, 0.08);
background: linear-gradient(135deg, rgba(0, 116, 217, 0.1), rgba(0, 146, 150, 0.1));
}
.submitBannerContent {
max-width: 480px;
margin: 0 auto;
}
.submitBannerTitle {
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.submitBannerText {
font-size: 0.9rem;
color: var(--ifm-color-emphasis-600);
margin-bottom: 0;
line-height: 1.6;
}

View file

@ -0,0 +1,219 @@
import React, {useMemo, useState, useCallback} from 'react';
import useBaseUrl from '@docusaurus/useBaseUrl';
import Layout from '@theme/Layout';
import templatesData from '@site/src/data/templates.json';
import integrationsData from '@site/src/data/integrations.json';
import styles from './index.module.css';
const CATEGORIES = ['all', 'chat', 'coding', 'assistant'] as const;
type Category = (typeof CATEGORIES)[number];
const CATEGORY_LABELS: Record<Category, string> = {
all: 'All',
chat: 'Chat',
coding: 'Coding',
assistant: 'Assistant',
};
// Build a lookup from integration ID to icon path and name
const INTEGRATION_MAP = Object.fromEntries(
integrationsData.integrations.map((i) => [i.id, {icon: i.icon, name: i.name}]),
);
interface Template {
id: string;
name: string;
description: string;
category: string;
integrations?: string[];
manifest: Record<string, unknown>;
}
function IntegrationIcons({ids}: {ids: string[]}) {
return (
<div className={styles.integrationIcons}>
{ids.map((id) => {
const info = INTEGRATION_MAP[id];
if (!info?.icon) return null;
const src = useBaseUrl(info.icon);
return <img key={id} src={src} alt={info.name} title={info.name} className={styles.integrationIcon} />;
})}
</div>
);
}
function TemplateCard({template, onSelect}: {template: Template; onSelect: () => void}) {
return (
<button className={styles.card} onClick={onSelect}>
<div className={styles.cardHeader}>
<span className={styles.categoryBadge}>{template.category}</span>
{template.integrations && template.integrations.length > 0 && (
<IntegrationIcons ids={template.integrations} />
)}
</div>
<div className={styles.cardBody}>
<h3 className={styles.cardTitle}>{template.name}</h3>
<p className={styles.cardDescription}>{template.description}</p>
</div>
<div className={styles.cardFooter}>
<span className={styles.viewLabel}>View manifest &rarr;</span>
</div>
</button>
);
}
function ManifestModal({
template,
onClose,
}: {
template: Template;
onClose: () => void;
}) {
const [copied, setCopied] = useState(false);
const json = JSON.stringify(template.manifest, null, 2);
const handleCopy = useCallback(async () => {
await navigator.clipboard.writeText(json);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}, [json]);
return (
<div className={styles.modalOverlay} onClick={onClose}>
<div className={styles.modal} onClick={(e) => e.stopPropagation()}>
<div className={styles.modalHeader}>
<div>
<h2 className={styles.modalTitle}>{template.name}</h2>
<p className={styles.modalDescription}>{template.description}</p>
</div>
<button className={styles.modalClose} onClick={onClose} aria-label="Close">
&times;
</button>
</div>
<div className={styles.modalBody}>
<div className={styles.manifestHeader}>
<span className={styles.manifestLabel}>Template Manifest</span>
<button className={styles.copyButton} onClick={handleCopy}>
{copied ? 'Copied!' : 'Copy JSON'}
</button>
</div>
<pre className={styles.manifestCode}>
<code>{json}</code>
</pre>
</div>
<div className={styles.modalFooter}>
<p className={styles.usageHint}>
Use this manifest with <code>POST /v1/default/banks/&#123;bank_id&#125;/import</code> or
paste it in the bank creation dialog in the control plane.
</p>
</div>
</div>
</div>
);
}
export default function TemplateGallery(): React.ReactElement {
const [search, setSearch] = useState('');
const [selectedCategory, setSelectedCategory] = useState<Category>('all');
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(null);
const templates = templatesData.templates as Template[];
const filtered = useMemo(() => {
const q = search.toLowerCase().trim();
return templates.filter((t) => {
if (selectedCategory !== 'all' && t.category !== selectedCategory) return false;
if (q && !t.name.toLowerCase().includes(q) && !t.description.toLowerCase().includes(q)) return false;
return true;
});
}, [templates, search, selectedCategory]);
return (
<Layout title="Bank Templates Hub" description="Pre-built bank templates for common use cases">
<div className={styles.heroSection}>
<h1 className={styles.heroTitle}>Bank Templates Hub</h1>
<p className={styles.heroSubtitle}>
Pre-built bank templates to get started fast. Browse, preview, and import into your Hindsight banks.
{' '}<a href="/developer/api/bank-templates" className={styles.heroLink}>Learn how templates work &rarr;</a>
</p>
<div className={styles.searchWrapper}>
<input
type="text"
className={styles.searchInput}
placeholder="Search templates..."
value={search}
onChange={(e) => setSearch(e.target.value)}
aria-label="Search templates"
autoComplete="off"
autoFocus
/>
{search && (
<button className={styles.searchClear} onClick={() => setSearch('')} aria-label="Clear search">
&times;
</button>
)}
</div>
<div className={styles.heroStats}>
<span className={styles.stat}>
<strong>{templates.length}</strong> templates
</span>
<span className={styles.statDivider}>&middot;</span>
<span className={styles.stat}>
<strong>{new Set(templates.map((t) => t.category)).size}</strong> categories
</span>
</div>
</div>
<div className={styles.page}>
<div className={styles.toolbar}>
<div className={styles.filterGroup}>
{CATEGORIES.map((c) => (
<button
key={c}
className={`${styles.filterPill} ${selectedCategory === c ? styles.filterPillActive : ''}`}
onClick={() => setSelectedCategory(c)}>
{CATEGORY_LABELS[c]}
</button>
))}
</div>
<span className={styles.resultCount}>
{filtered.length} template{filtered.length !== 1 ? 's' : ''}
</span>
</div>
{filtered.length === 0 ? (
<div className={styles.empty}>
<p>No templates match your search.</p>
<button
className={styles.resetButton}
onClick={() => {
setSearch('');
setSelectedCategory('all');
}}>
Reset filters
</button>
</div>
) : (
<div className={styles.grid}>
{filtered.map((t) => (
<TemplateCard key={t.id} template={t} onSelect={() => setSelectedTemplate(t)} />
))}
</div>
)}
<div className={styles.submitBanner}>
<div className={styles.submitBannerContent}>
<h3 className={styles.submitBannerTitle}>Have a template to share?</h3>
<p className={styles.submitBannerText}>
Add your template to the gallery by editing templates.json on GitHub.
</p>
</div>
</div>
</div>
{selectedTemplate && <ManifestModal template={selectedTemplate} onClose={() => setSelectedTemplate(null)} />}
</Layout>
);
}

View file

@ -12,6 +12,7 @@ import {
LuNetwork, LuCode, LuLayers, LuCpu, LuNetwork, LuCode, LuLayers, LuCpu,
LuArrowUpRight, LuBookOpen, LuRss, LuCloud, LuMessageCircle, LuArrowUpRight, LuBookOpen, LuRss, LuCloud, LuMessageCircle,
LuChartBar, LuChartColumn, LuStar, LuCircleHelp, LuChartBar, LuChartColumn, LuStar, LuCircleHelp,
LuLayoutTemplate, LuFileJson,
} from 'react-icons/lu'; } from 'react-icons/lu';
import {SiGo, SiPython, SiGithub, SiSlack} from 'react-icons/si'; import {SiGo, SiPython, SiGithub, SiSlack} from 'react-icons/si';
@ -54,6 +55,8 @@ const ICON_MAP: Record<string, IconType> = {
'lu-star': LuStar, 'lu-star': LuStar,
'lu-circle-help': LuCircleHelp, 'lu-circle-help': LuCircleHelp,
'lu-file-text': LuFileText, 'lu-file-text': LuFileText,
'lu-layout-template': LuLayoutTemplate,
'lu-file-json': LuFileJson,
}; };
type Props = WrapperProps<typeof LinkType>; type Props = WrapperProps<typeof LinkType>;

View file

@ -5,7 +5,7 @@ import type {WrapperProps} from '@docusaurus/types';
import type {IconType} from 'react-icons'; import type {IconType} from 'react-icons';
import { import {
LuArrowUpRight, LuCode, LuCircleHelp, LuScrollText, LuArrowUpRight, LuCode, LuCircleHelp, LuScrollText,
LuLayoutGrid, LuCloud, LuBook, LuRss, LuBookOpen, LuLayoutGrid, LuLayoutTemplate, LuCloud, LuBook, LuRss, LuBookOpen,
LuChartBar, LuCpu, LuFileText, LuStar, LuChartBar, LuCpu, LuFileText, LuStar,
} from 'react-icons/lu'; } from 'react-icons/lu';
import {SiGithub, SiSlack} from 'react-icons/si'; import {SiGithub, SiSlack} from 'react-icons/si';
@ -15,6 +15,7 @@ const ICON_MAP: Record<string, IconType> = {
'lu-circle-help': LuCircleHelp, 'lu-circle-help': LuCircleHelp,
'lu-scroll-text': LuScrollText, 'lu-scroll-text': LuScrollText,
'lu-layout-grid': LuLayoutGrid, 'lu-layout-grid': LuLayoutGrid,
'lu-layout-template': LuLayoutTemplate,
'lu-cloud': LuCloud, 'lu-cloud': LuCloud,
'lu-book': LuBook, 'lu-book': LuBook,
'lu-rss': LuRss, 'lu-rss': LuRss,

View file

@ -3,11 +3,12 @@ import DropdownNavbarItem from '@theme-original/NavbarItem/DropdownNavbarItem';
import type DropdownNavbarItemType from '@theme/NavbarItem/DropdownNavbarItem'; import type DropdownNavbarItemType from '@theme/NavbarItem/DropdownNavbarItem';
import type {WrapperProps} from '@docusaurus/types'; import type {WrapperProps} from '@docusaurus/types';
import type {IconType} from 'react-icons'; import type {IconType} from 'react-icons';
import {LuLayoutGrid, LuBook, LuRss, LuBookOpen} from 'react-icons/lu'; import {LuLayoutGrid, LuLayoutTemplate, LuBook, LuRss, LuBookOpen} from 'react-icons/lu';
import {SiSlack} from 'react-icons/si'; import {SiSlack} from 'react-icons/si';
const ICON_MAP: Record<string, IconType> = { const ICON_MAP: Record<string, IconType> = {
'lu-layout-grid': LuLayoutGrid, 'lu-layout-grid': LuLayoutGrid,
'lu-layout-template': LuLayoutTemplate,
'lu-book': LuBook, 'lu-book': LuBook,
'lu-rss': LuRss, 'lu-rss': LuRss,
'lu-book-open': LuBookOpen, 'lu-book-open': LuBookOpen,

View file

@ -0,0 +1,569 @@
{
"$defs": {
"BankTemplateConfig": {
"description": "Bank configuration fields within a template manifest.\n\nOnly includes configurable (per-bank) fields. Credential fields\n(API keys, base URLs) are intentionally excluded for security.",
"properties": {
"reflect_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Mission/context for Reflect operations",
"title": "Reflect Mission"
},
"retain_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Steers what gets extracted during retain",
"title": "Retain Mission"
},
"retain_extraction_mode": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Fact extraction mode: 'concise' (default), 'verbose', or 'custom'",
"title": "Retain Extraction Mode"
},
"retain_custom_instructions": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Custom extraction prompt (when mode='custom')",
"title": "Retain Custom Instructions"
},
"retain_chunk_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Max token size for each content chunk",
"title": "Retain Chunk Size"
},
"enable_observations": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Toggle observation consolidation",
"title": "Enable Observations"
},
"observations_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Controls what gets synthesised",
"title": "Observations Mission"
},
"disposition_skepticism": {
"anyOf": [
{
"maximum": 5,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Skepticism trait (1-5)",
"title": "Disposition Skepticism"
},
"disposition_literalism": {
"anyOf": [
{
"maximum": 5,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Literalism trait (1-5)",
"title": "Disposition Literalism"
},
"disposition_empathy": {
"anyOf": [
{
"maximum": 5,
"minimum": 1,
"type": "integer"
},
{
"type": "null"
}
],
"default": null,
"description": "Empathy trait (1-5)",
"title": "Disposition Empathy"
},
"entity_labels": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Controlled vocabulary for entity labels",
"title": "Entity Labels"
},
"entities_allow_free_form": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"default": null,
"description": "Allow entities outside the label vocabulary",
"title": "Entities Allow Free Form"
}
},
"title": "BankTemplateConfig",
"type": "object"
},
"BankTemplateDirective": {
"description": "A directive definition within a bank template manifest.\n\nDirectives are matched by name on re-import: existing directives\nwith the same name are updated, new ones are created.",
"properties": {
"name": {
"description": "Human-readable name for the directive (used as match key on re-import)",
"title": "Name",
"type": "string"
},
"content": {
"description": "The directive text to inject into prompts",
"title": "Content",
"type": "string"
},
"priority": {
"default": 0,
"description": "Higher priority directives are injected first",
"title": "Priority",
"type": "integer"
},
"is_active": {
"default": true,
"description": "Whether this directive is active",
"title": "Is Active",
"type": "boolean"
},
"tags": {
"default": [],
"description": "Tags for filtering",
"items": {
"type": "string"
},
"title": "Tags",
"type": "array"
}
},
"required": [
"name",
"content"
],
"title": "BankTemplateDirective",
"type": "object"
},
"BankTemplateMentalModel": {
"description": "A mental model definition within a bank template manifest.",
"properties": {
"id": {
"description": "Unique ID for the mental model (alphanumeric lowercase with hyphens)",
"title": "Id",
"type": "string"
},
"name": {
"description": "Human-readable name for the mental model",
"title": "Name",
"type": "string"
},
"source_query": {
"description": "The query to run to generate content",
"title": "Source Query",
"type": "string"
},
"tags": {
"default": [],
"description": "Tags for scoped visibility",
"items": {
"type": "string"
},
"title": "Tags",
"type": "array"
},
"max_tokens": {
"default": 2048,
"description": "Maximum tokens for generated content",
"maximum": 8192,
"minimum": 256,
"title": "Max Tokens",
"type": "integer"
},
"trigger": {
"$ref": "#/$defs/MentalModelTrigger",
"default": {},
"description": "Trigger settings"
}
},
"required": [
"id",
"name",
"source_query"
],
"title": "BankTemplateMentalModel",
"type": "object"
},
"MentalModelTrigger": {
"description": "Trigger settings for a mental model.",
"properties": {
"refresh_after_consolidation": {
"default": false,
"description": "If true, refresh this mental model after observations consolidation (real-time mode)",
"title": "Refresh After Consolidation",
"type": "boolean"
},
"fact_types": {
"anyOf": [
{
"items": {
"enum": [
"world",
"experience",
"observation"
],
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Filter which fact types are retrieved during reflect. None means all types (world, experience, observation).",
"title": "Fact Types"
},
"exclude_mental_models": {
"default": false,
"description": "If true, exclude all mental models from the reflect loop (skip search_mental_models tool).",
"title": "Exclude Mental Models",
"type": "boolean"
},
"exclude_mental_model_ids": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Exclude specific mental models by ID from the reflect loop.",
"title": "Exclude Mental Model Ids"
},
"tags_match": {
"anyOf": [
{
"enum": [
"any",
"all",
"any_strict",
"all_strict"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Override how the model's tags filter memories during refresh. If not set, defaults to 'all_strict' when the model has tags (security isolation) or 'any' when the model has no tags. Set to 'any' to include untagged memories alongside tagged ones during refresh.",
"title": "Tags Match"
},
"tag_groups": {
"anyOf": [
{
"items": {
"anyOf": [
{
"$ref": "#/$defs/TagGroupLeaf"
},
{
"$ref": "#/$defs/TagGroupAnd"
},
{
"$ref": "#/$defs/TagGroupOr"
},
{
"$ref": "#/$defs/TagGroupNot"
}
]
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Compound boolean tag expressions to use during refresh instead of the model's own tags. When set, these tag groups are passed to reflect and the model's flat tags are NOT used for filtering. Supports nested and/or/not expressions for complex tag-based scoping.",
"title": "Tag Groups"
}
},
"title": "MentalModelTrigger",
"type": "object"
},
"TagGroupAnd": {
"description": "Compound AND group: all child filters must match.",
"properties": {
"and": {
"items": {
"anyOf": [
{
"$ref": "#/$defs/TagGroupLeaf"
},
{
"$ref": "#/$defs/TagGroupAnd"
},
{
"$ref": "#/$defs/TagGroupOr"
},
{
"$ref": "#/$defs/TagGroupNot"
}
]
},
"title": "And",
"type": "array"
}
},
"required": [
"and"
],
"title": "TagGroupAnd",
"type": "object"
},
"TagGroupLeaf": {
"description": "A leaf tag filter: matches memories by tag list and match mode.",
"properties": {
"tags": {
"items": {
"type": "string"
},
"title": "Tags",
"type": "array"
},
"match": {
"default": "any_strict",
"enum": [
"any",
"all",
"any_strict",
"all_strict"
],
"title": "Match",
"type": "string"
}
},
"required": [
"tags"
],
"title": "TagGroupLeaf",
"type": "object"
},
"TagGroupNot": {
"description": "Compound NOT group: child filter must NOT match.",
"properties": {
"not": {
"anyOf": [
{
"$ref": "#/$defs/TagGroupLeaf"
},
{
"$ref": "#/$defs/TagGroupAnd"
},
{
"$ref": "#/$defs/TagGroupOr"
},
{
"$ref": "#/$defs/TagGroupNot"
}
],
"title": "Not"
}
},
"required": [
"not"
],
"title": "TagGroupNot",
"type": "object"
},
"TagGroupOr": {
"description": "Compound OR group: at least one child filter must match.",
"properties": {
"or": {
"items": {
"anyOf": [
{
"$ref": "#/$defs/TagGroupLeaf"
},
{
"$ref": "#/$defs/TagGroupAnd"
},
{
"$ref": "#/$defs/TagGroupOr"
},
{
"$ref": "#/$defs/TagGroupNot"
}
]
},
"title": "Or",
"type": "array"
}
},
"required": [
"or"
],
"title": "TagGroupOr",
"type": "object"
}
},
"description": "A bank template manifest for import/export.\n\nVersion field enables forward-compatible schema evolution: the API\nauto-upgrades older manifest versions to the current schema on import.",
"example": {
"bank": {
"disposition_empathy": 5,
"enable_observations": true,
"reflect_mission": "You are helping a support agent remember customer interactions.",
"retain_mission": "Extract customer issues, resolutions, and sentiment."
},
"directives": [
{
"content": "Always respond with empathy and understanding.",
"name": "Always be empathetic",
"priority": 10
}
],
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {
"refresh_after_consolidation": true
}
}
],
"version": "1"
},
"properties": {
"version": {
"description": "Manifest schema version (currently '1')",
"title": "Version",
"type": "string"
},
"bank": {
"anyOf": [
{
"$ref": "#/$defs/BankTemplateConfig"
},
{
"type": "null"
}
],
"default": null,
"description": "Bank configuration to apply. Omit to leave config unchanged."
},
"mental_models": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/BankTemplateMentalModel"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Mental models to create or update (matched by id). Omit to leave unchanged.",
"title": "Mental Models"
},
"directives": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/BankTemplateDirective"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Directives to create or update (matched by name). Omit to leave unchanged.",
"title": "Directives"
}
},
"required": [
"version"
],
"title": "BankTemplateManifest",
"type": "object"
}

View file

@ -3100,6 +3100,156 @@
} }
} }
}, },
"/v1/default/banks/{bank_id}/import": {
"post": {
"tags": [
"Bank Templates"
],
"summary": "Import bank template",
"description": "Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name \u2014 existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.",
"operationId": "import_bank_template",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "dry_run",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"description": "Validate only, do not apply changes",
"default": false,
"title": "Dry Run"
},
"description": "Validate only, do not apply changes"
},
{
"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/BankTemplateImportResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/export": {
"get": {
"tags": [
"Bank Templates"
],
"summary": "Export bank template",
"description": "Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.",
"operationId": "export_bank_template",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank 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/BankTemplateManifest"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/bank-template-schema": {
"get": {
"tags": [
"Bank Templates"
],
"summary": "Get bank template JSON Schema",
"description": "Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.",
"operationId": "get_bank_template_schema",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/observations": { "/v1/default/banks/{bank_id}/observations": {
"delete": { "delete": {
"tags": [ "tags": [
@ -4993,6 +5143,411 @@
"total_observations": 45 "total_observations": 45
} }
}, },
"BankTemplateConfig": {
"properties": {
"reflect_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Reflect Mission",
"description": "Mission/context for Reflect operations"
},
"retain_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Mission",
"description": "Steers what gets extracted during retain"
},
"retain_extraction_mode": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Extraction Mode",
"description": "Fact extraction mode: 'concise' (default), 'verbose', or 'custom'"
},
"retain_custom_instructions": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Custom Instructions",
"description": "Custom extraction prompt (when mode='custom')"
},
"retain_chunk_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Retain Chunk Size",
"description": "Max token size for each content chunk"
},
"enable_observations": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Enable Observations",
"description": "Toggle observation consolidation"
},
"observations_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Observations Mission",
"description": "Controls what gets synthesised"
},
"disposition_skepticism": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Skepticism",
"description": "Skepticism trait (1-5)"
},
"disposition_literalism": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Literalism",
"description": "Literalism trait (1-5)"
},
"disposition_empathy": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Empathy",
"description": "Empathy trait (1-5)"
},
"entity_labels": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Entity Labels",
"description": "Controlled vocabulary for entity labels"
},
"entities_allow_free_form": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Entities Allow Free Form",
"description": "Allow entities outside the label vocabulary"
}
},
"type": "object",
"title": "BankTemplateConfig",
"description": "Bank configuration fields within a template manifest.\n\nOnly includes configurable (per-bank) fields. Credential fields\n(API keys, base URLs) are intentionally excluded for security."
},
"BankTemplateDirective": {
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "Human-readable name for the directive (used as match key on re-import)"
},
"content": {
"type": "string",
"title": "Content",
"description": "The directive text to inject into prompts"
},
"priority": {
"type": "integer",
"title": "Priority",
"description": "Higher priority directives are injected first",
"default": 0
},
"is_active": {
"type": "boolean",
"title": "Is Active",
"description": "Whether this directive is active",
"default": true
},
"tags": {
"items": {
"type": "string"
},
"type": "array",
"title": "Tags",
"description": "Tags for filtering",
"default": []
}
},
"type": "object",
"required": [
"name",
"content"
],
"title": "BankTemplateDirective",
"description": "A directive definition within a bank template manifest.\n\nDirectives are matched by name on re-import: existing directives\nwith the same name are updated, new ones are created."
},
"BankTemplateImportResponse": {
"properties": {
"bank_id": {
"type": "string",
"title": "Bank Id",
"description": "Bank that was imported into"
},
"config_applied": {
"type": "boolean",
"title": "Config Applied",
"description": "Whether bank config was updated"
},
"mental_models_created": {
"items": {
"type": "string"
},
"type": "array",
"title": "Mental Models Created",
"description": "IDs of newly created mental models",
"default": []
},
"mental_models_updated": {
"items": {
"type": "string"
},
"type": "array",
"title": "Mental Models Updated",
"description": "IDs of updated mental models",
"default": []
},
"directives_created": {
"items": {
"type": "string"
},
"type": "array",
"title": "Directives Created",
"description": "Names of newly created directives",
"default": []
},
"directives_updated": {
"items": {
"type": "string"
},
"type": "array",
"title": "Directives Updated",
"description": "Names of updated directives",
"default": []
},
"operation_ids": {
"items": {
"type": "string"
},
"type": "array",
"title": "Operation Ids",
"description": "Operation IDs for mental model content generation (async)",
"default": []
},
"dry_run": {
"type": "boolean",
"title": "Dry Run",
"description": "True if this was a validation-only run",
"default": false
}
},
"type": "object",
"required": [
"bank_id",
"config_applied"
],
"title": "BankTemplateImportResponse",
"description": "Response model for the bank template import endpoint."
},
"BankTemplateManifest": {
"properties": {
"version": {
"type": "string",
"title": "Version",
"description": "Manifest schema version (currently '1')"
},
"bank": {
"anyOf": [
{
"$ref": "#/components/schemas/BankTemplateConfig"
},
{
"type": "null"
}
],
"description": "Bank configuration to apply. Omit to leave config unchanged."
},
"mental_models": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/BankTemplateMentalModel"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Mental Models",
"description": "Mental models to create or update (matched by id). Omit to leave unchanged."
},
"directives": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/BankTemplateDirective"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Directives",
"description": "Directives to create or update (matched by name). Omit to leave unchanged."
}
},
"type": "object",
"required": [
"version"
],
"title": "BankTemplateManifest",
"description": "A bank template manifest for import/export.\n\nVersion field enables forward-compatible schema evolution: the API\nauto-upgrades older manifest versions to the current schema on import.",
"example": {
"bank": {
"disposition_empathy": 5,
"enable_observations": true,
"reflect_mission": "You are helping a support agent remember customer interactions.",
"retain_mission": "Extract customer issues, resolutions, and sentiment."
},
"directives": [
{
"content": "Always respond with empathy and understanding.",
"name": "Always be empathetic",
"priority": 10
}
],
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {
"refresh_after_consolidation": true
}
}
],
"version": "1"
}
},
"BankTemplateMentalModel": {
"properties": {
"id": {
"type": "string",
"title": "Id",
"description": "Unique ID for the mental model (alphanumeric lowercase with hyphens)"
},
"name": {
"type": "string",
"title": "Name",
"description": "Human-readable name for the mental model"
},
"source_query": {
"type": "string",
"title": "Source Query",
"description": "The query to run to generate content"
},
"tags": {
"items": {
"type": "string"
},
"type": "array",
"title": "Tags",
"description": "Tags for scoped visibility",
"default": []
},
"max_tokens": {
"type": "integer",
"maximum": 8192.0,
"minimum": 256.0,
"title": "Max Tokens",
"description": "Maximum tokens for generated content",
"default": 2048
},
"trigger": {
"$ref": "#/components/schemas/MentalModelTrigger-Output",
"description": "Trigger settings",
"default": {}
}
},
"type": "object",
"required": [
"id",
"name",
"source_query"
],
"title": "BankTemplateMentalModel",
"description": "A mental model definition within a bank template manifest."
},
"Body_file_retain": { "Body_file_retain": {
"properties": { "properties": {
"files": { "files": {

View file

@ -0,0 +1,437 @@
# Bank Templates
Declarative JSON manifests for creating pre-configured memory banks with a single API call.
{/* Import raw source files */}
## Overview
A bank template is a JSON manifest that describes a bank's full setup: configuration overrides, mental models, directives, and more. Instead of making multiple API calls to configure a bank, you submit one manifest and the API provisions everything.
Templates are useful for:
- **Replication** — stamp out identically-configured banks for multiple users or agents
- **Onboarding** — new users start with a known-good configuration instead of configuring from scratch
- **Sharing** — distribute recommended setups as portable JSON files
- **Framework integrations** — ship a recommended template alongside your integration
Browse the Bank Templates Hub for ready-to-use templates.
## Manifest Schema
```json
{
"version": "1",
"bank": {
"reflect_mission": "...",
"retain_mission": "...",
"retain_extraction_mode": "concise | verbose | custom | chunks",
"retain_custom_instructions": "...",
"retain_chunk_size": 2048,
"disposition_skepticism": 3,
"disposition_literalism": 3,
"disposition_empathy": 3,
"enable_observations": true,
"observations_mission": "...",
"entity_labels": ["PERSON", "ORGANIZATION"],
"entities_allow_free_form": true
},
"mental_models": [
{
"id": "unique-lowercase-id",
"name": "Human-Readable Name",
"source_query": "The query that generates this mental model's content",
"tags": ["optional", "tags"],
"max_tokens": 2048,
"trigger": {
"refresh_after_consolidation": false,
"fact_types": ["world", "experience", "observation"],
"exclude_mental_models": false,
"exclude_mental_model_ids": []
}
}
],
"directives": [
{
"name": "directive-name",
"content": "The directive instruction text",
"priority": 0,
"is_active": true,
"tags": ["optional", "tags"]
}
]
}
```
### Fields
| Field | Required | Description |
|-------|----------|-------------|
| `version` | Yes | Schema version. Currently `"1"`. |
| `bank` | No | Bank configuration overrides. Omit to leave config unchanged. |
| `mental_models` | No | Mental models to create or update. Omit to leave unchanged. |
| `directives` | No | Directives to create or update. Omit to leave unchanged. |
All of `bank`, `mental_models`, and `directives` are optional. Omit any section to leave that part of the bank unchanged.
### Bank Config Fields
All fields in `bank` are optional. Only the fields you include will be set as per-bank overrides — everything else inherits from the server/tenant defaults.
| Field | Type | Description |
|-------|------|-------------|
| `reflect_mission` | string | Mission/context for reflect operations |
| `retain_mission` | string | Steers what gets extracted during retain |
| `retain_extraction_mode` | string | `concise`, `verbose`, `custom`, or `chunks` |
| `retain_custom_instructions` | string | Custom extraction prompt (requires `mode=custom`) |
| `retain_chunk_size` | integer | Max token size per content chunk |
| `disposition_skepticism` | integer (1-5) | How skeptical the disposition is |
| `disposition_literalism` | integer (1-5) | How literal the disposition is |
| `disposition_empathy` | integer (1-5) | How empathetic the disposition is |
| `enable_observations` | boolean | Toggle observation consolidation |
| `observations_mission` | string | Controls what gets synthesised into observations |
| `entity_labels` | string[] | Controlled vocabulary for entity labels |
| `entities_allow_free_form` | boolean | Allow entities outside the label vocabulary |
### Mental Model Fields
| Field | Required | Description |
|-------|----------|-------------|
| `id` | Yes | Unique ID (lowercase alphanumeric with hyphens). Used to match on re-import. |
| `name` | Yes | Human-readable name |
| `source_query` | Yes | The query that generates this model's content via reflect |
| `tags` | No | Tags for scoped visibility. Default: `[]` |
| `max_tokens` | No | Max tokens for generated content (256-8192). Default: `2048` |
| `trigger` | No | Trigger settings for auto-refresh |
### Directive Fields
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Directive name. Used as the match key on re-import. |
| `content` | Yes | The directive instruction text. |
| `priority` | No | Priority value (higher = more important). Default: `0` |
| `is_active` | No | Whether the directive is active. Default: `true` |
| `tags` | No | Tags for categorization. Default: `[]` |
## Import
Import a manifest into a bank. If the bank doesn't exist, it's created automatically.
### Python
```python
template = {
"version": "1",
"bank": {
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"enable_observations": True,
"observations_mission": "Track recurring customer pain points.",
},
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {"refresh_after_consolidation": True},
}
],
"directives": [
{
"name": "Acknowledge frustration",
"content": "Always acknowledge frustration before offering solutions.",
"priority": 10,
}
],
}
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/import",
json=template,
)
result = response.json()
print(f"Config applied: {result['config_applied']}")
print(f"Mental models created: {result['mental_models_created']}")
print(f"Directives created: {result['directives_created']}")
```
### Node.js
```javascript
const template = {
version: '1',
bank: {
retain_mission: 'Extract customer issues, resolutions, and sentiment.',
enable_observations: true,
observations_mission: 'Track recurring customer pain points.',
},
mental_models: [
{
id: 'sentiment-overview',
name: 'Customer Sentiment Overview',
source_query: 'What is the overall sentiment trend?',
trigger: { refresh_after_consolidation: true },
},
],
directives: [
{
name: 'Acknowledge frustration',
content: 'Always acknowledge frustration before offering solutions.',
priority: 10,
},
],
};
const importResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/import`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(template),
},
);
const result = await importResponse.json();
console.log('Config applied:', result.config_applied);
console.log('Mental models created:', result.mental_models_created);
console.log('Directives created:', result.directives_created);
```
### CLI
```bash
curl -X POST "$HINDSIGHT_URL/v1/default/banks/my-bank/import" \
-H "Content-Type: application/json" \
-d '{
"version": "1",
"bank": {
"retain_mission": "Extract customer issues, resolutions, and sentiment.",
"enable_observations": true,
"observations_mission": "Track recurring customer pain points."
},
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": { "refresh_after_consolidation": true }
}
],
"directives": [
{
"name": "Acknowledge frustration",
"content": "Always acknowledge frustration before offering solutions.",
"priority": 10
}
]
}'
```
### Go
```go
# Section 'import-template' not found in api/bank-templates.go
```
### Behavior
- **Config**: all `bank` fields are applied as per-bank config overrides
- **Mental models**: matched by `id` — existing models are updated, new ones are created
- **Directives**: matched by `name` — existing directives are updated, new ones are created
- **Async**: mental model content is generated asynchronously. The response includes `operation_ids` to track progress.
### Dry Run
Validate a manifest without applying changes:
### Python
```python
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/import",
params={"dry_run": "true"},
json=template,
)
result = response.json()
print(f"Dry run: {result['dry_run']}")
print(f"Would apply config: {result['config_applied']}")
```
### Node.js
```javascript
const dryRunResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/import?dry_run=true`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(template),
},
);
const dryRunResult = await dryRunResponse.json();
console.log('Dry run:', dryRunResult.dry_run);
console.log('Would apply config:', dryRunResult.config_applied);
```
### CLI
```bash
curl -X POST "$HINDSIGHT_URL/v1/default/banks/my-bank/import?dry_run=true" \
-H "Content-Type: application/json" \
-d @template.json
```
### Go
```go
# Section 'import-dry-run' not found in api/bank-templates.go
```
Returns what *would* happen (which config would be applied, which mental models would be created) without making any changes. Returns HTTP 400 with a detailed error message if the manifest is invalid.
## Export
Export a bank's current config overrides, mental models, and directives as a manifest:
### Python
```python
response = requests.get(
f"{HINDSIGHT_URL}/v1/default/banks/my-bank/export"
)
exported = response.json()
print(json.dumps(exported, indent=2))
```
### Node.js
```javascript
const exportResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/my-bank/export`,
);
const exported = await exportResponse.json();
console.log(JSON.stringify(exported, null, 2));
```
### CLI
```bash
curl "$HINDSIGHT_URL/v1/default/banks/my-bank/export"
```
### Go
```go
# Section 'export-template' not found in api/bank-templates.go
```
The exported manifest only includes config fields that were explicitly set as per-bank overrides — not the fully resolved config (which includes server/tenant defaults). This means the exported manifest is portable: importing it into a new bank only overrides the fields that were intentionally customized.
### Round-trip
Export from one bank and import into another to replicate the setup:
### Python
```python
# Export from source bank
response = requests.get(
f"{HINDSIGHT_URL}/v1/default/banks/source-bank/export"
)
exported = response.json()
# Import into a new bank
response = requests.post(
f"{HINDSIGHT_URL}/v1/default/banks/new-bank/import",
json=exported,
)
```
### Node.js
```javascript
// Export from source bank
const srcResponse = await fetch(
`${HINDSIGHT_URL}/v1/default/banks/source-bank/export`,
);
const srcExported = await srcResponse.json();
// Import into a new bank
await fetch(`${HINDSIGHT_URL}/v1/default/banks/new-bank/import`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(srcExported),
});
```
### CLI
```bash
# Export from source bank
curl "$HINDSIGHT_URL/v1/default/banks/source-bank/export" > template.json
# Import into a new bank
curl -X POST "$HINDSIGHT_URL/v1/default/banks/new-bank/import" \
-H "Content-Type: application/json" \
-d @template.json
```
### Go
```go
# Section 'export-reimport' not found in api/bank-templates.go
```
## JSON Schema
The manifest format is defined by a JSON Schema. Fetch the live schema from your server:
### Python
```python
response = requests.get(
f"{HINDSIGHT_URL}/v1/bank-template-schema"
)
schema = response.json()
print(json.dumps(schema, indent=2))
```
### Node.js
```javascript
const schemaResponse = await fetch(
`${HINDSIGHT_URL}/v1/bank-template-schema`,
);
const schema = await schemaResponse.json();
console.log(JSON.stringify(schema, null, 2));
```
### CLI
```bash
curl "$HINDSIGHT_URL/v1/bank-template-schema"
```
### Go
```go
# Section 'get-schema' not found in api/bank-templates.go
```
The static schema is also available at bank-template-schema.json.
## Control Plane
The control plane bank creation dialog includes an optional "Import from template" toggle. Enable it to paste a manifest JSON and pre-configure the bank on creation.
You can also export any bank's template from the bank Settings page via **Actions → Export Template**, which copies the manifest JSON to your clipboard.
## Versioning
The `version` field enables forward-compatible schema evolution. The current version is `"1"`.
When future versions are released:
- Older manifests are automatically upgraded to the current schema on import
- Export always produces the latest version
- The API rejects manifests with a version newer than what the server supports (with a clear error message suggesting an upgrade)
This means old templates keep working indefinitely — no need to manually update them.

View file

@ -3066,6 +3066,156 @@
} }
} }
}, },
"/v1/default/banks/{bank_id}/import": {
"post": {
"tags": [
"Bank Templates"
],
"summary": "Import bank template",
"description": "Import a bank template manifest to create or update a bank's configuration, mental models, and directives. If the bank does not exist it is created. Config fields are applied as per-bank overrides. Mental models are matched by id, directives by name \u2014 existing ones are updated, new ones are created. Use dry_run=true to validate the manifest without applying changes.",
"operationId": "import_bank_template",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank Id"
}
},
{
"name": "dry_run",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"description": "Validate only, do not apply changes",
"default": false,
"title": "Dry Run"
},
"description": "Validate only, do not apply changes"
},
{
"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/BankTemplateImportResponse"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/export": {
"get": {
"tags": [
"Bank Templates"
],
"summary": "Export bank template",
"description": "Export a bank's current configuration, mental models, and directives as a template manifest. The exported manifest can be imported into another bank to replicate the setup.",
"operationId": "export_bank_template",
"parameters": [
{
"name": "bank_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Bank 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/BankTemplateManifest"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
},
"/v1/bank-template-schema": {
"get": {
"tags": [
"Bank Templates"
],
"summary": "Get bank template JSON Schema",
"description": "Returns the JSON Schema for the bank template manifest format. Use this to validate template manifests before importing.",
"operationId": "get_bank_template_schema",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
"/v1/default/banks/{bank_id}/observations": { "/v1/default/banks/{bank_id}/observations": {
"delete": { "delete": {
"tags": [ "tags": [
@ -4959,6 +5109,411 @@
"total_observations": 45 "total_observations": 45
} }
}, },
"BankTemplateConfig": {
"properties": {
"reflect_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Reflect Mission",
"description": "Mission/context for Reflect operations"
},
"retain_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Mission",
"description": "Steers what gets extracted during retain"
},
"retain_extraction_mode": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Extraction Mode",
"description": "Fact extraction mode: 'concise' (default), 'verbose', or 'custom'"
},
"retain_custom_instructions": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Retain Custom Instructions",
"description": "Custom extraction prompt (when mode='custom')"
},
"retain_chunk_size": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Retain Chunk Size",
"description": "Max token size for each content chunk"
},
"enable_observations": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Enable Observations",
"description": "Toggle observation consolidation"
},
"observations_mission": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Observations Mission",
"description": "Controls what gets synthesised"
},
"disposition_skepticism": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Skepticism",
"description": "Skepticism trait (1-5)"
},
"disposition_literalism": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Literalism",
"description": "Literalism trait (1-5)"
},
"disposition_empathy": {
"anyOf": [
{
"type": "integer",
"maximum": 5.0,
"minimum": 1.0
},
{
"type": "null"
}
],
"title": "Disposition Empathy",
"description": "Empathy trait (1-5)"
},
"entity_labels": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Entity Labels",
"description": "Controlled vocabulary for entity labels"
},
"entities_allow_free_form": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Entities Allow Free Form",
"description": "Allow entities outside the label vocabulary"
}
},
"type": "object",
"title": "BankTemplateConfig",
"description": "Bank configuration fields within a template manifest.\n\nOnly includes configurable (per-bank) fields. Credential fields\n(API keys, base URLs) are intentionally excluded for security."
},
"BankTemplateDirective": {
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "Human-readable name for the directive (used as match key on re-import)"
},
"content": {
"type": "string",
"title": "Content",
"description": "The directive text to inject into prompts"
},
"priority": {
"type": "integer",
"title": "Priority",
"description": "Higher priority directives are injected first",
"default": 0
},
"is_active": {
"type": "boolean",
"title": "Is Active",
"description": "Whether this directive is active",
"default": true
},
"tags": {
"items": {
"type": "string"
},
"type": "array",
"title": "Tags",
"description": "Tags for filtering",
"default": []
}
},
"type": "object",
"required": [
"name",
"content"
],
"title": "BankTemplateDirective",
"description": "A directive definition within a bank template manifest.\n\nDirectives are matched by name on re-import: existing directives\nwith the same name are updated, new ones are created."
},
"BankTemplateImportResponse": {
"properties": {
"bank_id": {
"type": "string",
"title": "Bank Id",
"description": "Bank that was imported into"
},
"config_applied": {
"type": "boolean",
"title": "Config Applied",
"description": "Whether bank config was updated"
},
"mental_models_created": {
"items": {
"type": "string"
},
"type": "array",
"title": "Mental Models Created",
"description": "IDs of newly created mental models",
"default": []
},
"mental_models_updated": {
"items": {
"type": "string"
},
"type": "array",
"title": "Mental Models Updated",
"description": "IDs of updated mental models",
"default": []
},
"directives_created": {
"items": {
"type": "string"
},
"type": "array",
"title": "Directives Created",
"description": "Names of newly created directives",
"default": []
},
"directives_updated": {
"items": {
"type": "string"
},
"type": "array",
"title": "Directives Updated",
"description": "Names of updated directives",
"default": []
},
"operation_ids": {
"items": {
"type": "string"
},
"type": "array",
"title": "Operation Ids",
"description": "Operation IDs for mental model content generation (async)",
"default": []
},
"dry_run": {
"type": "boolean",
"title": "Dry Run",
"description": "True if this was a validation-only run",
"default": false
}
},
"type": "object",
"required": [
"bank_id",
"config_applied"
],
"title": "BankTemplateImportResponse",
"description": "Response model for the bank template import endpoint."
},
"BankTemplateManifest": {
"properties": {
"version": {
"type": "string",
"title": "Version",
"description": "Manifest schema version (currently '1')"
},
"bank": {
"anyOf": [
{
"$ref": "#/components/schemas/BankTemplateConfig"
},
{
"type": "null"
}
],
"description": "Bank configuration to apply. Omit to leave config unchanged."
},
"mental_models": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/BankTemplateMentalModel"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Mental Models",
"description": "Mental models to create or update (matched by id). Omit to leave unchanged."
},
"directives": {
"anyOf": [
{
"items": {
"$ref": "#/components/schemas/BankTemplateDirective"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Directives",
"description": "Directives to create or update (matched by name). Omit to leave unchanged."
}
},
"type": "object",
"required": [
"version"
],
"title": "BankTemplateManifest",
"description": "A bank template manifest for import/export.\n\nVersion field enables forward-compatible schema evolution: the API\nauto-upgrades older manifest versions to the current schema on import.",
"example": {
"bank": {
"disposition_empathy": 5,
"enable_observations": true,
"reflect_mission": "You are helping a support agent remember customer interactions.",
"retain_mission": "Extract customer issues, resolutions, and sentiment."
},
"directives": [
{
"content": "Always respond with empathy and understanding.",
"name": "Always be empathetic",
"priority": 10
}
],
"mental_models": [
{
"id": "sentiment-overview",
"name": "Customer Sentiment Overview",
"source_query": "What is the overall sentiment trend?",
"trigger": {
"refresh_after_consolidation": true
}
}
],
"version": "1"
}
},
"BankTemplateMentalModel": {
"properties": {
"id": {
"type": "string",
"title": "Id",
"description": "Unique ID for the mental model (alphanumeric lowercase with hyphens)"
},
"name": {
"type": "string",
"title": "Name",
"description": "Human-readable name for the mental model"
},
"source_query": {
"type": "string",
"title": "Source Query",
"description": "The query to run to generate content"
},
"tags": {
"items": {
"type": "string"
},
"type": "array",
"title": "Tags",
"description": "Tags for scoped visibility",
"default": []
},
"max_tokens": {
"type": "integer",
"maximum": 8192.0,
"minimum": 256.0,
"title": "Max Tokens",
"description": "Maximum tokens for generated content",
"default": 2048
},
"trigger": {
"$ref": "#/components/schemas/MentalModelTrigger-Output",
"description": "Trigger settings",
"default": {}
}
},
"type": "object",
"required": [
"id",
"name",
"source_query"
],
"title": "BankTemplateMentalModel",
"description": "A mental model definition within a bank template manifest."
},
"Body_file_retain": { "Body_file_retain": {
"properties": { "properties": {
"files": { "files": {