diff --git a/.github/workflows/release-integration.yml b/.github/workflows/release-integration.yml new file mode 100644 index 000000000..b5d18b9bb --- /dev/null +++ b/.github/workflows/release-integration.yml @@ -0,0 +1,99 @@ +name: Release Integration + +on: + push: + tags: + - 'integrations/**' + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write # for PyPI trusted publishing + + steps: + - uses: actions/checkout@v6 + + - name: Extract integration info + id: info + run: | + # refs/tags/integrations/litellm/v0.1.0 → integration=litellm, version=0.1.0 + TAG="${GITHUB_REF#refs/tags/}" + INTEGRATION=$(echo "$TAG" | cut -d'/' -f2) + VERSION=$(echo "$TAG" | cut -d'/' -f3 | sed 's/^v//') + echo "integration=$INTEGRATION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "Integration: $INTEGRATION, Version: $VERSION" + + - name: Detect integration type + id: type + run: | + if [ -f "hindsight-integrations/${{ steps.info.outputs.integration }}/pyproject.toml" ]; then + echo "type=python" >> $GITHUB_OUTPUT + else + echo "type=typescript" >> $GITHUB_OUTPUT + fi + + # ── Python integrations (litellm, pydantic-ai, crewai) ────────────────── + + - name: Install uv + if: steps.type.outputs.type == 'python' + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Set up Python + if: steps.type.outputs.type == 'python' + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Build Python package + if: steps.type.outputs.type == 'python' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: uv build --out-dir dist + + - name: Publish Python package to PyPI + if: steps.type.outputs.type == 'python' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ./hindsight-integrations/${{ steps.info.outputs.integration }}/dist + skip-existing: true + + # ── TypeScript integrations (ai-sdk, chat, openclaw) ──────────────────── + + - name: Set up Node.js + if: steps.type.outputs.type == 'typescript' + uses: actions/setup-node@v6 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: npm ci + + - name: Build TypeScript package + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: npm run build + + - name: Publish TypeScript package to npm + if: steps.type.outputs.type == 'typescript' + working-directory: ./hindsight-integrations/${{ steps.info.outputs.integration }} + run: | + set +e + OUTPUT=$(npm publish --access public 2>&1) + EXIT_CODE=$? + echo "$OUTPUT" + if [ $EXIT_CODE -ne 0 ]; then + if echo "$OUTPUT" | grep -q "cannot publish over"; then + echo "Package version already published, skipping..." + exit 0 + fi + exit $EXIT_CODE + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bed3dea36..16ba50321 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,30 +46,10 @@ jobs: working-directory: ./hindsight-all-slim run: uv build --out-dir dist - - name: Build hindsight-litellm - working-directory: ./hindsight-integrations/litellm - run: uv build --out-dir dist - - name: Build hindsight-embed working-directory: ./hindsight-embed run: uv build --out-dir dist - - name: Build hindsight-crewai - working-directory: ./hindsight-integrations/crewai - run: uv build --out-dir dist - - - name: Build hindsight-pydantic-ai - working-directory: ./hindsight-integrations/pydantic-ai - run: uv build --out-dir dist - - - name: Build hindsight-hermes - working-directory: ./hindsight-integrations/hermes - run: uv build --out-dir dist - - - name: Build hindsight-agno - working-directory: ./hindsight-integrations/agno - run: uv build --out-dir dist - # Publish in order (client and api-slim first, then api/all wrappers which depend on them) - name: Publish hindsight-client to PyPI uses: pypa/gh-action-pypi-publish@release/v1 @@ -101,42 +81,12 @@ jobs: packages-dir: ./hindsight-all-slim/dist skip-existing: true - - name: Publish hindsight-litellm to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/litellm/dist - skip-existing: true - - name: Publish hindsight-embed to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: ./hindsight-embed/dist skip-existing: true - - name: Publish hindsight-crewai to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/crewai/dist - skip-existing: true - - - name: Publish hindsight-pydantic-ai to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/pydantic-ai/dist - skip-existing: true - - - name: Publish hindsight-hermes to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/hermes/dist - skip-existing: true - - - name: Publish hindsight-agno to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ./hindsight-integrations/agno/dist - skip-existing: true - # Upload artifacts for GitHub release - name: Upload artifacts uses: actions/upload-artifact@v7 @@ -148,12 +98,7 @@ jobs: hindsight-api/dist/* hindsight-all/dist/* hindsight-all-slim/dist/* - hindsight-integrations/litellm/dist/* hindsight-embed/dist/* - hindsight-integrations/crewai/dist/* - hindsight-integrations/pydantic-ai/dist/* - hindsight-integrations/hermes/dist/* - hindsight-integrations/agno/dist/* retention-days: 1 release-typescript-client: @@ -205,153 +150,6 @@ jobs: path: hindsight-clients/typescript/*.tgz retention-days: 1 - release-openclaw-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/openclaw - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/openclaw - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/openclaw - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/openclaw - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: openclaw-integration - path: hindsight-integrations/openclaw/*.tgz - retention-days: 1 - - release-ai-sdk-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/ai-sdk - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/ai-sdk - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/ai-sdk - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/ai-sdk - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: ai-sdk-integration - path: hindsight-integrations/ai-sdk/*.tgz - retention-days: 1 - - release-chat-integration: - runs-on: ubuntu-latest - environment: npm - - steps: - - uses: actions/checkout@v6 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - registry-url: 'https://registry.npmjs.org' - - - name: Install dependencies - working-directory: ./hindsight-integrations/chat - run: npm ci - - - name: Build - working-directory: ./hindsight-integrations/chat - run: npm run build - - - name: Publish to npm - working-directory: ./hindsight-integrations/chat - run: | - set +e - OUTPUT=$(npm publish --access public 2>&1) - EXIT_CODE=$? - echo "$OUTPUT" - if [ $EXIT_CODE -ne 0 ]; then - if echo "$OUTPUT" | grep -q "cannot publish over"; then - echo "Package version already published, skipping..." - exit 0 - fi - exit $EXIT_CODE - fi - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Pack for GitHub release - working-directory: ./hindsight-integrations/chat - run: npm pack - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: chat-integration - path: hindsight-integrations/chat/*.tgz - retention-days: 1 - release-control-plane: runs-on: ubuntu-latest environment: npm @@ -609,7 +407,7 @@ jobs: create-github-release: runs-on: ubuntu-latest - needs: [release-python-packages, release-typescript-client, release-openclaw-integration, release-ai-sdk-integration, release-chat-integration, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart] + needs: [release-python-packages, release-typescript-client, release-control-plane, release-rust-cli, release-docker-images, release-helm-chart] permissions: contents: write @@ -632,24 +430,6 @@ jobs: name: typescript-client path: ./artifacts/typescript-client - - name: Download OpenClaw Integration - uses: actions/download-artifact@v8 - with: - name: openclaw-integration - path: ./artifacts/openclaw-integration - - - name: Download AI SDK Integration - uses: actions/download-artifact@v8 - with: - name: ai-sdk-integration - path: ./artifacts/ai-sdk-integration - - - name: Download Chat Integration - uses: actions/download-artifact@v8 - with: - name: chat-integration - path: ./artifacts/chat-integration - - name: Download Control Plane uses: actions/download-artifact@v8 with: @@ -689,19 +469,9 @@ jobs: cp artifacts/python-packages/hindsight-api/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-all/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-all-slim/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/litellm/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/pydantic-ai/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/hermes/dist/* release-assets/ || true - cp artifacts/python-packages/hindsight-integrations/agno/dist/* release-assets/ || true cp artifacts/python-packages/hindsight-embed/dist/* release-assets/ || true # TypeScript client cp artifacts/typescript-client/*.tgz release-assets/ || true - # OpenClaw Integration - cp artifacts/openclaw-integration/*.tgz release-assets/ || true - # AI SDK Integration - cp artifacts/ai-sdk-integration/*.tgz release-assets/ || true - # Chat Integration - cp artifacts/chat-integration/*.tgz release-assets/ || true # Control Plane cp artifacts/control-plane/*.tgz release-assets/ || true # Rust CLI binaries diff --git a/hindsight-cli/src/api.rs b/hindsight-cli/src/api.rs index f0a01cac0..a81857a19 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 09f0a1e5e..f9bdde4be 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 645b17b33..c0a08502a 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 1bf004257..9a81b5876 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 27a373fd0..da5ca667e 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 5d28213c8..fe4f1a18f 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 9ff7828a6..b4e4441fe 100644 --- a/hindsight-clients/python/hindsight_client/hindsight_client.py +++ b/hindsight-clients/python/hindsight_client/hindsight_client.py @@ -792,6 +792,7 @@ def create_mental_model( 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 @@ def create_mental_model( 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 @@ def create_mental_model( 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 21817a31e..01941fc94 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-dev/hindsight_dev/generate_changelog.py b/hindsight-dev/hindsight_dev/generate_changelog.py index 760c23810..d10469a2a 100644 --- a/hindsight-dev/hindsight_dev/generate_changelog.py +++ b/hindsight-dev/hindsight_dev/generate_changelog.py @@ -25,7 +25,10 @@ GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases" GITHUB_COMMIT_URL = f"https://github.com/{GITHUB_REPO}/commit" REPO_PATH = Path(__file__).parent.parent.parent -CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog.md" +CHANGELOG_PATH = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "index.md" +INTEGRATION_CHANGELOG_DIR = REPO_PATH / "hindsight-docs" / "src" / "pages" / "changelog" / "integrations" + +VALID_INTEGRATIONS = ["litellm", "pydantic-ai", "crewai", "ai-sdk", "chat", "openclaw"] class ChangelogEntry(BaseModel): @@ -82,6 +85,31 @@ def get_git_tags() -> list[str]: return valid_tags +def get_integration_tags(integration: str) -> list[str]: + """Get all tags for a specific integration, sorted by semver (newest first).""" + prefix = f"integrations/{integration}/v" + result = subprocess.run( + ["git", "tag", "-l", f"{prefix}*"], + cwd=REPO_PATH, + capture_output=True, + text=True, + check=True, + ) + tags = [t.strip() for t in result.stdout.strip().split("\n") if t.strip()] + + valid_tags = [] + for tag in tags: + version_part = tag.removeprefix(prefix) + try: + parse_semver(version_part) + valid_tags.append(tag) + except ValueError: + continue + + valid_tags.sort(key=lambda t: parse_semver(t.removeprefix(prefix)), reverse=True) + return valid_tags + + def find_previous_version(new_version: str, existing_tags: list[str]) -> str | None: """Find the previous version based on semver rules.""" new_major, new_minor, new_patch = parse_semver(new_version) @@ -105,13 +133,41 @@ def find_previous_version(new_version: str, existing_tags: list[str]) -> str | N return candidates[0][0] -def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]: +def find_previous_integration_tag(new_version: str, existing_tags: list[str], integration: str) -> str | None: + """Find the previous integration tag based on semver rules.""" + prefix = f"integrations/{integration}/v" + new_major, new_minor, new_patch = parse_semver(new_version) + + candidates = [] + for tag in existing_tags: + version_part = tag.removeprefix(prefix) + try: + major, minor, patch = parse_semver(version_part) + except ValueError: + continue + + if (major, minor, patch) >= (new_major, new_minor, new_patch): + continue + + candidates.append((tag, (major, minor, patch))) + + if not candidates: + return None + + candidates.sort(key=lambda x: x[1], reverse=True) + return candidates[0][0] + + +def get_commits(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> list[Commit]: """Get commits between two refs as structured data.""" if from_ref: cmd = ["git", "log", "--format=%h|%s", "--no-merges", f"{from_ref}..{to_ref}"] else: cmd = ["git", "log", "--format=%h|%s", "--no-merges", to_ref] + if path_filter: + cmd += ["--", path_filter] + result = subprocess.run( cmd, cwd=REPO_PATH, @@ -131,13 +187,16 @@ def get_commits(from_ref: str | None, to_ref: str) -> list[Commit]: return commits -def get_detailed_diff(from_ref: str | None, to_ref: str) -> str: +def get_detailed_diff(from_ref: str | None, to_ref: str, path_filter: str | None = None) -> str: """Get file change stats between two refs.""" if from_ref: cmd = ["git", "diff", "--stat", f"{from_ref}..{to_ref}"] else: cmd = ["git", "diff", "--stat", f"{to_ref}^..{to_ref}"] + if path_filter: + cmd += ["--", path_filter] + result = subprocess.run( cmd, cwd=REPO_PATH, @@ -153,11 +212,14 @@ def analyze_commits_with_llm( version: str, commits: list[Commit], file_diff: str, + integration: str | None = None, ) -> list[ChangelogEntry]: """Use LLM to analyze commits and return structured changelog entries.""" commits_json = json.dumps([{"commit_id": c.hash, "message": c.message} for c in commits], indent=2) - prompt = f"""Analyze the following git commits for release {version} of Hindsight (an AI memory system). + subject = f"the {integration} integration for Hindsight" if integration else f"release {version} of Hindsight" + + prompt = f"""Analyze the following git commits for {subject} (an AI memory system). For each meaningful change, create a changelog entry with: - category: one of "feature", "improvement", "bugfix", "breaking", "other" @@ -192,9 +254,14 @@ def build_changelog_markdown( version: str, tag: str, entries: list[ChangelogEntry], + integration: str | None = None, ) -> str: """Build markdown changelog from structured entries.""" - release_url = f"{GITHUB_RELEASES_URL}/tag/{tag}" + tag_url = ( + f"https://github.com/{GITHUB_REPO}/releases/tag/{tag}" + if not integration + else f"https://github.com/{GITHUB_REPO}/tree/{tag}" + ) # Group entries by category categories = { @@ -213,7 +280,7 @@ def build_changelog_markdown( categories["other"][1].append(entry) # Build markdown - lines = [f"## [{version}]({release_url})", ""] + lines = [f"## [{version}]({tag_url})", ""] has_entries = False for cat_key in ["breaking", "feature", "improvement", "bugfix", "other"]: @@ -234,22 +301,12 @@ def build_changelog_markdown( return "\n".join(lines) -def read_existing_changelog() -> tuple[str, str]: +def read_existing_changelog(path: Path, default_header: str) -> tuple[str, str]: """Read existing changelog and split into header and content.""" - if not CHANGELOG_PATH.exists(): - header = """--- -hide_table_of_contents: true ---- + if not path.exists(): + return default_header, "" -# Changelog - -This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted. - -For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). -""" - return header, "" - - content = CHANGELOG_PATH.read_text() + content = path.read_text() match = re.search(r"^## ", content, re.MULTILINE) if match: @@ -262,11 +319,11 @@ def read_existing_changelog() -> tuple[str, str]: return header, releases -def write_changelog(header: str, new_entry: str, existing_releases: str) -> None: +def write_changelog(path: Path, header: str, new_entry: str, existing_releases: str) -> None: """Write changelog with new entry prepended.""" content = header + new_entry + "\n" + existing_releases - CHANGELOG_PATH.parent.mkdir(parents=True, exist_ok=True) - CHANGELOG_PATH.write_text(content.rstrip() + "\n") + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content.rstrip() + "\n") def generate_changelog_entry( @@ -329,22 +386,150 @@ def generate_changelog_entry( new_entry = build_changelog_markdown(display_version, tag, entries) - header, existing_releases = read_existing_changelog() + default_header = """--- +hide_table_of_contents: true +--- + +# Changelog + +This changelog highlights user-facing changes only. Internal maintenance, CI/CD, and infrastructure updates are omitted. + +For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). + +""" + header, existing_releases = read_existing_changelog(CHANGELOG_PATH, default_header) if f"## [{display_version}]" in existing_releases: console.print(f"[red]Error: Version {display_version} already exists in changelog[/red]") sys.exit(1) - write_changelog(header, new_entry, existing_releases) + write_changelog(CHANGELOG_PATH, header, new_entry, existing_releases) console.print(f"\n[green]Changelog updated: {CHANGELOG_PATH}[/green]") console.print(f"\n[bold]New entry:[/bold]\n{new_entry}") +def generate_integration_changelog_entry( + integration: str, + version: str, + llm_model: str = "gpt-5.2", +) -> None: + """Generate changelog entry for a specific integration version.""" + if integration not in VALID_INTEGRATIONS: + console.print(f"[red]Error: Unknown integration '{integration}'. Valid: {', '.join(VALID_INTEGRATIONS)}[/red]") + sys.exit(1) + + api_key = os.environ.get("OPENAI_API_KEY") + if not api_key: + console.print("[red]Error: OPENAI_API_KEY environment variable not set[/red]") + sys.exit(1) + + client = OpenAI(api_key=api_key) + + display_version = version.lstrip("v") + path_filter = f"hindsight-integrations/{integration}/" + changelog_path = INTEGRATION_CHANGELOG_DIR / f"{integration}.md" + + console.print(f"[blue]Fetching integration tags for {integration}...[/blue]") + existing_tags = get_integration_tags(integration) + + previous_tag = find_previous_integration_tag(display_version, existing_tags, integration) + + if previous_tag: + console.print(f"[green]Found previous tag: {previous_tag}[/green]") + else: + console.print("[yellow]No previous tag found, will include all commits touching this integration[/yellow]") + + console.print(f"[blue]Getting commits for {path_filter}...[/blue]") + commits = get_commits(previous_tag, "HEAD", path_filter=path_filter) + file_diff = get_detailed_diff(previous_tag, "HEAD", path_filter=path_filter) + + if not commits: + console.print("[yellow]Warning: No commits found touching this integration path[/yellow]") + entries = [] + else: + console.print(f"[blue]Found {len(commits)} commits[/blue]") + + console.print("\n[bold]Commits:[/bold]") + for c in commits: + console.print(f" {c.hash} {c.message}") + + console.print("\n[bold]Files changed:[/bold]") + console.print(file_diff[:4000] if len(file_diff) > 4000 else file_diff) + console.print("") + + console.print(f"[blue]Analyzing commits with LLM ({llm_model})...[/blue]") + entries = analyze_commits_with_llm( + client, llm_model, display_version, commits, file_diff, integration=integration + ) + + console.print(f"\n[bold]LLM identified {len(entries)} changelog entries:[/bold]") + for entry in entries: + console.print(f" [{entry.category}] {entry.summary} ({entry.commit_id})") + + integration_tag = f"integrations/{integration}/v{display_version}" + new_entry = build_changelog_markdown(display_version, integration_tag, entries, integration=integration) + + package_name = _get_package_name(integration) + default_header = f"""--- +hide_table_of_contents: true +--- + +# {_integration_display_name(integration)} Integration Changelog + +Changelog for [`{package_name}`]({_package_url(integration, package_name)}). + +For the source code, see [`hindsight-integrations/{integration}`](https://github.com/{GITHUB_REPO}/tree/main/hindsight-integrations/{integration}). + +← [Back to main changelog](/changelog) + +""" + header, existing_releases = read_existing_changelog(changelog_path, default_header) + + if f"## [{display_version}]" in existing_releases: + console.print(f"[red]Error: Version {display_version} already exists in integration changelog[/red]") + sys.exit(1) + + write_changelog(changelog_path, header, new_entry, existing_releases) + + console.print(f"\n[green]Integration changelog updated: {changelog_path}[/green]") + console.print(f"\n[bold]New entry:[/bold]\n{new_entry}") + + +def _get_package_name(integration: str) -> str: + packages = { + "litellm": "hindsight-litellm", + "pydantic-ai": "hindsight-pydantic-ai", + "crewai": "hindsight-crewai", + "ai-sdk": "@vectorize-io/hindsight-ai-sdk", + "chat": "@vectorize-io/hindsight-chat", + "openclaw": "@vectorize-io/hindsight-openclaw", + } + return packages[integration] + + +def _package_url(integration: str, package_name: str) -> str: + if package_name.startswith("@"): + return f"https://www.npmjs.com/package/{package_name}" + return f"https://pypi.org/project/{package_name}/" + + +def _integration_display_name(integration: str) -> str: + names = { + "litellm": "LiteLLM", + "pydantic-ai": "Pydantic AI", + "crewai": "CrewAI", + "ai-sdk": "AI SDK", + "chat": "Chat SDK", + "openclaw": "OpenClaw", + } + return names.get(integration, integration) + + def main(): parser = argparse.ArgumentParser( description="Generate changelog entry for a release", - usage="generate-changelog VERSION [--model MODEL]", + usage="generate-changelog VERSION [--model MODEL] [--integration NAME]", ) parser.add_argument( "version", @@ -355,13 +540,25 @@ def main(): default="gpt-5.2", help="OpenAI model to use (default: gpt-5.2)", ) + parser.add_argument( + "--integration", + default=None, + help=f"Generate changelog for a specific integration. Valid: {', '.join(VALID_INTEGRATIONS)}", + ) args = parser.parse_args() - generate_changelog_entry( - version=args.version, - llm_model=args.model, - ) + if args.integration: + generate_integration_changelog_entry( + integration=args.integration, + version=args.version, + llm_model=args.model, + ) + else: + generate_changelog_entry( + version=args.version, + llm_model=args.model, + ) if __name__ == "__main__": diff --git a/hindsight-docs/docs/developer/api/documents.mdx b/hindsight-docs/docs/developer/api/documents.mdx index cbbc5a131..d73acad62 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 b7d2d760b..1cc3eb261 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 0ff99fcaa..17b85f559 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 4a0b3ca2a..389496d20 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 2f6bab989..47fe2bc08 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 8fee9e41a..00ae6b42d 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 079d29979..14e99c6b4 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 bd0df1e93..78bb2d938 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 000000000..e112ad39f --- /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 000000000..c92c1b1c3 --- /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 000000000..442fdf947 --- /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 000000000..00442afd7 --- /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 000000000..9150eaafb --- /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 000000000..65412c36b --- /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 000000000..6b3158bb8 --- /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 000000000..16979b019 --- /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 4f8505639..66fc8feba 100644 --- a/hindsight-docs/examples/api/mental-models.py +++ b/hindsight-docs/examples/api/mental-models.py @@ -42,6 +42,18 @@ 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 000000000..287f9bf15 --- /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 000000000..41475e3a6 --- /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 e34949614..5726d29e9 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 30de6c95b..77eabc0ee 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 000000000..a798c510c --- /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 0295d1429..5a0e7352a 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 47afaf66a..d2cfa8a78 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 000000000..2734852db --- /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 e7f3f7a73..5ac482c3a 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 a0111baad..5e820d183 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 e4c3bc36b..e02d83cf8 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 000000000..c491ddeb4 --- /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/hindsight-docs/src/pages/changelog.md b/hindsight-docs/src/pages/changelog/index.md similarity index 98% rename from hindsight-docs/src/pages/changelog.md rename to hindsight-docs/src/pages/changelog/index.md index c13f61fac..0abf569fe 100644 --- a/hindsight-docs/src/pages/changelog.md +++ b/hindsight-docs/src/pages/changelog/index.md @@ -8,6 +8,17 @@ This changelog highlights user-facing changes only. Internal maintenance, CI/CD, For full release details, see [GitHub Releases](https://github.com/vectorize-io/hindsight/releases). +## Integration Changelogs + +| Integration | Package | Description | +|---|---|---| +| [LiteLLM](/changelog/integrations/litellm) | `hindsight-litellm` | Universal LLM memory via LiteLLM (100+ providers) | +| [Pydantic AI](/changelog/integrations/pydantic-ai) | `hindsight-pydantic-ai` | Persistent memory tools for Pydantic AI agents | +| [CrewAI](/changelog/integrations/crewai) | `hindsight-crewai` | Persistent memory for CrewAI agents | +| [AI SDK](/changelog/integrations/ai-sdk) | `@vectorize-io/hindsight-ai-sdk` | Memory integration for Vercel AI SDK | +| [Chat SDK](/changelog/integrations/chat) | `@vectorize-io/hindsight-chat` | Memory integration for Vercel Chat SDK | +| [OpenClaw](/changelog/integrations/openclaw) | `@vectorize-io/hindsight-openclaw` | Hindsight memory plugin for OpenClaw | + ## [0.4.19](https://github.com/vectorize-io/hindsight/releases/tag/v0.4.19) **Features** diff --git a/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md b/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md new file mode 100644 index 000000000..2587c9605 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/ai-sdk.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# AI SDK Integration Changelog + +Changelog for [`@vectorize-io/hindsight-ai-sdk`](https://www.npmjs.com/package/@vectorize-io/hindsight-ai-sdk) — memory integration for Vercel AI SDK. + +For the source code, see [`hindsight-integrations/ai-sdk`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/ai-sdk). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/chat.md b/hindsight-docs/src/pages/changelog/integrations/chat.md new file mode 100644 index 000000000..6311de485 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/chat.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# Chat SDK Integration Changelog + +Changelog for [`@vectorize-io/hindsight-chat`](https://www.npmjs.com/package/@vectorize-io/hindsight-chat) — memory integration for Vercel Chat SDK. + +For the source code, see [`hindsight-integrations/chat`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/chat). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/crewai.md b/hindsight-docs/src/pages/changelog/integrations/crewai.md new file mode 100644 index 000000000..1bf3fba40 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/crewai.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# CrewAI Integration Changelog + +Changelog for [`hindsight-crewai`](https://pypi.org/project/hindsight-crewai/) — persistent memory for CrewAI agents. + +For the source code, see [`hindsight-integrations/crewai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/crewai). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/litellm.md b/hindsight-docs/src/pages/changelog/integrations/litellm.md new file mode 100644 index 000000000..f0090c3bd --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/litellm.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# LiteLLM Integration Changelog + +Changelog for [`hindsight-litellm`](https://pypi.org/project/hindsight-litellm/) — universal LLM memory integration via LiteLLM. + +For the source code, see [`hindsight-integrations/litellm`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/litellm). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/openclaw.md b/hindsight-docs/src/pages/changelog/integrations/openclaw.md new file mode 100644 index 000000000..1208d2c14 --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/openclaw.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# OpenClaw Integration Changelog + +Changelog for [`@vectorize-io/hindsight-openclaw`](https://www.npmjs.com/package/@vectorize-io/hindsight-openclaw) — Hindsight memory plugin for OpenClaw. + +For the source code, see [`hindsight-integrations/openclaw`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/openclaw). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md b/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md new file mode 100644 index 000000000..6a779c8bc --- /dev/null +++ b/hindsight-docs/src/pages/changelog/integrations/pydantic-ai.md @@ -0,0 +1,11 @@ +--- +hide_table_of_contents: true +--- + +# Pydantic AI Integration Changelog + +Changelog for [`hindsight-pydantic-ai`](https://pypi.org/project/hindsight-pydantic-ai/) — persistent memory tools for Pydantic AI agents. + +For the source code, see [`hindsight-integrations/pydantic-ai`](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/pydantic-ai). + +← [Back to main changelog](/changelog) diff --git a/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/agno.md b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/agno.md new file mode 100644 index 000000000..00a927884 --- /dev/null +++ b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/agno.md @@ -0,0 +1,186 @@ +--- +sidebar_position: 9 +--- + +# Agno + +Persistent memory tools for [Agno](https://github.com/agno-agi/agno) agents via Hindsight. Give your agents long-term memory with retain, recall, and reflect — using Agno's native Toolkit pattern. + +## Features + +- **Native Toolkit** - Extends Agno's `Toolkit` base class, just like `Mem0Tools` +- **Memory Instructions** - Pre-recall memories for injection into `Agent(instructions=[...])` +- **Three Memory Tools** - Retain (store), Recall (search), Reflect (synthesize) — include any combination +- **Flexible Bank Resolution** - Static bank ID, `RunContext.user_id`, or custom resolver +- **Simple Configuration** - Configure once globally, or pass a client directly + +## Installation + +```bash +pip install hindsight-agno +``` + +## Quick Start + +```python +from agno.agent import Agent +from agno.models.openai import OpenAIChat +from hindsight_agno import HindsightTools + +agent = Agent( + model=OpenAIChat(id="gpt-4o-mini"), + tools=[HindsightTools( + bank_id="user-123", + hindsight_api_url="http://localhost:8888", + )], +) + +agent.print_response("Remember that I prefer dark mode") +agent.print_response("What are my preferences?") +``` + +The agent now has three tools it can call: + +- **`retain_memory`** — Store information to long-term memory +- **`recall_memory`** — Search long-term memory for relevant facts +- **`reflect_on_memory`** — Synthesize a reasoned answer from memories + +## With Memory Instructions + +Pre-recall relevant memories and inject them into the system prompt: + +```python +from hindsight_agno import HindsightTools, memory_instructions + +agent = Agent( + model=OpenAIChat(id="gpt-4o-mini"), + tools=[HindsightTools( + bank_id="user-123", + hindsight_api_url="http://localhost:8888", + )], + instructions=[memory_instructions( + bank_id="user-123", + hindsight_api_url="http://localhost:8888", + )], +) +``` + +## Selecting Tools + +Include only the tools you need: + +```python +tools = [HindsightTools( + bank_id="user-123", + hindsight_api_url="http://localhost:8888", + enable_retain=True, + enable_recall=True, + enable_reflect=False, # Omit reflect +)] +``` + +## Bank Resolution + +The bank ID is resolved in order: + +1. **`bank_resolver`** — Custom callable `(RunContext) -> str` +2. **`bank_id`** — Static bank ID passed to constructor +3. **`run_context.user_id`** — Automatic per-user banks + +```python +# Per-user banks from RunContext +agent = Agent( + model=OpenAIChat(id="gpt-4o-mini"), + tools=[HindsightTools(hindsight_api_url="http://localhost:8888")], + user_id="user-123", # Used as bank_id +) + +# Custom resolver +def resolve_bank(ctx): + return f"team-{ctx.user_id}" + +agent = Agent( + model=OpenAIChat(id="gpt-4o-mini"), + tools=[HindsightTools( + bank_resolver=resolve_bank, + hindsight_api_url="http://localhost:8888", + )], +) +``` + +## Global Configuration + +Instead of passing connection details to every toolkit, configure once: + +```python +from hindsight_agno import configure, HindsightTools + +configure( + hindsight_api_url="http://localhost:8888", + api_key="your-api-key", # Or set HINDSIGHT_API_KEY env var + budget="mid", # Recall budget: low/mid/high + max_tokens=4096, # Max tokens for recall results + tags=["env:prod"], # Tags for stored memories + recall_tags=["scope:global"], # Tags to filter recall + recall_tags_match="any", # Tag match mode: any/all/any_strict/all_strict +) + +# Now create toolkit without passing connection details +tools = [HindsightTools(bank_id="user-123")] +``` + +## Configuration Reference + +### `HindsightTools()` + +| Parameter | Default | Description | +|---|---|---| +| `bank_id` | `None` | Static Hindsight memory bank ID | +| `bank_resolver` | `None` | Callable `(RunContext) -> str` for dynamic bank ID | +| `client` | `None` | Pre-configured Hindsight client | +| `hindsight_api_url` | `None` | API URL (used if no client provided) | +| `api_key` | `None` | API key (used if no client provided) | +| `budget` | `"mid"` | Recall/reflect budget level (low/mid/high) | +| `max_tokens` | `4096` | Maximum tokens for recall results | +| `tags` | `None` | Tags applied when storing memories | +| `recall_tags` | `None` | Tags to filter when searching | +| `recall_tags_match` | `"any"` | Tag matching mode | +| `enable_retain` | `True` | Include the retain (store) tool | +| `enable_recall` | `True` | Include the recall (search) tool | +| `enable_reflect` | `True` | Include the reflect (synthesize) tool | + +### `memory_instructions()` + +| Parameter | Default | Description | +|---|---|---| +| `bank_id` | *required* | Hindsight memory bank ID | +| `client` | `None` | Pre-configured Hindsight client | +| `hindsight_api_url` | `None` | API URL (used if no client provided) | +| `api_key` | `None` | API key (used if no client provided) | +| `query` | `"relevant context about the user"` | Recall query for memory injection | +| `budget` | `"low"` | Recall budget level | +| `max_results` | `5` | Maximum memories to inject | +| `max_tokens` | `4096` | Maximum tokens for recall results | +| `prefix` | `"Relevant memories:\n"` | Text prepended before memory list | +| `tags` | `None` | Tags to filter recall results | +| `tags_match` | `"any"` | Tag matching mode | + +### `configure()` + +| Parameter | Default | Description | +|---|---|---| +| `hindsight_api_url` | Production API | Hindsight API URL | +| `api_key` | `HINDSIGHT_API_KEY` env | API key for authentication | +| `budget` | `"mid"` | Default recall budget level | +| `max_tokens` | `4096` | Default max tokens for recall | +| `tags` | `None` | Default tags for retain operations | +| `recall_tags` | `None` | Default tags to filter recall | +| `recall_tags_match` | `"any"` | Default tag matching mode | +| `verbose` | `False` | Enable verbose logging | + +## Requirements + +- Python >= 3.10 +- agno +- hindsight-client >= 0.4.0 +- A running Hindsight API server diff --git a/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/hermes.md b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/hermes.md new file mode 100644 index 000000000..68f2d97c1 --- /dev/null +++ b/hindsight-docs/versioned_docs/version-0.4/sdks/integrations/hermes.md @@ -0,0 +1,219 @@ +--- +sidebar_position: 10 +--- + +# Hermes Agent + +Hindsight memory integration for [Hermes Agent](https://github.com/NousResearch/hermes-agent). Gives your Hermes agent persistent long-term memory via retain, recall, and reflect tools. + +## What it does + +This package registers three tools into Hermes via its plugin system: + +- **`hindsight_retain`** — Stores information to long-term memory. Hermes calls this when the user shares facts, preferences, or anything worth remembering. +- **`hindsight_recall`** — Searches long-term memory for relevant information. Returns a numbered list of matching memories. +- **`hindsight_reflect`** — Synthesizes a thoughtful answer from stored memories. Use this when you want Hermes to reason over what it knows rather than return raw facts. + +These tools appear under the `[hindsight]` toolset in Hermes's `/tools` list. + +## Setup + +### 1. Install hindsight-hermes into the Hermes venv + +The package must be installed in the **same Python environment** that Hermes runs in, so the entry point is discoverable. + +```bash +uv pip install hindsight-hermes --python $HOME/.hermes/hermes-agent/venv/bin/python +``` + +### 2. Set environment variables + +The plugin reads its configuration from environment variables. Set these before launching Hermes: + +```bash +# Required — tells the plugin where Hindsight is running +export HINDSIGHT_API_URL=http://localhost:8888 + +# Required — the memory bank to read/write. Think of this as a "brain" for one user or agent. +export HINDSIGHT_BANK_ID=my-agent + +# Optional — only needed if using Hindsight Cloud (https://api.hindsight.vectorize.io) +export HINDSIGHT_API_KEY=your-api-key + +# Optional — recall budget: low (fast), mid (default), high (thorough) +export HINDSIGHT_BUDGET=mid +``` + +If neither `HINDSIGHT_API_URL` nor `HINDSIGHT_API_KEY` is set, the plugin silently skips registration — Hermes starts normally without the Hindsight tools. + +### 3. Disable Hermes's built-in memory tool + +Hermes has its own `memory` tool that saves to local files (`~/.hermes/`). If both are active, the LLM tends to prefer the built-in one since it's familiar. Disable it so the LLM uses Hindsight instead: + +```bash +hermes tools disable memory +``` + +This persists across sessions. You can re-enable it later with `hermes tools enable memory`. + +### 4. Start Hindsight API + +Follow the [Quick Start](/developer/api/quickstart) guide to get the Hindsight API running, then come back here. + +### 5. Launch Hermes + +```bash +hermes +``` + +Verify the plugin loaded by typing `/tools` — you should see: + +``` +[hindsight] + * hindsight_recall - Search long-term memory for relevant information. + * hindsight_reflect - Synthesize a thoughtful answer from long-term memories. + * hindsight_retain - Store information to long-term memory for later retrieval. +``` + +### 6. Test it + +**Store a memory:** +> Remember that my favourite colour is red + +You should see `⚡ hindsight` in the response, confirming it called `hindsight_retain`. + +**Recall a memory:** +> What's my favourite colour? + +**Reflect on memories:** +> Based on what you know about me, suggest a colour scheme for my IDE + +This calls `hindsight_reflect`, which synthesizes a response from all stored memories. + +**Verify via API:** + +```bash +curl -s http://localhost:8888/v1/default/banks/my-agent/memories/recall \ + -H "Content-Type: application/json" \ + -d '{"query": "favourite colour", "budget": "low"}' | python3 -m json.tool +``` + +## Troubleshooting + +### Tools don't appear in `/tools` + +1. **Check the plugin is installed in the right venv.** Run this from the Hermes venv: + ```bash + python -c "from hindsight_hermes import register; print('OK')" + ``` + +2. **Check the entry point is registered:** + ```bash + python -c " + import importlib.metadata + eps = importlib.metadata.entry_points(group='hermes_agent.plugins') + print(list(eps)) + " + ``` + You should see `EntryPoint(name='hindsight', value='hindsight_hermes', group='hermes_agent.plugins')`. + +3. **Check env vars are set.** The plugin skips registration silently if `HINDSIGHT_API_URL` and `HINDSIGHT_API_KEY` are both unset. + +### Hermes uses built-in memory instead of Hindsight + +Run `hermes tools disable memory` and restart. The built-in `memory` tool and Hindsight tools have overlapping purposes — the LLM will prefer whichever it's more familiar with, which is usually the built-in one. + +### Bank not found errors + +The plugin auto-creates banks on first use. If you see bank errors, check that the Hindsight API is running and `HINDSIGHT_API_URL` is correct. + +### Connection refused + +Make sure the Hindsight API is running and listening on the URL you configured. Test with: +```bash +curl http://localhost:8888/health +``` + +## Manual registration (advanced) + +If you don't want to use the plugin system, you can register tools directly in a Hermes startup script or custom agent: + +```python +from hindsight_hermes import register_tools + +register_tools( + bank_id="my-agent", + hindsight_api_url="http://localhost:8888", + budget="mid", + tags=["hermes"], # applied to all retained memories + recall_tags=["hermes"], # filter recall to only these tags +) +``` + +This imports `tools.registry` from Hermes at call time and registers the three tools directly. This approach gives you more control over parameters but requires Hermes to be importable. + +## Memory instructions (system prompt injection) + +Pre-recall memories at startup and inject them into the system prompt, so the agent starts every conversation with relevant context: + +```python +from hindsight_hermes import memory_instructions + +context = memory_instructions( + bank_id="my-agent", + hindsight_api_url="http://localhost:8888", + query="user preferences and important context", + budget="low", + max_results=5, +) +# Returns: +# Relevant memories: +# 1. User's favourite colour is red +# 2. User prefers dark mode +``` + +This never raises — if the API is down or no memories exist, it returns an empty string. + +## Global configuration (advanced) + +Instead of passing parameters to every call, configure once: + +```python +from hindsight_hermes import configure + +configure( + hindsight_api_url="http://localhost:8888", + api_key="your-key", + budget="mid", + tags=["hermes"], +) +``` + +Subsequent calls to `register_tools()` or `memory_instructions()` will use these defaults if no explicit values are provided. + +## MCP alternative + +Hermes also supports MCP servers natively. You can use Hindsight's MCP server directly instead of this plugin — no `hindsight-hermes` package needed: + +```yaml +# In your Hermes config +mcp_servers: + - name: hindsight + url: http://localhost:8888/mcp +``` + +This exposes the same retain/recall/reflect operations through Hermes's MCP integration. The tradeoff is that MCP tools may have different naming and the LLM needs to discover them, whereas the plugin registers tools with Hermes-native schemas. + +## Configuration reference + +| Parameter | Env Var | Default | Description | +|-----------|---------|---------|-------------| +| `hindsight_api_url` | `HINDSIGHT_API_URL` | `https://api.hindsight.vectorize.io` | Hindsight API URL | +| `api_key` | `HINDSIGHT_API_KEY` | — | API key for authentication | +| `bank_id` | `HINDSIGHT_BANK_ID` | — | Memory bank ID | +| `budget` | `HINDSIGHT_BUDGET` | `mid` | Recall budget (low/mid/high) | +| `max_tokens` | — | `4096` | Max tokens for recall results | +| `tags` | — | — | Tags applied when storing memories | +| `recall_tags` | — | — | Tags to filter recall results | +| `recall_tags_match` | — | `any` | Tag matching mode (any/all/any_strict/all_strict) | +| `toolset` | — | `hindsight` | Hermes toolset group name | diff --git a/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json b/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json index 6284f3ff3..1feded7b3 100644 --- a/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json +++ b/hindsight-docs/versioned_sidebars/version-0.4-sidebars.json @@ -275,6 +275,22 @@ "icon": "/img/icons/pydanticai.png" } }, + { + "type": "doc", + "id": "sdks/integrations/agno", + "label": "Agno", + "customProps": { + "icon": "/img/icons/agno.png" + } + }, + { + "type": "doc", + "id": "sdks/integrations/hermes", + "label": "Hermes Agent", + "customProps": { + "icon": "/img/icons/hermes.png" + } + }, { "type": "doc", "id": "sdks/integrations/skills", diff --git a/package-lock.json b/package-lock.json index 0a41dbd0d..76212a7a9 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", diff --git a/scripts/release-integration.sh b/scripts/release-integration.sh new file mode 100755 index 000000000..c8351ea59 --- /dev/null +++ b/scripts/release-integration.sh @@ -0,0 +1,148 @@ +#!/bin/bash +set -e + +cd "$(dirname "$0")/.." + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +print_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +print_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +print_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +VALID_INTEGRATIONS=("litellm" "pydantic-ai" "crewai" "ai-sdk" "chat" "openclaw") + +usage() { + print_error "Usage: $0 " + echo "" + echo " integration One of: ${VALID_INTEGRATIONS[*]}" + echo " version Semantic version (e.g. 0.2.0)" + echo "" + echo "Examples:" + echo " $0 litellm 0.2.0" + echo " $0 pydantic-ai 1.0.0" + exit 1 +} + +if [ -z "$1" ] || [ -z "$2" ]; then + usage +fi + +INTEGRATION=$1 +VERSION=$2 + +# Validate integration name +VALID=false +for v in "${VALID_INTEGRATIONS[@]}"; do + if [ "$v" = "$INTEGRATION" ]; then + VALID=true + break + fi +done +if [ "$VALID" = "false" ]; then + print_error "Unknown integration '$INTEGRATION'" + print_info "Valid integrations: ${VALID_INTEGRATIONS[*]}" + exit 1 +fi + +# Validate version format +if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + print_error "Invalid version format. Please use semantic versioning (e.g., 0.2.0)" + exit 1 +fi + +TAG="integrations/$INTEGRATION/v$VERSION" + +print_info "Releasing $INTEGRATION v$VERSION (tag: $TAG)" + +# Check if we're on main branch +CURRENT_BRANCH=$(git branch --show-current) +if [ "$CURRENT_BRANCH" != "main" ]; then + print_warn "You are not on the main branch (current: $CURRENT_BRANCH)" + read -p "Do you want to continue? (y/n) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + print_error "Release cancelled" + exit 1 + fi +fi + +# Check if working directory is clean +if [[ -n $(git status -s) ]]; then + print_error "Working directory is not clean. Please commit or stash your changes." + git status -s + exit 1 +fi + +# Check if tag already exists +if git rev-parse "$TAG" >/dev/null 2>&1; then + print_error "Tag $TAG already exists" + exit 1 +fi + +# Load .env for OPENAI_API_KEY if needed +if [ -z "$OPENAI_API_KEY" ] && [ -f ".env" ]; then + print_info "Loading environment from .env" + set -a + source ".env" + set +a +fi + +if [ -z "$OPENAI_API_KEY" ]; then + print_error "OPENAI_API_KEY is not set and no .env file found. Required for changelog generation." + exit 1 +fi + +# Determine integration type and update version +INTEGRATION_DIR="hindsight-integrations/$INTEGRATION" + +if [ ! -d "$INTEGRATION_DIR" ]; then + print_error "Integration directory not found: $INTEGRATION_DIR" + exit 1 +fi + +if [ -f "$INTEGRATION_DIR/pyproject.toml" ]; then + print_info "Updating version in $INTEGRATION_DIR/pyproject.toml" + sed -i.bak "s/^version = \".*\"/version = \"$VERSION\"/" "$INTEGRATION_DIR/pyproject.toml" + rm "$INTEGRATION_DIR/pyproject.toml.bak" +elif [ -f "$INTEGRATION_DIR/package.json" ]; then + print_info "Updating version in $INTEGRATION_DIR/package.json" + sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$INTEGRATION_DIR/package.json" + rm "$INTEGRATION_DIR/package.json.bak" +else + print_error "No pyproject.toml or package.json found in $INTEGRATION_DIR" + exit 1 +fi + +# Generate changelog entry using LLM +print_info "Generating changelog entry..." +if cd hindsight-dev && uv run generate-changelog "$VERSION" --integration "$INTEGRATION"; then + cd .. + print_info "Changelog generated" +else + cd .. + print_error "Changelog generation failed" + git checkout . + exit 1 +fi + +# Commit version bump + changelog together +print_info "Committing changes..." +git add "hindsight-integrations/$INTEGRATION/" "hindsight-docs/src/pages/changelog/integrations/$INTEGRATION.md" +git commit --no-verify -m "release($INTEGRATION): v$VERSION" + +# Create annotated tag +print_info "Creating tag $TAG..." +git tag -a "$TAG" -m "Release $INTEGRATION v$VERSION" + +# Push commit and tag +print_info "Pushing to remote..." +git push origin "$CURRENT_BRANCH" +git push origin "$TAG" + +print_info "✅ Released $INTEGRATION v$VERSION" +print_info "Tag: $TAG" +print_info "GitHub Actions will publish to the package registry." diff --git a/scripts/release.sh b/scripts/release.sh index 3c8fd448c..c602fb5ae 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -64,8 +64,8 @@ fi print_info "Updating version in all components..." -# Update Python packages -PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-integrations/litellm" "hindsight-integrations/crewai" "hindsight-integrations/pydantic-ai" "hindsight-integrations/hermes" "hindsight-integrations/agno" "hindsight-embed") +# Update Python packages (integrations are versioned independently via scripts/release-integration.sh) +PYTHON_PACKAGES=("hindsight-api" "hindsight-api-slim" "hindsight-all-slim" "hindsight-dev" "hindsight-all" "hindsight-embed") for package in "${PYTHON_PACKAGES[@]}"; do PYPROJECT_FILE="$package/pyproject.toml" if [ -f "$PYPROJECT_FILE" ]; then @@ -144,36 +144,6 @@ else print_warn "File $TYPESCRIPT_CLIENT_PKG not found, skipping" fi -# Update OpenClaw integration -OPENCLAW_PKG="hindsight-integrations/openclaw/package.json" -if [ -f "$OPENCLAW_PKG" ]; then - print_info "Updating $OPENCLAW_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$OPENCLAW_PKG" - rm "${OPENCLAW_PKG}.bak" -else - print_warn "File $OPENCLAW_PKG not found, skipping" -fi - -# Update AI SDK integration -AI_SDK_PKG="hindsight-integrations/ai-sdk/package.json" -if [ -f "$AI_SDK_PKG" ]; then - print_info "Updating $AI_SDK_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$AI_SDK_PKG" - rm "${AI_SDK_PKG}.bak" -else - print_warn "File $AI_SDK_PKG not found, skipping" -fi - -# Update Chat SDK integration -CHAT_SDK_PKG="hindsight-integrations/chat/package.json" -if [ -f "$CHAT_SDK_PKG" ]; then - print_info "Updating $CHAT_SDK_PKG" - sed -i.bak "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" "$CHAT_SDK_PKG" - rm "${CHAT_SDK_PKG}.bak" -else - print_warn "File $CHAT_SDK_PKG not found, skipping" -fi - # Update documentation version (creates new version or syncs to existing) print_info "Updating documentation for version $VERSION..." if [ -f "scripts/update-docs-version.sh" ]; then @@ -216,14 +186,11 @@ COMMIT_MSG="Release v$VERSION - Update version to $VERSION in all components - Regenerate OpenAPI spec and client SDKs -- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-litellm, hindsight-crewai, hindsight-pydantic-ai, hindsight-hermes, hindsight-agno, hindsight-embed +- Python packages: hindsight-api, hindsight-dev, hindsight-all, hindsight-embed - Python client: hindsight-clients/python - TypeScript client: hindsight-clients/typescript - Rust CLI: hindsight-cli - Control Plane: hindsight-control-plane -- OpenClaw integration: hindsight-integrations/openclaw -- AI SDK integration: hindsight-integrations/ai-sdk -- Chat SDK integration: hindsight-integrations/chat - Helm chart" # Add docs update note