From a56cd044e516400c9744f457af4f1df9f6ab5c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 19 Mar 2026 11:31:51 +0100 Subject: [PATCH] feat: 4-tab code parity across all documentation examples (#613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: independent versioning for integrations - Add per-integration changelog pages at /changelog/integrations/ - Move main changelog to changelog/index.md (URL unchanged) - Add --integration flag to generate-changelog for LLM-based per-integration changelog generation - Add scripts/release-integration.sh for cutting integration releases - Add .github/workflows/release-integration.yml to publish on integrations/** tags - Remove integrations from main release.sh and release.yml cycle * fix: add agno and hermes integration docs to version-0.4 for production build * chore: apply ruff formatting to generate_changelog.py * feat: add 4-tab code parity across all documentation examples Every code snippet Tabs block now has Python, Node.js, CLI, and Go variants. Raw HTTP/curl tabs replaced with proper SDK calls. New example files: - Go: retain.go, recall.go, reflect.go, memory-banks.go, directives.go, mental-models.go, documents.go, main-methods.go - Shell: memory-banks.sh, directives.sh, mental-models.sh - Node.js: mental-models.mjs Extended example files with missing sections: - recall.mjs/sh: world/experience/observation types, token-budget, all tag modes - reflect.sh: reflect-with-params, reflect-disposition, reflect-sources, reflect-with-tags - reflect.mjs: reflect-with-tags, fixed reflect-sources API usage - retain.mjs/sh: retain-conversation, retain-batch, retain-files-batch SDK/CLI additions: - TypeScript: getMentalModelHistory method - CLI recall: --tags, --tags-match flags - CLI reflect: --tags, --tags-match, --include-facts flags - CLI directive update: --is-active flag - CLI bank set-config: --retain-mission, --retain-extraction-mode, --observations-mission, --reflect-mission, --disposition-* flags Build validation: - scripts/check-code-parity.mjs validates 4-tab parity across all MDX files - Integrated into npm run build — fails if any Tabs block is missing a variant * fix: fix doc examples for Go, Node.js, CLI + add mental model with-id examples - Fix Go Budget constants: BUDGET_HIGH/LOW/MID → HIGH/LOW/MID - Fix Go documents.go: ListDocuments returns []map[string]interface{}, use map access - Fix Go retain.go: use correct relative path for sample.pdf - Fix Node.js createMentalModel: use positional args (name, sourceQuery) not object - Add CLI 'history' subcommand for mental models (api.rs, main.rs, mental_model.rs) - Rebuild TypeScript/Python clients to support id param in createMentalModel - Add create-mental-model-with-id examples across all 4 languages and docs * fix: move id param to end of create_mental_model signature for backwards compat --- hindsight-cli/src/api.rs | 7 + hindsight-cli/src/commands/bank.rs | 30 ++- hindsight-cli/src/commands/directive.rs | 7 +- hindsight-cli/src/commands/memory.rs | 36 ++- hindsight-cli/src/commands/mental_model.rs | 49 ++++ hindsight-cli/src/main.rs | 80 ++++++- .../hindsight_client/hindsight_client.py | 3 + hindsight-clients/typescript/src/index.ts | 14 ++ .../docs/developer/api/documents.mdx | 19 ++ .../docs/developer/api/main-methods.mdx | 33 +-- .../docs/developer/api/memory-banks.mdx | 61 ++++- .../docs/developer/api/mental-models.mdx | 141 +++++++----- .../docs/developer/api/quickstart.mdx | 10 + hindsight-docs/docs/developer/api/recall.mdx | 87 +++++++ hindsight-docs/docs/developer/api/reflect.mdx | 31 +++ hindsight-docs/docs/developer/api/retain.mdx | 46 +++- hindsight-docs/examples/api/directives.go | 86 +++++++ hindsight-docs/examples/api/directives.sh | 51 ++++ hindsight-docs/examples/api/documents.go | 94 ++++++++ hindsight-docs/examples/api/main-methods.go | 60 +++++ hindsight-docs/examples/api/memory-banks.go | 114 +++++++++ hindsight-docs/examples/api/memory-banks.sh | 71 ++++++ hindsight-docs/examples/api/mental-models.go | 173 ++++++++++++++ hindsight-docs/examples/api/mental-models.mjs | 129 +++++++++++ hindsight-docs/examples/api/mental-models.py | 12 + hindsight-docs/examples/api/mental-models.sh | 90 ++++++++ hindsight-docs/examples/api/recall.go | 217 ++++++++++++++++++ hindsight-docs/examples/api/recall.mjs | 82 +++++++ hindsight-docs/examples/api/recall.sh | 70 ++++++ hindsight-docs/examples/api/reflect.go | 155 +++++++++++++ hindsight-docs/examples/api/reflect.mjs | 15 +- hindsight-docs/examples/api/reflect.sh | 26 ++- hindsight-docs/examples/api/retain.go | 137 +++++++++++ hindsight-docs/examples/api/retain.mjs | 15 ++ hindsight-docs/examples/api/retain.sh | 25 ++ hindsight-docs/package.json | 2 +- hindsight-docs/scripts/check-code-parity.mjs | 128 +++++++++++ package-lock.json | 4 +- 38 files changed, 2306 insertions(+), 104 deletions(-) create mode 100644 hindsight-docs/examples/api/directives.go create mode 100644 hindsight-docs/examples/api/directives.sh create mode 100644 hindsight-docs/examples/api/documents.go create mode 100644 hindsight-docs/examples/api/main-methods.go create mode 100644 hindsight-docs/examples/api/memory-banks.go create mode 100644 hindsight-docs/examples/api/memory-banks.sh create mode 100644 hindsight-docs/examples/api/mental-models.go create mode 100644 hindsight-docs/examples/api/mental-models.mjs create mode 100644 hindsight-docs/examples/api/mental-models.sh create mode 100644 hindsight-docs/examples/api/recall.go create mode 100644 hindsight-docs/examples/api/reflect.go create mode 100644 hindsight-docs/examples/api/retain.go create mode 100644 hindsight-docs/scripts/check-code-parity.mjs diff --git a/hindsight-cli/src/api.rs b/hindsight-cli/src/api.rs index f0a01cac..a81857a1 100644 --- a/hindsight-cli/src/api.rs +++ b/hindsight-cli/src/api.rs @@ -601,6 +601,13 @@ impl ApiClient { }) } + pub fn get_mental_model_history(&self, bank_id: &str, mental_model_id: &str, _verbose: bool) -> Result { + self.runtime.block_on(async { + let response = self.client.get_mental_model_history(bank_id, mental_model_id, None).await?; + Ok(response.into_inner()) + }) + } + // --- Directive Methods --- pub fn list_directives(&self, bank_id: &str, _verbose: bool) -> Result { diff --git a/hindsight-cli/src/commands/bank.rs b/hindsight-cli/src/commands/bank.rs index 09f0a1e5..f9bdde4b 100644 --- a/hindsight-cli/src/commands/bank.rs +++ b/hindsight-cli/src/commands/bank.rs @@ -717,6 +717,13 @@ pub fn set_config( llm_model: Option, llm_api_key: Option, llm_base_url: Option, + retain_mission: Option, + retain_extraction_mode: Option, + observations_mission: Option, + reflect_mission: Option, + disposition_skepticism: Option, + disposition_literalism: Option, + disposition_empathy: Option, verbose: bool, output_format: OutputFormat, ) -> Result<()> { @@ -736,9 +743,30 @@ pub fn set_config( if let Some(base_url) = llm_base_url { updates.insert("llm_base_url".to_string(), serde_json::Value::String(base_url)); } + if let Some(mission) = retain_mission { + updates.insert("retain_mission".to_string(), serde_json::Value::String(mission)); + } + if let Some(mode) = retain_extraction_mode { + updates.insert("retain_extraction_mode".to_string(), serde_json::Value::String(mode)); + } + if let Some(mission) = observations_mission { + updates.insert("observations_mission".to_string(), serde_json::Value::String(mission)); + } + if let Some(mission) = reflect_mission { + updates.insert("reflect_mission".to_string(), serde_json::Value::String(mission)); + } + if let Some(skepticism) = disposition_skepticism { + updates.insert("disposition_skepticism".to_string(), serde_json::Value::Number(skepticism.into())); + } + if let Some(literalism) = disposition_literalism { + updates.insert("disposition_literalism".to_string(), serde_json::Value::Number(literalism.into())); + } + if let Some(empathy) = disposition_empathy { + updates.insert("disposition_empathy".to_string(), serde_json::Value::Number(empathy.into())); + } if updates.is_empty() { - return Err(anyhow!("No config updates provided. Use --llm-provider, --llm-model, --llm-api-key, or --llm-base-url".to_string())); + return Err(anyhow!("No config updates provided. Use --llm-provider, --llm-model, --retain-mission, --observations-mission, or other flags".to_string())); } let spinner = if output_format == OutputFormat::Pretty { diff --git a/hindsight-cli/src/commands/directive.rs b/hindsight-cli/src/commands/directive.rs index 645b17b3..c0a08502 100644 --- a/hindsight-cli/src/commands/directive.rs +++ b/hindsight-cli/src/commands/directive.rs @@ -149,11 +149,12 @@ pub fn update( directive_id: &str, name: Option, content: Option, + is_active: Option, verbose: bool, output_format: OutputFormat, ) -> Result<()> { - if name.is_none() && content.is_none() { - anyhow::bail!("At least one of --name or --content must be provided"); + if name.is_none() && content.is_none() && is_active.is_none() { + anyhow::bail!("At least one of --name, --content, or --is-active must be provided"); } let spinner = if output_format == OutputFormat::Pretty { @@ -165,7 +166,7 @@ pub fn update( let request = types::UpdateDirectiveRequest { name, content, - is_active: None, + is_active, priority: None, tags: None, }; diff --git a/hindsight-cli/src/commands/memory.rs b/hindsight-cli/src/commands/memory.rs index 1bf00425..9a81b587 100644 --- a/hindsight-cli/src/commands/memory.rs +++ b/hindsight-cli/src/commands/memory.rs @@ -9,7 +9,7 @@ use crate::output::{self, OutputFormat}; use crate::ui; // Import types from generated client -use hindsight_client::types::{Budget, ChunkIncludeOptions, IncludeOptions, TagsMatch}; +use hindsight_client::types::{Budget, ChunkIncludeOptions, FactsIncludeOptions, IncludeOptions, ReflectIncludeOptions, TagsMatch}; use serde::Deserialize; use serde_json; @@ -43,6 +43,16 @@ fn parse_budget(budget: &str) -> Budget { } } +// Helper function to parse tags_match string to TagsMatch enum +fn parse_tags_match(tags_match: &Option) -> TagsMatch { + match tags_match.as_deref().unwrap_or("any").to_lowercase().as_str() { + "all" => TagsMatch::All, + "any_strict" => TagsMatch::AnyStrict, + "all_strict" => TagsMatch::AllStrict, + _ => TagsMatch::Any, + } +} + /// List memory units with pagination and optional filters pub fn list( client: &ApiClient, @@ -250,6 +260,8 @@ pub fn recall( trace: bool, include_chunks: bool, chunk_max_tokens: i64, + tags: Vec, + tags_match: Option, verbose: bool, output_format: OutputFormat, ) -> Result<()> { @@ -280,8 +292,8 @@ pub fn recall( trace, query_timestamp: None, include, - tags: None, - tags_match: TagsMatch::Any, + tags: if tags.is_empty() { None } else { Some(tags) }, + tags_match: parse_tags_match(&tags_match), tag_groups: None, }; @@ -312,6 +324,9 @@ pub fn reflect( context: Option, max_tokens: Option, schema_path: Option, + tags: Vec, + tags_match: Option, + include_facts: bool, verbose: bool, output_format: OutputFormat, ) -> Result<()> { @@ -332,15 +347,24 @@ pub fn reflect( None }; + let include = if include_facts { + Some(ReflectIncludeOptions { + facts: Some(FactsIncludeOptions(serde_json::Map::new())), + tool_calls: None, + }) + } else { + None + }; + let request = ReflectRequest { query, budget: Some(parse_budget(&budget)), context, max_tokens: max_tokens.unwrap_or(4096), - include: None, + include, response_schema, - tags: None, - tags_match: TagsMatch::Any, + tags: if tags.is_empty() { None } else { Some(tags) }, + tags_match: parse_tags_match(&tags_match), tag_groups: None, }; diff --git a/hindsight-cli/src/commands/mental_model.rs b/hindsight-cli/src/commands/mental_model.rs index 27a373fd..da5ca667 100644 --- a/hindsight-cli/src/commands/mental_model.rs +++ b/hindsight-cli/src/commands/mental_model.rs @@ -272,6 +272,55 @@ pub fn refresh( } } +/// Get the change history of a mental model +pub fn history( + client: &ApiClient, + bank_id: &str, + mental_model_id: &str, + verbose: bool, + output_format: OutputFormat, +) -> Result<()> { + let spinner = if output_format == OutputFormat::Pretty { + Some(ui::create_spinner("Fetching mental model history...")) + } else { + None + }; + + let response = client.get_mental_model_history(bank_id, mental_model_id, verbose); + + if let Some(mut sp) = spinner { + sp.finish(); + } + + match response { + Ok(history) => { + if output_format == OutputFormat::Pretty { + ui::print_section_header(&format!("History: {}", mental_model_id)); + + if let Some(entries) = history.as_array() { + if entries.is_empty() { + println!(" {}", ui::dim("No history entries found.")); + } else { + for entry in entries { + let changed_at = entry.get("changed_at").and_then(|v| v.as_str()).unwrap_or("unknown"); + let previous = entry.get("previous_content").and_then(|v| v.as_str()).unwrap_or("(none)"); + println!(" {} {}", ui::dim("Changed at:"), changed_at); + let preview: String = previous.chars().take(80).collect(); + let ellipsis = if previous.len() > 80 { "..." } else { "" }; + println!(" {} {}{}", ui::dim("Previous:"), ui::dim(&preview), ellipsis); + println!(); + } + } + } + } else { + output::print_output(&history, output_format)?; + } + Ok(()) + } + Err(e) => Err(e), + } +} + // Helper function to print mental model details fn print_mental_model_detail(mental_model: &types::MentalModelResponse) { ui::print_section_header(&mental_model.name); diff --git a/hindsight-cli/src/main.rs b/hindsight-cli/src/main.rs index 5d28213c..fe4f1a18 100644 --- a/hindsight-cli/src/main.rs +++ b/hindsight-cli/src/main.rs @@ -310,6 +310,34 @@ enum BankCommands { /// LLM base URL override #[arg(long)] llm_base_url: Option, + + /// Retain mission: what to focus on during fact extraction + #[arg(long)] + retain_mission: Option, + + /// Retain extraction mode (concise, verbose, custom) + #[arg(long)] + retain_extraction_mode: Option, + + /// Observations mission: what to synthesize into durable observations + #[arg(long)] + observations_mission: Option, + + /// Reflect mission: first-person identity for reflect operations + #[arg(long)] + reflect_mission: Option, + + /// Disposition skepticism trait (1-5) + #[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))] + disposition_skepticism: Option, + + /// Disposition literalism trait (1-5) + #[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))] + disposition_literalism: Option, + + /// Disposition empathy trait (1-5) + #[arg(long, value_parser = clap::value_parser!(i64).range(1..=5))] + disposition_empathy: Option, }, /// Reset bank configuration to defaults (remove all overrides) @@ -387,6 +415,14 @@ enum MemoryCommands { /// Maximum tokens for chunks (only used with --include-chunks) #[arg(long, default_value = "8192")] chunk_max_tokens: i64, + + /// Filter by tags (comma-separated, e.g. user:alice,team) + #[arg(long, value_delimiter = ',')] + tags: Vec, + + /// Tag matching mode: any, all, any_strict, all_strict (default: any) + #[arg(long)] + tags_match: Option, }, /// Generate answers using bank identity (reflect/reasoning) @@ -412,6 +448,18 @@ enum MemoryCommands { /// Path to JSON schema file for structured output #[arg(short = 's', long)] schema: Option, + + /// Filter by tags (comma-separated, e.g. user:alice,team) + #[arg(long, value_delimiter = ',')] + tags: Vec, + + /// Tag matching mode: any, all, any_strict, all_strict (default: any) + #[arg(long)] + tags_match: Option, + + /// Include source facts (based_on) in the response + #[arg(long)] + include_facts: bool, }, /// Store (retain) a single memory @@ -678,6 +726,15 @@ enum MentalModelCommands { /// Mental model ID mental_model_id: String, }, + + /// Get the change history of a mental model + History { + /// Bank ID + bank_id: String, + + /// Mental model ID + mental_model_id: String, + }, } #[derive(Subcommand)] @@ -724,6 +781,10 @@ enum DirectiveCommands { /// New content #[arg(long)] content: Option, + + /// Enable or disable the directive + #[arg(long)] + is_active: Option, }, /// Delete a directive @@ -821,8 +882,8 @@ fn run() -> Result<()> { BankCommands::Config { bank_id, overrides_only } => { commands::bank::config(&client, &bank_id, overrides_only, verbose, output_format) } - BankCommands::SetConfig { bank_id, llm_provider, llm_model, llm_api_key, llm_base_url } => { - commands::bank::set_config(&client, &bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, verbose, output_format) + BankCommands::SetConfig { bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, retain_mission, retain_extraction_mode, observations_mission, reflect_mission, disposition_skepticism, disposition_literalism, disposition_empathy } => { + commands::bank::set_config(&client, &bank_id, llm_provider, llm_model, llm_api_key, llm_base_url, retain_mission, retain_extraction_mode, observations_mission, reflect_mission, disposition_skepticism, disposition_literalism, disposition_empathy, verbose, output_format) } BankCommands::ResetConfig { bank_id, yes } => { commands::bank::reset_config(&client, &bank_id, yes, verbose, output_format) @@ -837,11 +898,11 @@ fn run() -> Result<()> { MemoryCommands::Get { bank_id, memory_id } => { commands::memory::get(&client, &bank_id, &memory_id, verbose, output_format) } - MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens } => { - commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, verbose, output_format) + MemoryCommands::Recall { bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, tags, tags_match } => { + commands::memory::recall(&client, &bank_id, query, fact_type, budget, max_tokens, trace, include_chunks, chunk_max_tokens, tags, tags_match, verbose, output_format) } - MemoryCommands::Reflect { bank_id, query, budget, context, max_tokens, schema } => { - commands::memory::reflect(&client, &bank_id, query, budget, context, max_tokens, schema, verbose, output_format) + MemoryCommands::Reflect { bank_id, query, budget, context, max_tokens, schema, tags, tags_match, include_facts } => { + commands::memory::reflect(&client, &bank_id, query, budget, context, max_tokens, schema, tags, tags_match, include_facts, verbose, output_format) } MemoryCommands::Retain { bank_id, content, doc_id, context, r#async } => { commands::memory::retain(&client, &bank_id, content, doc_id, context, r#async, verbose, output_format) @@ -930,6 +991,9 @@ fn run() -> Result<()> { MentalModelCommands::Refresh { bank_id, mental_model_id } => { commands::mental_model::refresh(&client, &bank_id, &mental_model_id, verbose, output_format) } + MentalModelCommands::History { bank_id, mental_model_id } => { + commands::mental_model::history(&client, &bank_id, &mental_model_id, verbose, output_format) + } }, // Directive commands @@ -943,8 +1007,8 @@ fn run() -> Result<()> { DirectiveCommands::Create { bank_id, name, content } => { commands::directive::create(&client, &bank_id, &name, &content, verbose, output_format) } - DirectiveCommands::Update { bank_id, directive_id, name, content } => { - commands::directive::update(&client, &bank_id, &directive_id, name, content, verbose, output_format) + DirectiveCommands::Update { bank_id, directive_id, name, content, is_active } => { + commands::directive::update(&client, &bank_id, &directive_id, name, content, is_active, verbose, output_format) } DirectiveCommands::Delete { bank_id, directive_id, yes } => { commands::directive::delete(&client, &bank_id, &directive_id, yes, verbose, output_format) diff --git a/hindsight-clients/python/hindsight_client/hindsight_client.py b/hindsight-clients/python/hindsight_client/hindsight_client.py index 9ff7828a..b4e4441f 100644 --- a/hindsight-clients/python/hindsight_client/hindsight_client.py +++ b/hindsight-clients/python/hindsight_client/hindsight_client.py @@ -792,6 +792,7 @@ class Hindsight: tags: list[str] | None = None, max_tokens: int | None = None, trigger: dict[str, Any] | None = None, + id: str | None = None, ): """ Create a mental model (runs reflect in background). @@ -803,6 +804,7 @@ class Hindsight: tags: Optional tags for filtering during retrieval max_tokens: Optional maximum tokens for the mental model content trigger: Optional trigger settings (e.g., {"refresh_after_consolidation": True}) + id: Optional custom ID for the mental model (alphanumeric lowercase with hyphens) Returns: CreateMentalModelResponse with operation_id @@ -814,6 +816,7 @@ class Hindsight: trigger_obj = mental_model_trigger.MentalModelTrigger(**trigger) request_obj = create_mental_model_request.CreateMentalModelRequest( + id=id, name=name, source_query=source_query, tags=tags, diff --git a/hindsight-clients/typescript/src/index.ts b/hindsight-clients/typescript/src/index.ts index 21817a31..01941fc9 100644 --- a/hindsight-clients/typescript/src/index.ts +++ b/hindsight-clients/typescript/src/index.ts @@ -628,6 +628,7 @@ export class HindsightClient { name: string, sourceQuery: string, options?: { + id?: string; tags?: string[]; maxTokens?: number; trigger?: { refreshAfterConsolidation?: boolean }; @@ -637,6 +638,7 @@ export class HindsightClient { client: this.client, path: { bank_id: bankId }, body: { + id: options?.id, name, source_query: sourceQuery, tags: options?.tags, @@ -726,6 +728,18 @@ export class HindsightClient { throw new Error(`deleteMentalModel failed: ${JSON.stringify(response.error)}`); } } + + /** + * Get the change history of a mental model. + */ + async getMentalModelHistory(bankId: string, mentalModelId: string): Promise { + const response = await sdk.getMentalModelHistory({ + client: this.client, + path: { bank_id: bankId, mental_model_id: mentalModelId }, + }); + + return this.validateResponse(response, 'getMentalModelHistory'); + } } // Re-export types for convenience diff --git a/hindsight-docs/docs/developer/api/documents.mdx b/hindsight-docs/docs/developer/api/documents.mdx index cbbc5a13..d73acad6 100644 --- a/hindsight-docs/docs/developer/api/documents.mdx +++ b/hindsight-docs/docs/developer/api/documents.mdx @@ -13,6 +13,7 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; {/* Import raw source files */} import documentsPy from '!!raw-loader!@site/examples/api/documents.py'; import documentsMjs from '!!raw-loader!@site/examples/api/documents.mjs'; +import documentsGo from '!!raw-loader!@site/examples/api/documents.go'; :::tip Prerequisites Make sure you've completed the [Quick Start](./quickstart) and understand [how retain works](./retain). @@ -60,6 +61,9 @@ hindsight memory retain my-bank "Meeting notes content..." --doc-id notes-2024-0 hindsight memory retain-files my-bank docs/ ``` + + + @@ -84,6 +88,9 @@ hindsight memory retain my-bank "Project deadline: March 31" --doc-id project-pl hindsight memory retain my-bank "Project deadline: April 15 (extended)" --doc-id project-plan ``` + + + @@ -104,6 +111,9 @@ Retrieve a document's original text and metadata. This is useful for expanding d hindsight document get my-bank meeting-2024-03-15 ``` + + + @@ -128,6 +138,9 @@ hindsight document update-tags my-bank meeting-2024-03-15 --tags team-a --tags t hindsight document update-tags my-bank meeting-2024-03-15 ``` + + + @@ -152,6 +165,9 @@ Remove a document and all its associated memories: hindsight document delete my-bank meeting-2024-03-15 ``` + + + @@ -183,6 +199,9 @@ hindsight document list my-bank --q report hindsight document list my-bank --tags team-a --tags team-b ``` + + + diff --git a/hindsight-docs/docs/developer/api/main-methods.mdx b/hindsight-docs/docs/developer/api/main-methods.mdx index b7d2d760..1cc3eb26 100644 --- a/hindsight-docs/docs/developer/api/main-methods.mdx +++ b/hindsight-docs/docs/developer/api/main-methods.mdx @@ -13,6 +13,7 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; {/* Import raw source files */} import mainMethodsPy from '!!raw-loader!@site/examples/api/main-methods.py'; import mainMethodsMjs from '!!raw-loader!@site/examples/api/main-methods.mjs'; +import mainMethodsGo from '!!raw-loader!@site/examples/api/main-methods.go'; :::tip Prerequisites Make sure you've [installed Hindsight](../installation) and completed the [Quick Start](./quickstart). @@ -33,15 +34,18 @@ Store conversations, documents, and facts into a memory bank. ```bash # Store a single fact -hindsight retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer" +hindsight memory retain my-bank "Alice joined Google in March 2024 as a Senior ML Engineer" # Store from a file -hindsight retain my-bank --file conversation.txt --context "Daily standup" +hindsight memory retain-files my-bank conversation.txt --context "Daily standup" # Store multiple files -hindsight retain my-bank --files docs/*.md +hindsight memory retain-files my-bank docs/ ``` + + + @@ -66,18 +70,21 @@ Search for relevant memories using multi-strategy retrieval. ```bash # Basic search -hindsight recall my-bank "What does Alice do at Google?" +hindsight memory recall my-bank "What does Alice do at Google?" # Search with options -hindsight recall my-bank "What happened last spring?" \ +hindsight memory recall my-bank "What happened last spring?" \ --budget high \ --max-tokens 8192 \ - --fact-type world + --fact-type world,experience -# Verbose output (shows weights and sources) -hindsight recall my-bank "Tell me about Alice" -v +# Verbose output +hindsight memory recall my-bank "Tell me about Alice" -v ``` + + + @@ -102,15 +109,15 @@ Generate disposition-aware responses using memories and observations. ```bash # Basic reflect -hindsight reflect my-bank "Should we adopt TypeScript for our backend?" - -# Verbose output (shows sources and observations) -hindsight reflect my-bank "What are Alice's strengths for the team lead role?" -v +hindsight memory reflect my-bank "Should we adopt TypeScript for our backend?" # With higher reasoning budget -hindsight reflect my-bank "Analyze our tech stack" --budget high +hindsight memory reflect my-bank "Analyze our tech stack" --budget high ``` + + + diff --git a/hindsight-docs/docs/developer/api/memory-banks.mdx b/hindsight-docs/docs/developer/api/memory-banks.mdx index 0ff99fca..17b85f55 100644 --- a/hindsight-docs/docs/developer/api/memory-banks.mdx +++ b/hindsight-docs/docs/developer/api/memory-banks.mdx @@ -13,8 +13,12 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; {/* Import raw source files */} import memoryBanksPy from '!!raw-loader!@site/examples/api/memory-banks.py'; import memoryBanksMjs from '!!raw-loader!@site/examples/api/memory-banks.mjs'; +import memoryBanksSh from '!!raw-loader!@site/examples/api/memory-banks.sh'; +import memoryBanksGo from '!!raw-loader!@site/examples/api/memory-banks.go'; import directivesPy from '!!raw-loader!@site/examples/api/directives.py'; import directivesMjs from '!!raw-loader!@site/examples/api/directives.mjs'; +import directivesSh from '!!raw-loader!@site/examples/api/directives.sh'; +import directivesGo from '!!raw-loader!@site/examples/api/directives.go'; ## What is a Memory Bank? @@ -44,11 +48,10 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client - -```bash -hindsight bank create my-bank -``` - + + + + @@ -205,6 +208,12 @@ How skeptical vs trusting the bank is when evaluating claims during `reflect`. S + + + + + + | Value | Behaviour | @@ -275,6 +284,12 @@ Bank configuration fields (retain mission, extraction mode, observations mission + + + + + + You can update any subset of fields — only the keys you provide are changed. @@ -288,6 +303,12 @@ You can update any subset of fields — only the keys you provide are changed. + + + + + + The response distinguishes: @@ -303,6 +324,12 @@ The response distinguishes: + + + + + + This removes all bank-level overrides. The bank reverts to server-wide defaults (set via environment variables). @@ -337,6 +364,12 @@ Use directives for rules that must never be violated: + + + + + + ### Listing Directives @@ -348,6 +381,12 @@ Use directives for rules that must never be violated: + + + + + + ### Updating Directives @@ -359,6 +398,12 @@ Use directives for rules that must never be violated: + + + + + + ### Deleting Directives @@ -370,6 +415,12 @@ Use directives for rules that must never be violated: + + + + + + ### Directives vs Disposition diff --git a/hindsight-docs/docs/developer/api/mental-models.mdx b/hindsight-docs/docs/developer/api/mental-models.mdx index 4a0b3ca2..389496d2 100644 --- a/hindsight-docs/docs/developer/api/mental-models.mdx +++ b/hindsight-docs/docs/developer/api/mental-models.mdx @@ -12,6 +12,9 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; {/* Import raw source files */} import mentalModelsPy from '!!raw-loader!@site/examples/api/mental-models.py'; +import mentalModelsMjs from '!!raw-loader!@site/examples/api/mental-models.mjs'; +import mentalModelsSh from '!!raw-loader!@site/examples/api/mental-models.sh'; +import mentalModelsGo from '!!raw-loader!@site/examples/api/mental-models.go'; ## What Are Mental Models? @@ -56,22 +59,14 @@ Creating a mental model runs a reflect operation in the background and saves the + + + - -```bash -# Create a mental model (async operation) -curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Team Communication Preferences", - "source_query": "How does the team prefer to communicate?", - "tags": ["team"] - }' - -# Response: {"operation_id": "op-123"} -# Use the operations endpoint to check completion -``` - + + + + @@ -81,12 +76,38 @@ curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \ |-----------|------|----------|-------------| | `name` | string | Yes | Human-readable name for the mental model | | `source_query` | string | Yes | The query to run to generate content | +| `id` | string | No | Custom ID for the mental model (alphanumeric lowercase with hyphens). Auto-generated if omitted. | | `tags` | list | No | Tags for filtering during retrieval | | `max_tokens` | int | No | Maximum tokens for the mental model content | | `trigger` | object | No | Trigger settings (see [Automatic Refresh](#automatic-refresh)) | --- +## Create with Custom ID + +Assign a stable, human-readable ID to a mental model so you can retrieve or update it by name instead of relying on the auto-generated UUID: + + + + + + + + + + + + + + + + +:::tip +Custom IDs must be lowercase alphanumeric and may contain hyphens (e.g. `team-policies`, `q4-status`). If a mental model with that ID already exists, the request is rejected. +::: + +--- + ## Automatic Refresh Mental models can be configured to **automatically refresh** when observations are updated. This keeps them in sync with the latest knowledge without manual intervention. @@ -103,19 +124,14 @@ When `refresh_after_consolidation` is enabled, the mental model will be re-gener + + + - -```bash -# Create a mental model with automatic refresh enabled -curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Project Status", - "source_query": "What is the current project status?", - "trigger": {"refresh_after_consolidation": true} - }' -``` - + + + + @@ -140,12 +156,14 @@ Enable automatic refresh for mental models that need to stay current. Disable it + + + - -```bash -curl "http://localhost:8888/v1/default/banks/my-bank/mental-models" -``` - + + + + @@ -157,12 +175,14 @@ curl "http://localhost:8888/v1/default/banks/my-bank/mental-models" + + + - -```bash -curl "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" -``` - + + + + @@ -190,12 +210,14 @@ Re-run the source query to update the mental model with current knowledge: + + + - -```bash -curl -X POST "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}/refresh" -``` - + + + + @@ -214,14 +236,14 @@ Update the mental model's name: + + + - -```bash -curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" \ - -H "Content-Type: application/json" \ - -d '{"name": "Updated Team Communication Preferences"}' -``` - + + + + @@ -233,12 +255,14 @@ curl -X PATCH "http://localhost:8888/v1/default/banks/my-bank/mental-models/{men + + + - -```bash -curl -X DELETE "http://localhost:8888/v1/default/banks/my-bank/mental-models/{mental_model_id}" -``` - + + + + @@ -280,6 +304,15 @@ Every time a mental model's content changes (via refresh or manual update), the + + + + + + + + + ### Response diff --git a/hindsight-docs/docs/developer/api/quickstart.mdx b/hindsight-docs/docs/developer/api/quickstart.mdx index 2f6bab98..47fe2bc0 100644 --- a/hindsight-docs/docs/developer/api/quickstart.mdx +++ b/hindsight-docs/docs/developer/api/quickstart.mdx @@ -15,6 +15,7 @@ import {ClientsGrid, IntegrationsGrid} from '@site/src/components/SupportedGrids import quickstartPy from '!!raw-loader!@site/examples/api/quickstart.py'; import quickstartMjs from '!!raw-loader!@site/examples/api/quickstart.mjs'; import quickstartSh from '!!raw-loader!@site/examples/api/quickstart.sh'; +import quickstartGo from '!!raw-loader!@site/examples/api/quickstart.go'; ## Clients @@ -90,6 +91,15 @@ curl -fsSL https://hindsight.vectorize.io/get-cli | bash + + + +```bash +go get github.com/vectorize-io/hindsight/hindsight-clients/go +``` + + + diff --git a/hindsight-docs/docs/developer/api/recall.mdx b/hindsight-docs/docs/developer/api/recall.mdx index 8fee9e41..00ae6b42 100644 --- a/hindsight-docs/docs/developer/api/recall.mdx +++ b/hindsight-docs/docs/developer/api/recall.mdx @@ -16,6 +16,7 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; import recallPy from '!!raw-loader!@site/examples/api/recall.py'; import recallMjs from '!!raw-loader!@site/examples/api/recall.mjs'; import recallSh from '!!raw-loader!@site/examples/api/recall.sh'; +import recallGo from '!!raw-loader!@site/examples/api/recall.go'; :::info How Recall Works Learn about the four retrieval strategies (semantic, keyword, graph, temporal) and RRF fusion in the [Recall Architecture](/developer/retrieval) guide. @@ -37,6 +38,9 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client + + + --- @@ -59,9 +63,19 @@ Each type runs the full four-strategy retrieval pipeline independently, so narro + + + + + + + + + + :::tip About Observations @@ -79,6 +93,12 @@ Controls retrieval depth and breadth. Accepted values are `low`, `mid` (default) + + + + + + ### max_tokens @@ -89,6 +109,15 @@ The maximum number of tokens the returned facts can collectively occupy. Default + + + + + + + + + ### query_timestamp @@ -118,6 +147,12 @@ When enabled and `types` includes `observation`, each observation result is acco + + + + + + #### entities @@ -152,7 +187,20 @@ Consider a bank with these four memories: Returns memories that have **at least one** matching tag, plus untagged memories. + + + + + + + + + + + + + Use this for **shared global knowledge + user-specific** patterns, where untagged memories represent information everyone should see. @@ -160,7 +208,20 @@ Use this for **shared global knowledge + user-specific** patterns, where untagge Same as `any` but untagged memories are excluded. + + + + + + + + + + + + + Use this when memories are **fully partitioned by tags** and untagged memories should never be visible. @@ -168,7 +229,20 @@ Use this when memories are **fully partitioned by tags** and untagged memories s Returns memories that have **every** specified tag, plus untagged memories. + + + + + + + + + + + + + Use this when memories must belong to a **specific intersection** of scopes (e.g., only memories relevant to both a user and a project), while still surfacing shared global knowledge. @@ -176,7 +250,20 @@ Use this when memories must belong to a **specific intersection** of scopes (e.g Returns memories that have **every** specified tag, and excludes untagged memories. + + + + + + + + + + + + + Use this for strict scope enforcement where a memory must explicitly belong to **all** specified contexts. diff --git a/hindsight-docs/docs/developer/api/reflect.mdx b/hindsight-docs/docs/developer/api/reflect.mdx index 079d2997..14e99c6b 100644 --- a/hindsight-docs/docs/developer/api/reflect.mdx +++ b/hindsight-docs/docs/developer/api/reflect.mdx @@ -16,6 +16,7 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; import reflectPy from '!!raw-loader!@site/examples/api/reflect.py'; import reflectMjs from '!!raw-loader!@site/examples/api/reflect.mjs'; import reflectSh from '!!raw-loader!@site/examples/api/reflect.sh'; +import reflectGo from '!!raw-loader!@site/examples/api/reflect.go'; :::info How Reflect Works Learn about disposition-driven reasoning in the [Reflect Architecture](/developer/reflect) guide. @@ -37,6 +38,9 @@ Make sure you've completed the [Quick Start](./quickstart) to install the client + + + --- @@ -58,6 +62,12 @@ Controls how thoroughly the agent explores the memory bank before answering. Acc + + + + + + ### max_tokens @@ -78,6 +88,9 @@ An optional JSON Schema object. When provided, the LLM generates a response that + + + ### tags @@ -88,6 +101,15 @@ Filters which memories the agent can access during reflection. Works identically + + + + + + + + + ### include @@ -102,6 +124,15 @@ When enabled, the response includes a `based_on` object listing the memories, me + + + + + + + + + #### include.tool_calls diff --git a/hindsight-docs/docs/developer/api/retain.mdx b/hindsight-docs/docs/developer/api/retain.mdx index bd0df1e9..78bb2d93 100644 --- a/hindsight-docs/docs/developer/api/retain.mdx +++ b/hindsight-docs/docs/developer/api/retain.mdx @@ -16,6 +16,7 @@ import CodeSnippet from '@site/src/components/CodeSnippet'; import retainPy from '!!raw-loader!@site/examples/api/retain.py'; import retainMjs from '!!raw-loader!@site/examples/api/retain.mjs'; import retainSh from '!!raw-loader!@site/examples/api/retain.sh'; +import retainGo from '!!raw-loader!@site/examples/api/retain.go'; :::info How Retain Works Learn about fact extraction, entity resolution, and graph construction in the [Retain Architecture](/developer/retain) guide. @@ -39,6 +40,9 @@ A single retain call accepts one or more **items**. Each item is a piece of raw + + + ### Retaining a Conversation @@ -52,6 +56,12 @@ A full conversation should be retained as a single item. The LLM can parse any f + + + + + + When the conversation grows — a new message arrives — just retain again with the full updated content and the same `document_id`. Hindsight will delete the previous version and reprocess from scratch, so memories always reflect the latest state of the conversation. @@ -92,6 +102,9 @@ Providing context consistently is one of the highest-leverage things you can do + + + ### metadata @@ -203,6 +216,12 @@ Multiple items can be submitted in a single request. Batch ingestion is the reco + + + + + + @@ -215,18 +234,18 @@ Upload files directly — Hindsight converts them to text and extracts memories **Supported formats:** PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, images (JPG, PNG, GIF, etc. — OCR), audio (MP3, WAV, FLAC, etc. — transcription), HTML, and plain text formats (TXT, MD, CSV, JSON, YAML, etc.) - - - - - - + + + + + + The file retain endpoint always returns asynchronously. The response contains `operation_ids` — one per uploaded file — which you can poll via `GET /v1/default/banks/{bank_id}/operations` to track progress. @@ -237,6 +256,15 @@ Upload up to 10 files per request (max 100 MB total). Each file becomes a separa + + + + + + + + + :::info File Storage @@ -256,6 +284,12 @@ For large batches, use async ingestion to avoid blocking your application: + + + + + + When `async: true`, the call returns immediately with an `operation_id`. Processing runs in the background via the worker service. No `usage` metrics are returned for async operations. diff --git a/hindsight-docs/examples/api/directives.go b/hindsight-docs/examples/api/directives.go new file mode 100644 index 00000000..e112ad39 --- /dev/null +++ b/hindsight-docs/examples/api/directives.go @@ -0,0 +1,86 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +const bankID = "directives-example-bank" + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Setup (not shown in docs) + // ============================================================================= + client.BanksAPI.CreateOrUpdateBank(ctx, bankID). + CreateBankRequest(hindsight.CreateBankRequest{ + Name: *hindsight.NewNullableString(hindsight.PtrString("Test Bank")), + }).Execute() + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:create-directive] + // Create a directive (hard rule for reflect) + directive, _, _ := client.DirectivesAPI.CreateDirective(ctx, bankID). + CreateDirectiveRequest(hindsight.CreateDirectiveRequest{ + Name: "Formal Language", + Content: "Always respond in formal English, avoiding slang and colloquialisms.", + }).Execute() + + fmt.Printf("Created directive: %s\n", directive.GetId()) + // [/docs:create-directive] + + directiveID := directive.GetId() + + // [docs:list-directives] + // List all directives in a bank + directives, _, _ := client.DirectivesAPI.ListDirectives(ctx, bankID).Execute() + + for _, d := range directives.GetItems() { + content := d.GetContent() + if len(content) > 50 { + content = content[:50] + } + fmt.Printf("- %s: %s...\n", d.GetName(), content) + } + // [/docs:list-directives] + + // [docs:update-directive] + // Update a directive (e.g., disable without deleting) + isActiveFalse := false + updated, _, _ := client.DirectivesAPI.UpdateDirective(ctx, bankID, directiveID). + UpdateDirectiveRequest(hindsight.UpdateDirectiveRequest{ + IsActive: *hindsight.NewNullableBool(&isActiveFalse), + }).Execute() + + fmt.Printf("Directive active: %v\n", updated.GetIsActive()) + // [/docs:update-directive] + + // [docs:delete-directive] + // Delete a directive + client.DirectivesAPI.DeleteDirective(ctx, bankID, directiveID).Execute() + // [/docs:delete-directive] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil) + http.DefaultClient.Do(req) + + fmt.Println("directives.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/directives.sh b/hindsight-docs/examples/api/directives.sh new file mode 100644 index 00000000..c92c1b1c --- /dev/null +++ b/hindsight-docs/examples/api/directives.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Directives API examples for Hindsight CLI +# Run: bash examples/api/directives.sh + +set -e + +HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}" +BANK_ID="directives-example-bank" + +# ============================================================================= +# Setup (not shown in docs) +# ============================================================================= +hindsight bank create "$BANK_ID" --name "Test Bank" + +# ============================================================================= +# Doc Examples +# ============================================================================= + +# [docs:create-directive] +# Create a directive (hard rule for reflect) +hindsight directive create "$BANK_ID" \ + "Formal Language" \ + "Always respond in formal English, avoiding slang and colloquialisms." +# [/docs:create-directive] + +# Get the directive ID for subsequent operations +DIRECTIVE_ID=$(hindsight directive list "$BANK_ID" -o json | python3 -c "import sys,json; items=json.load(sys.stdin).get('items',[]); print(items[0]['id'] if items else '')" 2>/dev/null || echo "") + +# [docs:list-directives] +# List all directives in a bank +hindsight directive list "$BANK_ID" +# [/docs:list-directives] + +if [ -n "$DIRECTIVE_ID" ]; then + # [docs:update-directive] + # Update a directive (e.g., disable without deleting) + hindsight directive update "$BANK_ID" "$DIRECTIVE_ID" --is-active false + # [/docs:update-directive] + + # [docs:delete-directive] + # Delete a directive + hindsight directive delete "$BANK_ID" "$DIRECTIVE_ID" -y + # [/docs:delete-directive] +fi + +# ============================================================================= +# Cleanup (not shown in docs) +# ============================================================================= +hindsight bank delete "$BANK_ID" -y + +echo "directives.sh: All examples passed" diff --git a/hindsight-docs/examples/api/documents.go b/hindsight-docs/examples/api/documents.go new file mode 100644 index 00000000..442fdf94 --- /dev/null +++ b/hindsight-docs/examples/api/documents.go @@ -0,0 +1,94 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // [docs:document-retain] + // Retain with document ID + docID := "meeting-2024-03-15" + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + { + Content: "Alice presented the Q4 roadmap...", + DocumentId: *hindsight.NewNullableString(&docID), + }, + }, + }).Execute() + // [/docs:document-retain] + + // [docs:document-update] + // Original + planDoc := "project-plan" + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + { + Content: "Project deadline: March 31", + DocumentId: *hindsight.NewNullableString(&planDoc), + }, + }, + }).Execute() + + // Update (deletes old facts, creates new ones) + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + { + Content: "Project deadline: April 15 (extended)", + DocumentId: *hindsight.NewNullableString(&planDoc), + }, + }, + }).Execute() + // [/docs:document-update] + + // [docs:document-get] + doc, _, err := client.DocumentsAPI.GetDocument(ctx, "my-bank", "meeting-2024-03-15").Execute() + if err != nil { + log.Fatalf("Failed to get document: %v", err) + } + fmt.Printf("Document ID: %s\n", doc.GetId()) + fmt.Printf("Memory units: %d\n", doc.GetMemoryUnitCount()) + // [/docs:document-get] + + // [docs:document-delete] + client.DocumentsAPI.DeleteDocument(ctx, "my-bank", "meeting-2024-03-15").Execute() + // [/docs:document-delete] + + // [docs:document-list] + // List all documents + docs, _, err := client.DocumentsAPI.ListDocuments(ctx, "my-bank").Execute() + if err != nil { + log.Fatalf("Failed to list documents: %v", err) + } + for _, d := range docs.Items { + id, _ := d["id"].(string) + memCount, _ := d["memory_unit_count"].(float64) + fmt.Printf("%s: %d memories\n", id, int(memCount)) + } + // [/docs:document-list] + + // Cleanup (not shown in docs) + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil) + http.DefaultClient.Do(req) + + fmt.Println("documents.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/main-methods.go b/hindsight-docs/examples/api/main-methods.go new file mode 100644 index 00000000..00442afd --- /dev/null +++ b/hindsight-docs/examples/api/main-methods.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // [docs:main-retain] + // Store a fact or conversation into a memory bank + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + {Content: "Alice joined Google in March 2024 as a Senior ML Engineer"}, + }, + }).Execute() + // [/docs:main-retain] + + // [docs:main-recall] + // Search for memories using a natural language query + resp, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What does Alice do at Google?", + }).Execute() + + for _, r := range resp.Results { + fmt.Println(r.Text) + } + // [/docs:main-recall] + + // [docs:main-reflect] + // Generate a reasoned response using memories and bank disposition + answer, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "Should we adopt TypeScript for our backend?", + }).Execute() + + fmt.Println(answer.GetText()) + // [/docs:main-reflect] + + // Cleanup (not shown in docs) + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil) + http.DefaultClient.Do(req) + + fmt.Println("main-methods.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/memory-banks.go b/hindsight-docs/examples/api/memory-banks.go new file mode 100644 index 00000000..9150eaaf --- /dev/null +++ b/hindsight-docs/examples/api/memory-banks.go @@ -0,0 +1,114 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:create-bank] + client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank"). + CreateBankRequest(hindsight.CreateBankRequest{}).Execute() + // [/docs:create-bank] + + // [docs:bank-with-disposition] + client.BanksAPI.CreateOrUpdateBank(ctx, "architect-bank"). + CreateBankRequest(hindsight.CreateBankRequest{ + ReflectMission: *hindsight.NewNullableString(hindsight.PtrString( + "You're a senior software architect - keep track of system designs, " + + "technology decisions, and architectural patterns. Prefer simplicity over cutting-edge.", + )), + DispositionSkepticism: *hindsight.NewNullableInt32(hindsight.PtrInt32(4)), + DispositionLiteralism: *hindsight.NewNullableInt32(hindsight.PtrInt32(4)), + DispositionEmpathy: *hindsight.NewNullableInt32(hindsight.PtrInt32(2)), + }).Execute() + // [/docs:bank-with-disposition] + + // [docs:bank-background] + client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank"). + CreateBankRequest(hindsight.CreateBankRequest{ + ReflectMission: *hindsight.NewNullableString(hindsight.PtrString( + "I am a research assistant specializing in machine learning.", + )), + }).Execute() + // [/docs:bank-background] + + // [docs:bank-mission] + client.BanksAPI.CreateOrUpdateBank(ctx, "my-bank"). + CreateBankRequest(hindsight.CreateBankRequest{ + ReflectMission: *hindsight.NewNullableString(hindsight.PtrString( + "You're a senior software architect - keep track of system designs, " + + "technology decisions, and architectural patterns.", + )), + }).Execute() + // [/docs:bank-mission] + + // [docs:bank-support-agent] + client.BanksAPI.CreateOrUpdateBank(ctx, "support-bank"). + CreateBankRequest(hindsight.CreateBankRequest{}).Execute() + client.BanksAPI.UpdateBankConfig(ctx, "support-bank"). + BankConfigUpdate(hindsight.BankConfigUpdate{ + Updates: map[string]interface{}{ + "observations_mission": "I am a customer support agent. Track customer preferences, " + + "recurring issues, and resolution history to provide consistent, personalized support.", + }, + }).Execute() + // [/docs:bank-support-agent] + + // [docs:update-bank-config] + client.BanksAPI.UpdateBankConfig(ctx, "my-bank"). + BankConfigUpdate(hindsight.BankConfigUpdate{ + Updates: map[string]interface{}{ + "retain_mission": "Always include technical decisions, API design choices, and architectural trade-offs. " + + "Ignore meeting logistics and social exchanges.", + "retain_extraction_mode": "verbose", + "observations_mission": "Observations are stable facts about people and projects. " + + "Always include preferences, skills, and recurring patterns. Ignore one-off events.", + "disposition_skepticism": 4, + "disposition_literalism": 4, + "disposition_empathy": 2, + }, + }).Execute() + // [/docs:update-bank-config] + + // [docs:get-bank-config] + // Returns resolved config (server defaults merged with bank overrides) and the raw overrides + result, _, _ := client.BanksAPI.GetBankConfig(ctx, "my-bank").Execute() + // result.Config — full resolved configuration + // result.Overrides — only fields overridden at the bank level + fmt.Println("Config keys:", len(result.GetConfig())) + // [/docs:get-bank-config] + + // [docs:reset-bank-config] + // Remove all bank-level overrides, reverting to server defaults + client.BanksAPI.ResetBankConfig(ctx, "my-bank").Execute() + // [/docs:reset-bank-config] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + for _, bankID := range []string{"my-bank", "architect-bank", "support-bank"} { + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil) + http.DefaultClient.Do(req) + } + + fmt.Println("memory-banks.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/memory-banks.sh b/hindsight-docs/examples/api/memory-banks.sh new file mode 100644 index 00000000..65412c36 --- /dev/null +++ b/hindsight-docs/examples/api/memory-banks.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Memory Banks API examples for Hindsight CLI +# Run: bash examples/api/memory-banks.sh + +set -e + +HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}" + +# ============================================================================= +# Doc Examples +# ============================================================================= + +# [docs:create-bank] +hindsight bank create my-bank +# [/docs:create-bank] + +# [docs:bank-with-disposition] +hindsight bank create architect-bank \ + --mission "You're a senior software architect - keep track of system designs, technology decisions, and architectural patterns. Prefer simplicity over cutting-edge." \ + --skepticism 4 \ + --literalism 4 \ + --empathy 2 +# [/docs:bank-with-disposition] + +# [docs:bank-background] +hindsight bank create my-bank \ + --mission "I am a research assistant specializing in machine learning." +# [/docs:bank-background] + +# [docs:bank-mission] +hindsight bank create my-bank \ + --mission "You're a senior software architect - keep track of system designs, technology decisions, and architectural patterns." +# [/docs:bank-mission] + +# [docs:bank-support-agent] +hindsight bank create support-bank +hindsight bank set-config support-bank \ + --observations-mission "I am a customer support agent. Track customer preferences, recurring issues, and resolution history." +# [/docs:bank-support-agent] + +# [docs:update-bank-config] +hindsight bank set-config my-bank \ + --retain-mission "Always include technical decisions, API design choices, and architectural trade-offs. Ignore meeting logistics and social exchanges." \ + --retain-extraction-mode verbose \ + --observations-mission "Observations are stable facts about people and projects. Always include preferences, skills, and recurring patterns. Ignore one-off events." \ + --disposition-skepticism 4 \ + --disposition-literalism 4 \ + --disposition-empathy 2 +# [/docs:update-bank-config] + +# [docs:get-bank-config] +# Returns resolved config (server defaults merged with bank overrides) +hindsight bank config my-bank + +# Show only bank-specific overrides +hindsight bank config my-bank --overrides-only +# [/docs:get-bank-config] + +# [docs:reset-bank-config] +# Remove all bank-level overrides, reverting to server defaults +hindsight bank reset-config my-bank -y +# [/docs:reset-bank-config] + +# ============================================================================= +# Cleanup (not shown in docs) +# ============================================================================= +for bank_id in my-bank architect-bank support-bank; do + curl -s -X DELETE "${HINDSIGHT_URL}/v1/default/banks/${bank_id}" > /dev/null +done + +echo "memory-banks.sh: All examples passed" diff --git a/hindsight-docs/examples/api/mental-models.go b/hindsight-docs/examples/api/mental-models.go new file mode 100644 index 00000000..6b3158bb --- /dev/null +++ b/hindsight-docs/examples/api/mental-models.go @@ -0,0 +1,173 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +const mmBankID = "mental-models-demo-bank" + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Setup (not shown in docs) + // ============================================================================= + client.BanksAPI.CreateOrUpdateBank(ctx, mmBankID). + CreateBankRequest(hindsight.CreateBankRequest{ + Name: *hindsight.NewNullableString(hindsight.PtrString("Mental Models Demo")), + }).Execute() + for _, content := range []string{ + "The team prefers async communication via Slack", + "For urgent issues, use the #incidents channel", + "Weekly syncs happen every Monday at 10am", + } { + client.MemoryAPI.RetainMemories(ctx, mmBankID). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{{Content: content}}, + }).Execute() + } + time.Sleep(2 * time.Second) + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:create-mental-model] + // Create a mental model (runs reflect in background) + result, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID). + CreateMentalModelRequest(hindsight.CreateMentalModelRequest{ + Name: "Team Communication Preferences", + SourceQuery: "How does the team prefer to communicate?", + Tags: []string{"team", "communication"}, + }).Execute() + + // Returns an operation_id — check operations endpoint for completion + fmt.Printf("Operation ID: %s\n", result.GetOperationId()) + // [/docs:create-mental-model] + + // [docs:create-mental-model-with-id] + // Create a mental model with a specific custom ID + mmID := "communication-policy" + resultWithID, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID). + CreateMentalModelRequest(hindsight.CreateMentalModelRequest{ + Id: *hindsight.NewNullableString(&mmID), + Name: "Communication Policy", + SourceQuery: "What are the team's communication guidelines?", + }).Execute() + + fmt.Printf("Created with custom ID: %s\n", resultWithID.GetOperationId()) + // [/docs:create-mental-model-with-id] + + time.Sleep(5 * time.Second) + + // [docs:create-mental-model-with-trigger] + // Create a mental model with automatic refresh enabled + refreshTrue := true + result2, _, _ := client.MentalModelsAPI.CreateMentalModel(ctx, mmBankID). + CreateMentalModelRequest(hindsight.CreateMentalModelRequest{ + Name: "Project Status", + SourceQuery: "What is the current project status?", + Trigger: &hindsight.MentalModelTrigger{ + RefreshAfterConsolidation: &refreshTrue, + }, + }).Execute() + + // This mental model will automatically refresh when observations are updated + fmt.Printf("Operation ID: %s\n", result2.GetOperationId()) + // [/docs:create-mental-model-with-trigger] + + time.Sleep(5 * time.Second) + + // [docs:list-mental-models] + // List all mental models in a bank + mentalModels, _, _ := client.MentalModelsAPI.ListMentalModels(ctx, mmBankID).Execute() + + for _, mm := range mentalModels.GetItems() { + fmt.Printf("- %s: %s\n", mm.GetName(), mm.GetSourceQuery()) + } + // [/docs:list-mental-models] + + if len(mentalModels.GetItems()) == 0 { + fmt.Println("mental-models.go: All examples passed (no mental models created yet)") + cleanupMentalModels(client, ctx, apiURL) + return + } + + mentalModelID := mentalModels.GetItems()[0].GetId() + + // [docs:get-mental-model] + // Get a specific mental model + mentalModel, _, _ := client.MentalModelsAPI.GetMentalModel(ctx, mmBankID, mentalModelID).Execute() + + fmt.Printf("Name: %s\n", mentalModel.GetName()) + fmt.Printf("Content: %s\n", mentalModel.GetContent()) + fmt.Printf("Last refreshed: %s\n", mentalModel.GetLastRefreshedAt()) + // [/docs:get-mental-model] + + // [docs:refresh-mental-model] + // Refresh a mental model to update with current knowledge + refreshResult, _, _ := client.MentalModelsAPI.RefreshMentalModel(ctx, mmBankID, mentalModelID).Execute() + + fmt.Printf("Refresh operation ID: %s\n", refreshResult.GetOperationId()) + // [/docs:refresh-mental-model] + + // [docs:update-mental-model] + // Update a mental model's metadata + newName := "Updated Team Communication Preferences" + refreshAfter := true + updated, _, _ := client.MentalModelsAPI.UpdateMentalModel(ctx, mmBankID, mentalModelID). + UpdateMentalModelRequest(hindsight.UpdateMentalModelRequest{ + Name: *hindsight.NewNullableString(&newName), + Trigger: *hindsight.NewNullableMentalModelTrigger(&hindsight.MentalModelTrigger{ + RefreshAfterConsolidation: &refreshAfter, + }), + }).Execute() + + fmt.Printf("Updated name: %s\n", updated.GetName()) + // [/docs:update-mental-model] + + // [docs:get-mental-model-history] + // Get the change history of a mental model + history, _, _ := client.MentalModelsAPI.GetMentalModelHistory(ctx, mmBankID, mentalModelID).Execute() + + if entries, ok := history.([]interface{}); ok { + for _, entry := range entries { + if e, ok := entry.(map[string]interface{}); ok { + fmt.Printf("Changed at: %v\n", e["changed_at"]) + fmt.Printf("Previous content: %v\n", e["previous_content"]) + } + } + } + // [/docs:get-mental-model-history] + + // [docs:delete-mental-model] + // Delete a mental model + client.MentalModelsAPI.DeleteMentalModel(ctx, mmBankID, mentalModelID).Execute() + // [/docs:delete-mental-model] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + cleanupMentalModels(client, ctx, apiURL) + + fmt.Println("mental-models.go: All examples passed") +} + +func cleanupMentalModels(client *hindsight.APIClient, ctx context.Context, apiURL string) { + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, mmBankID), nil) + http.DefaultClient.Do(req) +} diff --git a/hindsight-docs/examples/api/mental-models.mjs b/hindsight-docs/examples/api/mental-models.mjs new file mode 100644 index 00000000..16979b01 --- /dev/null +++ b/hindsight-docs/examples/api/mental-models.mjs @@ -0,0 +1,129 @@ +#!/usr/bin/env node +/** + * Mental Models API examples for Hindsight (Node.js) + * Run: node examples/api/mental-models.mjs + */ +import { HindsightClient } from '@vectorize-io/hindsight-client'; + +const HINDSIGHT_URL = process.env.HINDSIGHT_API_URL || 'http://localhost:8888'; +const BANK_ID = 'mental-models-demo-bank'; + +// ============================================================================= +// Setup (not shown in docs) +// ============================================================================= +const client = new HindsightClient({ baseUrl: HINDSIGHT_URL }); +await client.createBank(BANK_ID, { name: 'Mental Models Demo' }); +await client.retain(BANK_ID, 'The team prefers async communication via Slack'); +await client.retain(BANK_ID, 'For urgent issues, use the #incidents channel'); +await client.retain(BANK_ID, 'Weekly syncs happen every Monday at 10am'); +await new Promise(r => setTimeout(r, 2000)); + +// ============================================================================= +// Doc Examples +// ============================================================================= + +// [docs:create-mental-model] +// Create a mental model (runs reflect in background) +const result = await client.createMentalModel( + BANK_ID, + 'Team Communication Preferences', + 'How does the team prefer to communicate?', + { tags: ['team', 'communication'] }, +); + +// Returns an operation_id — check operations endpoint for completion +console.log(`Operation ID: ${result.operation_id}`); +// [/docs:create-mental-model] + +// [docs:create-mental-model-with-id] +// Create a mental model with a specific custom ID +const resultWithId = await client.createMentalModel( + BANK_ID, + 'Communication Policy', + "What are the team's communication guidelines?", + { id: 'communication-policy' }, +); + +console.log(`Created with custom ID: ${resultWithId.operation_id}`); +// [/docs:create-mental-model-with-id] + +await new Promise(r => setTimeout(r, 5000)); + +// [docs:create-mental-model-with-trigger] +// Create a mental model with automatic refresh enabled +const result2 = await client.createMentalModel( + BANK_ID, + 'Project Status', + 'What is the current project status?', + { trigger: { refreshAfterConsolidation: true } }, +); + +// This mental model will automatically refresh when observations are updated +console.log(`Operation ID: ${result2.operation_id}`); +// [/docs:create-mental-model-with-trigger] + +await new Promise(r => setTimeout(r, 5000)); + +// [docs:list-mental-models] +// List all mental models in a bank +const mentalModels = await client.listMentalModels(BANK_ID); + +for (const mm of mentalModels.items) { + console.log(`- ${mm.name}: ${mm.source_query}`); +} +// [/docs:list-mental-models] + +const mentalModelId = mentalModels.items[0]?.id; +if (!mentalModelId) { + console.log('mental-models.mjs: All examples passed (no mental models created yet)'); + await fetch(`${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}`, { method: 'DELETE' }); + process.exit(0); +} + +// [docs:get-mental-model] +// Get a specific mental model +const mentalModel = await client.getMentalModel(BANK_ID, mentalModelId); + +console.log(`Name: ${mentalModel.name}`); +console.log(`Content: ${mentalModel.content}`); +console.log(`Last refreshed: ${mentalModel.last_refreshed_at}`); +// [/docs:get-mental-model] + +// [docs:refresh-mental-model] +// Refresh a mental model to update with current knowledge +const refreshResult = await client.refreshMentalModel(BANK_ID, mentalModelId); + +console.log(`Refresh operation ID: ${refreshResult.operation_id}`); +// [/docs:refresh-mental-model] + +// [docs:update-mental-model] +// Update a mental model's metadata +const updated = await client.updateMentalModel(BANK_ID, mentalModelId, { + name: 'Updated Team Communication Preferences', + trigger: { refresh_after_consolidation: true }, +}); + +console.log(`Updated name: ${updated.name}`); +// [/docs:update-mental-model] + +// [docs:get-mental-model-history] +// Get the change history of a mental model +const history = await client.getMentalModelHistory(BANK_ID, mentalModelId); + +for (const entry of history) { + console.log(`Changed at: ${entry.changed_at}`); + console.log(`Previous content: ${entry.previous_content}`); +} +// [/docs:get-mental-model-history] + +// [docs:delete-mental-model] +// Delete a mental model +await client.deleteMentalModel(BANK_ID, mentalModelId); +// [/docs:delete-mental-model] + +// ============================================================================= +// Cleanup (not shown in docs) +// ============================================================================= +await client.deleteBank(BANK_ID); + +console.log('mental-models.mjs: All examples passed'); diff --git a/hindsight-docs/examples/api/mental-models.py b/hindsight-docs/examples/api/mental-models.py index 4f850563..66fc8feb 100644 --- a/hindsight-docs/examples/api/mental-models.py +++ b/hindsight-docs/examples/api/mental-models.py @@ -42,6 +42,18 @@ result = client.create_mental_model( print(f"Operation ID: {result.operation_id}") # [/docs:create-mental-model] +# [docs:create-mental-model-with-id] +# Create a mental model with a specific custom ID +result_with_id = client.create_mental_model( + bank_id=BANK_ID, + name="Communication Policy", + source_query="What are the team's communication guidelines?", + id="communication-policy" +) + +print(f"Created with custom ID: {result_with_id.operation_id}") +# [/docs:create-mental-model-with-id] + # Wait for the mental model to be created time.sleep(5) diff --git a/hindsight-docs/examples/api/mental-models.sh b/hindsight-docs/examples/api/mental-models.sh new file mode 100644 index 00000000..287f9bf1 --- /dev/null +++ b/hindsight-docs/examples/api/mental-models.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Mental Models API examples for Hindsight CLI +# Run: bash examples/api/mental-models.sh + +set -e + +HINDSIGHT_URL="${HINDSIGHT_API_URL:-http://localhost:8888}" +BANK_ID="mental-models-demo-bank" + +# ============================================================================= +# Setup (not shown in docs) +# ============================================================================= +hindsight bank create "$BANK_ID" --name "Mental Models Demo" +hindsight memory retain "$BANK_ID" "The team prefers async communication via Slack" +hindsight memory retain "$BANK_ID" "For urgent issues, use the #incidents channel" +hindsight memory retain "$BANK_ID" "Weekly syncs happen every Monday at 10am" +sleep 2 + +# ============================================================================= +# Doc Examples +# ============================================================================= + +# [docs:create-mental-model] +# Create a mental model (runs reflect in background) +hindsight mental-model create "$BANK_ID" \ + "Team Communication Preferences" \ + "How does the team prefer to communicate?" +# [/docs:create-mental-model] + +# [docs:create-mental-model-with-id] +# Create a mental model with a specific custom ID +hindsight mental-model create "$BANK_ID" \ + "Communication Policy" \ + "What are the team's communication guidelines?" \ + --id communication-policy +# [/docs:create-mental-model-with-id] + +sleep 5 + +# [docs:create-mental-model-with-trigger] +# Create a mental model and get its ID for subsequent operations +hindsight mental-model create "$BANK_ID" \ + "Project Status" \ + "What is the current project status?" +# [/docs:create-mental-model-with-trigger] + +sleep 5 + +# [docs:list-mental-models] +# List all mental models in a bank +hindsight mental-model list "$BANK_ID" +# [/docs:list-mental-models] + +# Get the first mental model ID for subsequent examples +MENTAL_MODEL_ID=$(hindsight mental-model list "$BANK_ID" -o json | python3 -c "import sys,json; items=json.load(sys.stdin).get('items',[]); print(items[0]['id'] if items else '')" 2>/dev/null || echo "") + +if [ -n "$MENTAL_MODEL_ID" ]; then + # [docs:get-mental-model] + # Get a specific mental model + hindsight mental-model get "$BANK_ID" "$MENTAL_MODEL_ID" + # [/docs:get-mental-model] + + # [docs:refresh-mental-model] + # Refresh a mental model to update with current knowledge + hindsight mental-model refresh "$BANK_ID" "$MENTAL_MODEL_ID" + # [/docs:refresh-mental-model] + + # [docs:update-mental-model] + # Update a mental model's metadata + hindsight mental-model update "$BANK_ID" "$MENTAL_MODEL_ID" \ + --name "Updated Team Communication Preferences" + # [/docs:update-mental-model] + + # [docs:get-mental-model-history] + # Get the change history of a mental model + hindsight mental-model history "$BANK_ID" "$MENTAL_MODEL_ID" + # [/docs:get-mental-model-history] + + # [docs:delete-mental-model] + # Delete a mental model + hindsight mental-model delete "$BANK_ID" "$MENTAL_MODEL_ID" -y + # [/docs:delete-mental-model] +fi + +# ============================================================================= +# Cleanup (not shown in docs) +# ============================================================================= +curl -s -X DELETE "${HINDSIGHT_URL}/v1/default/banks/${BANK_ID}" > /dev/null + +echo "mental-models.sh: All examples passed" diff --git a/hindsight-docs/examples/api/recall.go b/hindsight-docs/examples/api/recall.go new file mode 100644 index 00000000..41475e3a --- /dev/null +++ b/hindsight-docs/examples/api/recall.go @@ -0,0 +1,217 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Setup (not shown in docs) + // ============================================================================= + for _, content := range []string{ + "Alice works at Google as a software engineer", + "Alice loves hiking on weekends", + "Bob is a data scientist who works with Alice", + } { + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{{Content: content}}, + }).Execute() + } + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:recall-basic] + response, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What does Alice do?", + }).Execute() + + // response.Results is a slice of RecallResult, each with: + // - Id: fact ID + // - Text: the extracted fact + // - Type: "world", "experience", or "observation" + // - Context: context label set during retain + // - Tags: []string of tags + // - Entities: []string of entity names linked to this fact + // - OccurredStart: ISO datetime of when the event started + // - OccurredEnd: ISO datetime of when the event ended + // - MentionedAt: ISO datetime of when the fact was retained + // - DocumentId: document this fact belongs to + for _, r := range response.GetResults() { + fmt.Println(r.GetText()) + } + // [/docs:recall-basic] + + // [docs:recall-with-options] + budgetHigh := hindsight.HIGH + maxTokens := int32(8000) + traceTrue := true + detailedResponse, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What does Alice do?", + Types: []string{"world", "experience"}, + Budget: &budgetHigh, + MaxTokens: &maxTokens, + Trace: &traceTrue, + }).Execute() + + for _, r := range detailedResponse.GetResults() { + fmt.Println("-", r.GetText()) + } + // [/docs:recall-with-options] + + // [docs:recall-world-only] + // Only world facts (objective information) + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "Where does Alice work?", + Types: []string{"world"}, + }).Execute() + // [/docs:recall-world-only] + + // [docs:recall-experience-only] + // Only experience (conversations and events) + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What have I recommended?", + Types: []string{"experience"}, + }).Execute() + // [/docs:recall-experience-only] + + // [docs:recall-observations-only] + // Only observations (consolidated knowledge) + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What patterns have I learned?", + Types: []string{"observation"}, + }).Execute() + // [/docs:recall-observations-only] + + // [docs:recall-source-facts] + // Recall observations and include their source facts + maxSFTokens := int32(4096) + sfOpts := hindsight.SourceFactsIncludeOptions{MaxTokens: &maxSFTokens} + obsResponse, _, _ := client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What patterns have I learned about Alice?", + Types: []string{"observation"}, + Include: &hindsight.IncludeOptions{ + SourceFacts: *hindsight.NewNullableSourceFactsIncludeOptions(&sfOpts), + }, + }).Execute() + + for _, obs := range obsResponse.GetResults() { + fmt.Printf("Observation: %s\n", obs.GetText()) + for _, factID := range obs.GetSourceFactIds() { + if fact, ok := obsResponse.GetSourceFacts()[factID]; ok { + fmt.Printf(" - [%s] %s\n", fact.GetType(), fact.GetText()) + } + } + } + // [/docs:recall-source-facts] + + // [docs:recall-budget-levels] + budgetLow := hindsight.LOW + // Quick lookup + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "Alice's email", + Budget: &budgetLow, + }).Execute() + + // Deep exploration + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "How are Alice and Bob connected?", + Budget: &budgetHigh, + }).Execute() + // [/docs:recall-budget-levels] + + // [docs:recall-token-budget] + // Fill up to 4K tokens of context with relevant memories + mt4k := int32(4096) + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What do I know about Alice?", + MaxTokens: &mt4k, + }).Execute() + + // Smaller budget for quick lookups + mt500 := int32(500) + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "Alice's email", + MaxTokens: &mt500, + }).Execute() + // [/docs:recall-token-budget] + + // [docs:recall-with-tags] + // Filter recall to only memories tagged for a specific user + tagsMatch := "any" + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What feedback did the user give?", + Tags: []string{"user:alice"}, + TagsMatch: &tagsMatch, + }).Execute() + // [/docs:recall-with-tags] + + // [docs:recall-tags-strict] + // Strict mode: only return memories that have matching tags (exclude untagged) + tagsMatchStrict := "any_strict" + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What did the user say?", + Tags: []string{"user:alice"}, + TagsMatch: &tagsMatchStrict, + }).Execute() + // [/docs:recall-tags-strict] + + // [docs:recall-tags-all] + // AND matching: require ALL specified tags to be present + tagsMatchAll := "all_strict" + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "What bugs were reported?", + Tags: []string{"user:alice", "bug-report"}, + TagsMatch: &tagsMatchAll, + }).Execute() + // [/docs:recall-tags-all] + + // [docs:recall-tags-all-mode] + // AND matching, includes untagged memories + tagsMatchAllMode := "all" + client.MemoryAPI.RecallMemories(ctx, "my-bank"). + RecallRequest(hindsight.RecallRequest{ + Query: "communication tools", + Tags: []string{"user:alice", "team"}, + TagsMatch: &tagsMatchAllMode, + }).Execute() + // [/docs:recall-tags-all-mode] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil) + http.DefaultClient.Do(req) + + fmt.Println("recall.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/recall.mjs b/hindsight-docs/examples/api/recall.mjs index e3494961..5726d29e 100644 --- a/hindsight-docs/examples/api/recall.mjs +++ b/hindsight-docs/examples/api/recall.mjs @@ -61,6 +61,88 @@ for (const r of detailedResponse.results) { // [/docs:recall-with-options] +// [docs:recall-world-only] +await client.recall('my-bank', 'query', { types: ['world'] }); +// [/docs:recall-world-only] + + +// [docs:recall-experience-only] +await client.recall('my-bank', 'query', { types: ['experience'] }); +// [/docs:recall-experience-only] + + +// [docs:recall-observations-only] +await client.recall('my-bank', 'query', { types: ['observation'] }); +// [/docs:recall-observations-only] + + +// [docs:recall-token-budget] +// Fill up to 4K tokens of context with relevant memories +await client.recall('my-bank', 'What do I know about Alice?', { maxTokens: 4096 }); + +// Smaller budget for quick lookups +await client.recall('my-bank', "Alice's email", { maxTokens: 500 }); +// [/docs:recall-token-budget] + + +// [docs:recall-with-tags] +// Filter recall to only memories tagged for a specific user +await client.recall('my-bank', 'What feedback did the user give?', { + tags: ['user:alice'] +}); +// [/docs:recall-with-tags] + + +// [docs:recall-tags-strict] +// Strict: only memories that have matching tags (excludes untagged) +await client.recall('my-bank', 'What did the user say?', { + tags: ['user:alice'], + tagsMatch: 'any_strict' +}); +// [/docs:recall-tags-strict] + + +// [docs:recall-tags-all] +// AND matching: require ALL specified tags to be present +await client.recall('my-bank', 'What bugs were reported?', { + tags: ['user:alice', 'bug-report'], + tagsMatch: 'all_strict' +}); +// [/docs:recall-tags-all] + + +// [docs:recall-tags-any] +await client.recall('my-bank', 'communication preferences', { + tags: ['user:alice'], + tagsMatch: 'any' +}); +// [/docs:recall-tags-any] + + +// [docs:recall-tags-any-strict] +await client.recall('my-bank', 'communication preferences', { + tags: ['user:alice'], + tagsMatch: 'any_strict' +}); +// [/docs:recall-tags-any-strict] + + +// [docs:recall-tags-all-mode] +await client.recall('my-bank', 'communication tools', { + tags: ['user:alice', 'team'], + tagsMatch: 'all' +}); +// [/docs:recall-tags-all-mode] + + +// [docs:recall-tags-all-strict] +await client.recall('my-bank', 'communication tools', { + tags: ['user:alice', 'team'], + tagsMatch: 'all_strict' +}); +// [/docs:recall-tags-all-strict] + + // [docs:recall-source-facts] // Recall observations and include their source facts const obsResponse = await client.recall('my-bank', 'What patterns have I learned about Alice?', { diff --git a/hindsight-docs/examples/api/recall.sh b/hindsight-docs/examples/api/recall.sh index 30de6c95..77eabc0e 100755 --- a/hindsight-docs/examples/api/recall.sh +++ b/hindsight-docs/examples/api/recall.sh @@ -38,6 +38,76 @@ hindsight memory recall my-bank "query" --trace # [/docs:recall-trace] +# [docs:recall-budget-levels] +# Quick lookup +hindsight memory recall my-bank "Alice's email" --budget low + +# Deep exploration +hindsight memory recall my-bank "How are Alice and Bob connected?" --budget high +# [/docs:recall-budget-levels] + + +# [docs:recall-token-budget] +# Fill up to 4K tokens of context with relevant memories +hindsight memory recall my-bank "What do I know about Alice?" --max-tokens 4096 + +# Smaller budget for quick lookups +hindsight memory recall my-bank "Alice's email" --max-tokens 500 +# [/docs:recall-token-budget] + + +# [docs:recall-source-facts] +# Recall observations with source facts +hindsight memory recall my-bank "What patterns have I learned about Alice?" \ + --fact-type observation +# [/docs:recall-source-facts] + + +# [docs:recall-with-tags] +# Filter recall to only memories tagged for a specific user +hindsight memory recall my-bank "What feedback did the user give?" \ + --tags "user:alice" +# [/docs:recall-with-tags] + + +# [docs:recall-tags-strict] +# Strict: only memories that have matching tags (excludes untagged) +hindsight memory recall my-bank "What did the user say?" \ + --tags "user:alice" --tags-match any_strict +# [/docs:recall-tags-strict] + + +# [docs:recall-tags-all] +# AND matching: require ALL specified tags to be present +hindsight memory recall my-bank "What bugs were reported?" \ + --tags "user:alice,bug-report" --tags-match all_strict +# [/docs:recall-tags-all] + + +# [docs:recall-tags-any] +hindsight memory recall my-bank "communication preferences" \ + --tags "user:alice" --tags-match any +# [/docs:recall-tags-any] + + +# [docs:recall-tags-any-strict] +hindsight memory recall my-bank "communication preferences" \ + --tags "user:alice" --tags-match any_strict +# [/docs:recall-tags-any-strict] + + +# [docs:recall-tags-all-mode] +hindsight memory recall my-bank "communication tools" \ + --tags "user:alice,team" --tags-match all +# [/docs:recall-tags-all-mode] + + +# [docs:recall-tags-all-strict] +hindsight memory recall my-bank "communication tools" \ + --tags "user:alice,team" --tags-match all_strict +# [/docs:recall-tags-all-strict] + + # ============================================================================= # Cleanup (not shown in docs) # ============================================================================= diff --git a/hindsight-docs/examples/api/reflect.go b/hindsight-docs/examples/api/reflect.go new file mode 100644 index 00000000..a798c510 --- /dev/null +++ b/hindsight-docs/examples/api/reflect.go @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Setup (not shown in docs) + // ============================================================================= + for _, content := range []string{ + "Alice works at Google as a software engineer", + "Alice has been working there for 5 years", + "Alice recently got promoted to senior engineer", + } { + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{{Content: content}}, + }).Execute() + } + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:reflect-basic] + client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "What should I know about Alice?", + }).Execute() + // [/docs:reflect-basic] + + // [docs:reflect-with-params] + budgetMid := hindsight.MID + client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "We're considering a hybrid work policy. What do you think about remote work?", + Budget: &budgetMid, + }).Execute() + // [/docs:reflect-with-params] + + // [docs:reflect-with-context] + // Context is passed to the LLM to help it understand the situation + ctxText := "We're in a budget review meeting discussing Q4 spending" + client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "What do you think about the proposal?", + Context: *hindsight.NewNullableString(&ctxText), + }).Execute() + // [/docs:reflect-with-context] + + // [docs:reflect-disposition] + // Create a bank with specific disposition + skepticism := int32(5) + literalism := int32(4) + empathy := int32(2) + mission := "I am a risk-aware financial advisor" + client.BanksAPI.CreateOrUpdateBank(ctx, "cautious-advisor"). + CreateBankRequest(hindsight.CreateBankRequest{ + Name: *hindsight.NewNullableString(hindsight.PtrString("Cautious Advisor")), + ReflectMission: *hindsight.NewNullableString(&mission), + DispositionSkepticism: *hindsight.NewNullableInt32(&skepticism), + DispositionLiteralism: *hindsight.NewNullableInt32(&literalism), + DispositionEmpathy: *hindsight.NewNullableInt32(&empathy), + }).Execute() + + // Reflect responses will reflect this disposition + client.MemoryAPI.Reflect(ctx, "cautious-advisor"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "Should I invest in crypto?", + }).Execute() + // Response will likely emphasize risks and caution + // [/docs:reflect-disposition] + + // [docs:reflect-sources] + // include.facts enables the based_on field in the response + sourcesResponse, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "Tell me about Alice", + Include: &hindsight.ReflectIncludeOptions{ + Facts: map[string]interface{}{}, // empty map enables fact inclusion + }, + }).Execute() + + fmt.Println("Response:", sourcesResponse.GetText()) + fmt.Println("\nBased on:") + if basedOn := sourcesResponse.GetBasedOn(); basedOn.Memories != nil { + for _, fact := range basedOn.GetMemories() { + fmt.Printf(" - [%s] %s\n", fact.GetType(), fact.GetText()) + } + } + // [/docs:reflect-sources] + + // [docs:reflect-with-tags] + // Filter reflection to only consider memories for a specific user + tagsMatch := "any_strict" + client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "What does this user think about our product?", + Tags: []string{"user:alice"}, + TagsMatch: &tagsMatch, + }).Execute() + // [/docs:reflect-with-tags] + + // [docs:reflect-structured-output] + // Define JSON schema for structured output + responseSchema := map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "recommendation": map[string]interface{}{"type": "string"}, + "confidence": map[string]interface{}{"type": "string", "enum": []string{"low", "medium", "high"}}, + "key_factors": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}}, + "risks": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}}, + }, + "required": []string{"recommendation", "confidence", "key_factors"}, + } + + structuredResponse, _, _ := client.MemoryAPI.Reflect(ctx, "my-bank"). + ReflectRequest(hindsight.ReflectRequest{ + Query: "Should we hire Alice for the ML team lead position?", + ResponseSchema: responseSchema, + }).Execute() + + // Access structured output + if out := structuredResponse.GetStructuredOutput(); out != nil { + fmt.Println("Recommendation:", out["recommendation"]) + fmt.Println("Key factors:", out["key_factors"]) + } + // [/docs:reflect-structured-output] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + for _, bankID := range []string{"my-bank", "cautious-advisor"} { + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/%s", apiURL, bankID), nil) + http.DefaultClient.Do(req) + } + + fmt.Println("reflect.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/reflect.mjs b/hindsight-docs/examples/api/reflect.mjs index 0295d142..5a0e7352 100644 --- a/hindsight-docs/examples/api/reflect.mjs +++ b/hindsight-docs/examples/api/reflect.mjs @@ -60,16 +60,27 @@ const advisorResponse = await client.reflect('cautious-advisor', 'Should I inves // [docs:reflect-sources] -const sourcesResponse = await client.reflect('my-bank', 'Tell me about Alice'); +const sourcesResponse = await client.reflect('my-bank', 'Tell me about Alice', { + includeFacts: true +}); console.log('Response:', sourcesResponse.text); console.log('\nBased on:'); -for (const fact of sourcesResponse.based_on || []) { +for (const fact of (sourcesResponse.based_on?.memories || [])) { console.log(` - [${fact.type}] ${fact.text}`); } // [/docs:reflect-sources] +// [docs:reflect-with-tags] +// Filter reflect to only use memories tagged for a specific user +await client.reflect('my-bank', 'What feedback did the user give?', { + tags: ['user:alice'], + tagsMatch: 'any_strict' +}); +// [/docs:reflect-with-tags] + + // [docs:reflect-structured-output] // Define JSON schema directly const responseSchema = { diff --git a/hindsight-docs/examples/api/reflect.sh b/hindsight-docs/examples/api/reflect.sh index 47afaf66..d2cfa8a7 100755 --- a/hindsight-docs/examples/api/reflect.sh +++ b/hindsight-docs/examples/api/reflect.sh @@ -26,9 +26,29 @@ hindsight memory reflect my-bank "Should I learn Python?" --context "career advi # [/docs:reflect-with-context] -# [docs:reflect-high-budget] -hindsight memory reflect my-bank "Summarize my week" --budget high -# [/docs:reflect-high-budget] +# [docs:reflect-with-params] +hindsight memory reflect my-bank "Summarize my week" --budget high --max-tokens 8192 +# [/docs:reflect-with-params] + + +# [docs:reflect-disposition] +hindsight bank set-config my-bank \ + --disposition-skepticism 5 \ + --disposition-literalism 4 \ + --disposition-empathy 2 +hindsight memory reflect my-bank "Should I invest in crypto?" +# [/docs:reflect-disposition] + + +# [docs:reflect-sources] +hindsight memory reflect my-bank "Tell me about Alice" --include-facts +# [/docs:reflect-sources] + + +# [docs:reflect-with-tags] +hindsight memory reflect my-bank "What feedback did the user give?" \ + --tags "user:alice" --tags-match any_strict +# [/docs:reflect-with-tags] # [docs:reflect-structured-output] diff --git a/hindsight-docs/examples/api/retain.go b/hindsight-docs/examples/api/retain.go new file mode 100644 index 00000000..2734852d --- /dev/null +++ b/hindsight-docs/examples/api/retain.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + + hindsight "github.com/vectorize-io/hindsight/hindsight-clients/go" +) + +func main() { + apiURL := os.Getenv("HINDSIGHT_API_URL") + if apiURL == "" { + apiURL = "http://localhost:8888" + } + + cfg := hindsight.NewConfiguration() + cfg.Servers = hindsight.ServerConfigurations{{URL: apiURL}} + client := hindsight.NewAPIClient(cfg) + ctx := context.Background() + + // ============================================================================= + // Doc Examples + // ============================================================================= + + // [docs:retain-basic] + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + {Content: "Alice works at Google as a software engineer"}, + }, + }).Execute() + // [/docs:retain-basic] + + // [docs:retain-conversation] + // Retain an entire conversation as a single document. + conversation := "Alice (2024-03-15T09:00:00Z): Hi Bob! Did you end up going to the doctor last week?\n" + + "Bob (2024-03-15T09:01:00Z): Yes, finally. Turns out I have a mild peanut allergy.\n" + + "Alice (2024-03-15T09:02:00Z): Oh no! Are you okay?\n" + + "Bob (2024-03-15T09:03:00Z): Yeah, nothing serious. Just need to carry an antihistamine.\n" + + "Alice (2024-03-15T09:04:00Z): Good to know. We'll avoid peanuts at the team lunch." + + docID := "chat-2024-03-15-alice-bob" + context_ := "team chat" + ts := "2024-03-15T09:04:00Z" + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + { + Content: conversation, + Context: *hindsight.NewNullableString(&context_), + DocumentId: *hindsight.NewNullableString(&docID), + Timestamp: *hindsight.NewNullableTimestamp(&hindsight.Timestamp{ + String: &ts, + }), + }, + }, + }).Execute() + // [/docs:retain-conversation] + + // [docs:retain-with-context] + ctxLabel := "career update" + ts2 := "2024-03-15T10:00:00Z" + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + { + Content: "Alice got promoted to senior engineer", + Context: *hindsight.NewNullableString(&ctxLabel), + Timestamp: *hindsight.NewNullableTimestamp(&hindsight.Timestamp{ + String: &ts2, + }), + }, + }, + }).Execute() + // [/docs:retain-with-context] + + // [docs:retain-batch] + doc1 := "conversation_001_msg_1" + doc2 := "conversation_001_msg_2" + doc3 := "conversation_001_msg_3" + ctx1 := "career" + ctx2 := "relationship" + client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + {Content: "Alice works at Google", Context: *hindsight.NewNullableString(&ctx1), DocumentId: *hindsight.NewNullableString(&doc1)}, + {Content: "Bob is a data scientist at Meta", Context: *hindsight.NewNullableString(&ctx1), DocumentId: *hindsight.NewNullableString(&doc2)}, + {Content: "Alice and Bob are friends", Context: *hindsight.NewNullableString(&ctx2), DocumentId: *hindsight.NewNullableString(&doc3)}, + }, + }).Execute() + // [/docs:retain-batch] + + // [docs:retain-async] + // Start async ingestion (returns immediately) + asyncTrue := true + largeDoc1 := "large-doc-1" + largeDoc2 := "large-doc-2" + retainResp, _, _ := client.MemoryAPI.RetainMemories(ctx, "my-bank"). + RetainRequest(hindsight.RetainRequest{ + Items: []hindsight.MemoryItem{ + {Content: "Large batch item 1", DocumentId: *hindsight.NewNullableString(&largeDoc1)}, + {Content: "Large batch item 2", DocumentId: *hindsight.NewNullableString(&largeDoc2)}, + }, + Async: &asyncTrue, + }).Execute() + + // Check if it was processed asynchronously + fmt.Println("Async:", retainResp.GetAsync()) + // [/docs:retain-async] + + // [docs:retain-files] + // Open a file and upload it — Hindsight converts it to text and extracts memories. + // Supports: PDF, DOCX, PPTX, XLSX, images (OCR), audio (transcription), and text formats. + f, err := os.Open("../../hindsight-docs/examples/api/sample.pdf") + if err != nil { + log.Fatalf("Failed to open file: %v", err) + } + defer f.Close() + + fileResp, _, _ := client.FilesAPI.FileRetain(ctx, "my-bank"). + Files([]*os.File{f}). + Request(`{"files_metadata": [{"context": "quarterly report"}]}`). + Execute() + fmt.Println("Operation IDs:", fileResp.GetOperationIds()) // Track processing via the operations endpoint + // [/docs:retain-files] + + // ============================================================================= + // Cleanup (not shown in docs) + // ============================================================================= + req, _ := http.NewRequest("DELETE", fmt.Sprintf("%s/v1/default/banks/my-bank", apiURL), nil) + http.DefaultClient.Do(req) + + fmt.Println("retain.go: All examples passed") +} diff --git a/hindsight-docs/examples/api/retain.mjs b/hindsight-docs/examples/api/retain.mjs index e7f3f7a7..5ac482c3 100644 --- a/hindsight-docs/examples/api/retain.mjs +++ b/hindsight-docs/examples/api/retain.mjs @@ -85,6 +85,21 @@ console.log(result.operation_ids); // Track processing via the operations endpo // [/docs:retain-files] +// [docs:retain-files-batch] +// Upload multiple files with per-file metadata (up to 10 files per request) +const batchResult = await client.retainFiles('my-bank', [ + new File([pdfBytes], 'report.pdf'), + new File([pdfBytes], 'notes.pdf'), +], { + filesMetadata: [ + { context: 'quarterly report', document_id: 'q1-report', tags: ['project:alpha'] }, + { context: 'meeting notes', document_id: 'q1-notes', tags: ['project:alpha'] }, + ] +}); +console.log(batchResult.operation_ids); // One operation ID per file +// [/docs:retain-files-batch] + + // ============================================================================= // Cleanup (not shown in docs) // ============================================================================= diff --git a/hindsight-docs/examples/api/retain.sh b/hindsight-docs/examples/api/retain.sh index a0111baa..5e820d18 100755 --- a/hindsight-docs/examples/api/retain.sh +++ b/hindsight-docs/examples/api/retain.sh @@ -25,12 +25,37 @@ hindsight memory retain my-bank "Alice works at Google as a software engineer" # [/docs:retain-basic] +# [docs:retain-conversation] +# Retain an entire conversation as a single document. +CONVERSATION="Alice (2024-03-15T09:00:00Z): Hi Bob! Did you end up going to the doctor last week? +Bob (2024-03-15T09:01:00Z): Yes, finally. Turns out I have a mild peanut allergy. +Alice (2024-03-15T09:02:00Z): Oh no! Are you okay? +Bob (2024-03-15T09:03:00Z): Yeah, nothing serious. Just need to carry an antihistamine. +Alice (2024-03-15T09:04:00Z): Good to know. We'll avoid peanuts at the team lunch." + +hindsight memory retain my-bank "$CONVERSATION" \ + --context "team chat" \ + --doc-id "chat-2024-03-15-alice-bob" +# [/docs:retain-conversation] + + # [docs:retain-with-context] hindsight memory retain my-bank "Alice got promoted" \ --context "career update" # [/docs:retain-with-context] +# [docs:retain-batch] +# Batch ingestion via individual retain calls (CLI processes items one at a time) +hindsight memory retain my-bank "Alice works at Google" \ + --context "career" --doc-id "conversation_001_msg_1" +hindsight memory retain my-bank "Bob is a data scientist at Meta" \ + --context "career" --doc-id "conversation_001_msg_2" +hindsight memory retain my-bank "Alice and Bob are friends" \ + --context "relationship" --doc-id "conversation_001_msg_3" +# [/docs:retain-batch] + + # [docs:retain-async] hindsight memory retain my-bank "Meeting notes" --async # [/docs:retain-async] diff --git a/hindsight-docs/package.json b/hindsight-docs/package.json index e4c3bc36..e02d83cf 100644 --- a/hindsight-docs/package.json +++ b/hindsight-docs/package.json @@ -5,7 +5,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", - "build": "docusaurus build", + "build": "node scripts/check-code-parity.mjs && docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", diff --git a/hindsight-docs/scripts/check-code-parity.mjs b/hindsight-docs/scripts/check-code-parity.mjs new file mode 100644 index 00000000..c491ddeb --- /dev/null +++ b/hindsight-docs/scripts/check-code-parity.mjs @@ -0,0 +1,128 @@ +#!/usr/bin/env node +/** + * Validates that every "language" Tabs block in MDX docs has all 4 required variants: + * Python, Node.js, CLI, Go. + * + * A Tabs block is considered a "language" block if it contains at least one TabItem + * with value "python", "node", "cli", or "go". + * + * Run: node scripts/check-code-parity.mjs + */ + +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const docsRoot = join(__dirname, '..'); +const REQUIRED_TABS = new Set(['python', 'node', 'cli', 'go']); + +const IGNORED_PATHS = [ + 'node_modules', + 'build', + '.docusaurus', + 'versioned_docs', // skip versioned docs +]; + +/** + * Recursively find all .mdx files under a directory. + */ +function findMdxFiles(dir) { + const results = []; + for (const entry of readdirSync(dir)) { + if (IGNORED_PATHS.includes(entry)) continue; + const full = join(dir, entry); + const stat = statSync(full); + if (stat.isDirectory()) { + results.push(...findMdxFiles(full)); + } else if (entry.endsWith('.mdx') || entry.endsWith('.md')) { + results.push(full); + } + } + return results; +} + +/** + * Parse a single MDX file and return all violations. + * A violation is a Tabs block that has at least one language tab but is missing + * one or more of the 4 required language variants. + */ +function checkFile(filePath) { + const content = readFileSync(filePath, 'utf8'); + const violations = []; + + // Split content into Tabs blocks. + // Strategy: find ... sections and scan for TabItem values. + // We use a simple line-by-line state machine. + const lines = content.split('\n'); + let inTabs = false; + let tabsStartLine = -1; + let currentTabValues = new Set(); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (!inTabs) { + // Look for opening tag (not ) + if (/^\s*]/.test(line) && !/<\/Tabs/.test(line)) { + inTabs = true; + tabsStartLine = i + 1; // 1-indexed + currentTabValues = new Set(); + } + } else { + // Inside a Tabs block — look for or nested TabItem values + if (/^\s*<\/Tabs\s*>/.test(line)) { + // End of Tabs block — check if it's a language block + const hasLanguageTab = [...currentTabValues].some(v => REQUIRED_TABS.has(v)); + if (hasLanguageTab) { + const missing = [...REQUIRED_TABS].filter(t => !currentTabValues.has(t)); + if (missing.length > 0) { + violations.push({ + line: tabsStartLine, + found: [...currentTabValues].filter(v => REQUIRED_TABS.has(v)), + missing, + }); + } + } + inTabs = false; + currentTabValues = new Set(); + } else { + // Look for TabItem value attributes + // Matches: ]*value=["']([^"']+)["']/); + if (match) { + currentTabValues.add(match[1]); + } + } + } + } + + return violations; +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +const mdxFiles = findMdxFiles(docsRoot); +let totalViolations = 0; + +for (const filePath of mdxFiles) { + const violations = checkFile(filePath); + if (violations.length > 0) { + const rel = relative(docsRoot, filePath); + for (const v of violations) { + console.error( + `[code-parity] ${rel}:${v.line} — Tabs block missing language tabs: ${v.missing.join(', ')} (found: ${v.found.join(', ')})` + ); + } + totalViolations += violations.length; + } +} + +if (totalViolations > 0) { + console.error(`\n[code-parity] ❌ Found ${totalViolations} Tabs block(s) missing required language variants.`); + console.error('[code-parity] Every Tabs block with language tabs must include: python, node, cli, go'); + process.exit(1); +} else { + console.log(`[code-parity] ✅ All ${mdxFiles.length} docs files pass 4-tab parity check.`); +} diff --git a/package-lock.json b/package-lock.json index 0a41dbd0..76212a7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ }, "hindsight-clients/typescript": { "name": "@vectorize-io/hindsight-client", - "version": "0.4.18", + "version": "0.4.19", "license": "MIT", "devDependencies": { "@hey-api/openapi-ts": "0.88.0", @@ -293,7 +293,7 @@ }, "hindsight-control-plane": { "name": "@vectorize-io/hindsight-control-plane", - "version": "0.4.18", + "version": "0.4.19", "license": "ISC", "dependencies": { "@radix-ui/react-alert-dialog": "^1.1.15",