Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0c5153861 | ||
|
|
ef24828356 | ||
|
|
db7a0ad3a5 |
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,10 @@ GITHUB_REPO = "vectorize-io/hindsight"
|
||||
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 @@ For full release details, see [GitHub Releases](https://github.com/vectorize-io/
|
||||
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__":
|
||||
|
||||
@@ -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**
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
@@ -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",
|
||||
|
||||
Executable
+148
@@ -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 <integration> <version>"
|
||||
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."
|
||||
+3
-36
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user