Skip to content

feat(eve): add Eve agent-framework MCP connection helper #4806

feat(eve): add Eve agent-framework MCP connection helper

feat(eve): add Eve agent-framework MCP connection helper #4806

Workflow file for this run

name: CI
on:
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
detect-changes:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
pull-requests: read
outputs:
core: ${{ steps.filter.outputs.core }}
clients-ts: ${{ steps.filter.outputs.clients-ts }}
clients-python: ${{ steps.filter.outputs.clients-python }}
clients-rust: ${{ steps.filter.outputs.clients-rust }}
clients-go: ${{ steps.filter.outputs.clients-go }}
control-plane: ${{ steps.filter.outputs.control-plane }}
cli: ${{ steps.filter.outputs.cli }}
docker: ${{ steps.filter.outputs.docker }}
helm: ${{ steps.filter.outputs.helm }}
docs: ${{ steps.filter.outputs.docs }}
embed: ${{ steps.filter.outputs.embed }}
all-npm: ${{ steps.filter.outputs.all-npm }}
hindsight-all: ${{ steps.filter.outputs.hindsight-all }}
integration-tests: ${{ steps.filter.outputs.integration-tests }}
integrations-openclaw: ${{ steps.filter.outputs.integrations-openclaw }}
integrations-ai-sdk: ${{ steps.filter.outputs.integrations-ai-sdk }}
integrations-agent-framework: ${{ steps.filter.outputs.integrations-agent-framework }}
integrations-composio: ${{ steps.filter.outputs.integrations-composio }}
integrations-chat: ${{ steps.filter.outputs.integrations-chat }}
integrations-claude-code: ${{ steps.filter.outputs.integrations-claude-code }}
integrations-cline: ${{ steps.filter.outputs.integrations-cline }}
integrations-codex: ${{ steps.filter.outputs.integrations-codex }}
integrations-continue: ${{ steps.filter.outputs.integrations-continue }}
integrations-cursor-cli: ${{ steps.filter.outputs.integrations-cursor-cli }}
integrations-crewai: ${{ steps.filter.outputs.integrations-crewai }}
integrations-litellm: ${{ steps.filter.outputs.integrations-litellm }}
integrations-pydantic-ai: ${{ steps.filter.outputs.integrations-pydantic-ai }}
integrations-ag2: ${{ steps.filter.outputs.integrations-ag2 }}
integrations-autogen: ${{ steps.filter.outputs.integrations-autogen }}
integrations-aider: ${{ steps.filter.outputs.integrations-aider }}
integrations-langgraph: ${{ steps.filter.outputs.integrations-langgraph }}
integrations-llamaindex: ${{ steps.filter.outputs.integrations-llamaindex }}
integrations-paperclip: ${{ steps.filter.outputs.integrations-paperclip }}
integrations-opencode: ${{ steps.filter.outputs.integrations-opencode }}
integrations-eve: ${{ steps.filter.outputs.integrations-eve }}
integrations-cursor: ${{ steps.filter.outputs.integrations-cursor }}
integrations-zed: ${{ steps.filter.outputs.integrations-zed }}
integrations-n8n: ${{ steps.filter.outputs.integrations-n8n }}
integrations-zapier: ${{ steps.filter.outputs.integrations-zapier }}
integrations-cloudflare-oauth-proxy: ${{ steps.filter.outputs.integrations-cloudflare-oauth-proxy }}
integrations-superagent: ${{ steps.filter.outputs.integrations-superagent }}
integrations-lockfiles: ${{ steps.filter.outputs.integrations-lockfiles }}
integrations-openai-agents: ${{ steps.filter.outputs.integrations-openai-agents }}
integrations-openhands: ${{ steps.filter.outputs.integrations-openhands }}
integrations-pipecat: ${{ steps.filter.outputs.integrations-pipecat }}
integrations-agentcore: ${{ steps.filter.outputs.integrations-agentcore }}
integrations-smolagents: ${{ steps.filter.outputs.integrations-smolagents }}
integrations-claude-agent-sdk: ${{ steps.filter.outputs.integrations-claude-agent-sdk }}
integrations-dify: ${{ steps.filter.outputs.integrations-dify }}
integrations-gemini-spark: ${{ steps.filter.outputs.integrations-gemini-spark }}
integrations-vapi: ${{ steps.filter.outputs.integrations-vapi }}
integrations-flowise: ${{ steps.filter.outputs.integrations-flowise }}
integrations-google-adk: ${{ steps.filter.outputs.integrations-google-adk }}
integrations-obsidian: ${{ steps.filter.outputs.integrations-obsidian }}
integrations-omo: ${{ steps.filter.outputs.integrations-omo }}
integrations-haystack: ${{ steps.filter.outputs.integrations-haystack }}
tools-agent-sdk: ${{ steps.filter.outputs.tools-agent-sdk }}
integrations-roo-code: ${{ steps.filter.outputs.integrations-roo-code }}
dev: ${{ steps.filter.outputs.dev }}
ci: ${{ steps.filter.outputs.ci }}
# Secrets are available for internal PRs and workflow_dispatch.
# Fork PRs via pull_request event do NOT have access to secrets.
has_secrets: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
core:
- 'hindsight-api-slim/**'
- 'hindsight-api/**'
- 'scripts/**'
- '.python-version'
- '.env.example'
clients-ts:
- 'hindsight-clients/typescript/**'
- 'package.json'
- 'package-lock.json'
clients-python:
- 'hindsight-clients/python/**'
clients-rust:
- 'hindsight-clients/rust/**'
clients-go:
- 'hindsight-clients/go/**'
control-plane:
- 'hindsight-control-plane/**'
- 'package.json'
- 'package-lock.json'
cli:
- 'hindsight-cli/**'
docker:
- 'docker/**'
helm:
- 'helm/**'
docs:
- 'hindsight-docs/**'
- '*.md'
# Integration changes can add/rename integrations, which the docs
# build's integrations check validates against integrations.json.
- 'hindsight-integrations/**'
embed:
- 'hindsight-embed/**'
all-npm:
- 'hindsight-all-npm/**'
- 'package.json'
- 'package-lock.json'
hindsight-all:
- 'hindsight-all/**'
integration-tests:
- 'hindsight-integration-tests/**'
integrations-openclaw:
- 'hindsight-integrations/openclaw/**'
integrations-ai-sdk:
- 'hindsight-integrations/ai-sdk/**'
integrations-agent-framework:
- 'hindsight-integrations/agent-framework/**'
integrations-composio:
- 'hindsight-integrations/composio/**'
integrations-chat:
- 'hindsight-integrations/chat/**'
integrations-claude-code:
- 'hindsight-integrations/claude-code/**'
integrations-cline:
- 'hindsight-integrations/cline/**'
integrations-codex:
- 'hindsight-integrations/codex/**'
integrations-continue:
- 'hindsight-integrations/continue/**'
integrations-cursor-cli:
- 'hindsight-integrations/cursor-cli/**'
integrations-crewai:
- 'hindsight-integrations/crewai/**'
integrations-litellm:
- 'hindsight-integrations/litellm/**'
integrations-pydantic-ai:
- 'hindsight-integrations/pydantic-ai/**'
integrations-ag2:
- 'hindsight-integrations/ag2/**'
integrations-autogen:
- 'hindsight-integrations/autogen/**'
integrations-aider:
- 'hindsight-integrations/aider/**'
integrations-langgraph:
- 'hindsight-integrations/langgraph/**'
integrations-llamaindex:
- 'hindsight-integrations/llamaindex/**'
integrations-haystack:
- 'hindsight-integrations/haystack/**'
integrations-paperclip:
- 'hindsight-integrations/paperclip/**'
integrations-opencode:
- 'hindsight-integrations/opencode/**'
integrations-eve:
- 'hindsight-integrations/eve/**'
integrations-cursor:
- 'hindsight-integrations/cursor/**'
integrations-zed:
- 'hindsight-integrations/zed/**'
integrations-n8n:
- 'hindsight-integrations/n8n/**'
integrations-zapier:
- 'hindsight-integrations/zapier/**'
integrations-cloudflare-oauth-proxy:
- 'hindsight-integrations/cloudflare-oauth-proxy/**'
integrations-superagent:
- 'hindsight-integrations/superagent/**'
integrations-lockfiles:
- 'hindsight-integrations/*/package-lock.json'
- 'hindsight-integrations/*/package.json'
- 'scripts/check-integration-lockfiles.sh'
integrations-openai-agents:
- 'hindsight-integrations/openai-agents/**'
integrations-openhands:
- 'hindsight-integrations/openhands/**'
integrations-pipecat:
- 'hindsight-integrations/pipecat/**'
integrations-agentcore:
- 'hindsight-integrations/agentcore/**'
integrations-smolagents:
- 'hindsight-integrations/smolagents/**'
integrations-claude-agent-sdk:
- 'hindsight-integrations/claude-agent-sdk/**'
integrations-dify:
- 'hindsight-integrations/dify/**'
integrations-gemini-spark:
- 'hindsight-integrations/gemini-spark/**'
integrations-vapi:
- 'hindsight-integrations/vapi/**'
integrations-flowise:
- 'hindsight-integrations/flowise/**'
integrations-google-adk:
- 'hindsight-integrations/google-adk/**'
integrations-obsidian:
- 'hindsight-integrations/obsidian/**'
integrations-omo:
- 'hindsight-integrations/omo/**'
tools-agent-sdk:
- 'hindsight-tools/hindsight-agent-sdk/**'
integrations-roo-code:
- 'hindsight-integrations/roo-code/**'
dev:
- 'hindsight-dev/**'
ci:
- '.github/**'
# Fail fast if any hindsight-integrations/*/package-lock.json was regenerated
# from the monorepo root and ended up symlinked at a workspace path instead
# of the npm registry. That bit us on the 0.6.0 openclaw release — tsc in
# the release workflow couldn't find `@vectorize-io/hindsight-client`
# because its `resolved` url pointed at a workspace dir whose `dist/` was
# gitignored and unbuilt. Catching this at PR time means the release CI
# never hits that class of failure.
check-integration-lockfiles:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-lockfiles == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Check integration lockfiles resolve from the npm registry
run: ./scripts/check-integration-lockfiles.sh
build-api-python-versions:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
python-version: ['3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Build hindsight-api
working-directory: ./hindsight-api-slim
run: uv build
build-typescript-client:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci --workspace=hindsight-clients/typescript
- name: Build TypeScript client
run: npm run build --workspace=hindsight-clients/typescript
build-hindsight-all-npm:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.all-npm == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci --workspace=hindsight-all-npm
- name: Run tests
run: npm test --workspace=hindsight-all-npm
- name: Build
run: npm run build --workspace=hindsight-all-npm
build-openclaw-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-openclaw == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.all-npm == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
# openclaw depends on two monorepo workspaces via `file:` deps:
# @vectorize-io/hindsight-client and @vectorize-io/hindsight-all. Their
# `dist/` directories are gitignored, so we must build them first.
# Otherwise vitest/tsc in openclaw fails with
# "Failed to resolve entry for package ..." on the value imports.
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (openclaw dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
# Build must run before tests: one unit test in src/backfill.test.ts
# creates a symlink to `$cwd/dist/backfill.js` and calls realpathSync on
# it via isDirectExecution(). Without a populated dist/ the realpath call
# throws, both paths stay unresolved, and the equality assertion fails.
- name: Build
working-directory: ./hindsight-integrations/openclaw
run: npm run build
- name: Run tests
working-directory: ./hindsight-integrations/openclaw
run: npm test
smoke-openclaw-install:
needs: [detect-changes, build-openclaw-integration]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-openclaw == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.all-npm == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
# Install the openclaw CLI globally. The smoke test exercises the real
# `openclaw plugins install` / `openclaw config set` / `openclaw plugins
# doctor` commands — not the in-repo integration tests — so a real CLI
# must be on PATH.
- name: Install openclaw CLI
run: npm install -g openclaw
- name: Verify openclaw CLI
run: openclaw --version
# openclaw depends on the workspace packages via published version
# ranges (^0.1.0 / ^0.5.0), not file: paths, so the smoke test's
# `openclaw plugins install <tarball>` resolves them straight from the
# npm registry. These builds are just for `npm pack` / local unit
# tests, not for resolving the plugin's runtime deps.
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (openclaw dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
- name: Run openclaw install smoke test
working-directory: ./hindsight-integrations/openclaw
run: ./scripts/smoke-test.sh
test-claude-code-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-claude-code == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install pytest
run: pip install pytest
- name: Run tests
working-directory: ./hindsight-integrations/claude-code
run: python -m pytest tests/ -v
test-cursor-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-cursor == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install pytest
run: pip install pytest
- name: Run tests
working-directory: ./hindsight-integrations/cursor
run: python -m pytest tests/ -v
test-zed-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-zed == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install package and pytest
working-directory: ./hindsight-integrations/zed
# Installs the package (incl. the zstandard runtime dep) so the threads.db
# reader tests can decompress Zed's zstd blobs.
run: pip install -e . pytest
- name: Run tests
working-directory: ./hindsight-integrations/zed
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: python -m pytest tests/ -v -m "not requires_real_llm"
test-omo-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-omo == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install pytest
run: pip install pytest
- name: Run tests
working-directory: ./hindsight-integrations/omo
run: python -m pytest tests/ -v
test-cline-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-cline == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build cline integration
working-directory: ./hindsight-integrations/cline
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/cline
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/cline
run: uv run pytest tests -v
test-codex-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-codex == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install pytest
run: pip install pytest
- name: Run tests
working-directory: ./hindsight-integrations/codex
run: python -m pytest tests/ -v
test-cursor-cli-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-cursor-cli == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build cursor-cli integration
working-directory: ./hindsight-integrations/cursor-cli
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/cursor-cli
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/cursor-cli
run: uv run pytest tests -v
build-ai-sdk-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-ai-sdk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/ai-sdk
run: npm ci
- name: Run tests
working-directory: ./hindsight-integrations/ai-sdk
run: npm test
- name: Build
working-directory: ./hindsight-integrations/ai-sdk
run: npm run build
test-ai-sdk-integration-deno:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-ai-sdk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/ai-sdk
run: npm ci
- name: Run tests (Deno)
working-directory: ./hindsight-integrations/ai-sdk
run: npm run test:deno
test-opencode-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-opencode == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/opencode
run: npm ci
- name: Run tests
working-directory: ./hindsight-integrations/opencode
run: npm test
- name: Build
working-directory: ./hindsight-integrations/opencode
run: npm run build
test-eve-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-eve == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Install dependencies
working-directory: ./hindsight-integrations/eve
run: npm ci
- name: Run tests
working-directory: ./hindsight-integrations/eve
run: npm test
- name: Build
working-directory: ./hindsight-integrations/eve
run: npm run build
test-n8n-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-n8n == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/n8n
run: npm install --no-fund --no-audit
- name: Run tests
working-directory: ./hindsight-integrations/n8n
run: npm test
- name: Build
working-directory: ./hindsight-integrations/n8n
run: npm run build
test-zapier-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-zapier == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/zapier
run: npm install --no-fund --no-audit
- name: Validate app definition
working-directory: ./hindsight-integrations/zapier
run: npm run validate
- name: Run tests
working-directory: ./hindsight-integrations/zapier
run: npm test
test-hindsight-agent-sdk:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.tools-agent-sdk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (agent-sdk dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Run tests
run: npm test --workspace=hindsight-tools/hindsight-agent-sdk
- name: Build
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
test-cloudflare-oauth-proxy-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-cloudflare-oauth-proxy == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/cloudflare-oauth-proxy
run: npm ci
- name: Typecheck
working-directory: ./hindsight-integrations/cloudflare-oauth-proxy
run: npm run typecheck
- name: Run tests
working-directory: ./hindsight-integrations/cloudflare-oauth-proxy
run: npm test
build-chat-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-chat == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/chat
run: npm ci
- name: Run tests
working-directory: ./hindsight-integrations/chat
run: npm test
- name: Build
working-directory: ./hindsight-integrations/chat
run: npm run build
test-paperclip-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-paperclip == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/paperclip
run: npm ci
- name: Build
working-directory: ./hindsight-integrations/paperclip
run: npm run build
- name: Run tests
working-directory: ./hindsight-integrations/paperclip
run: npm test
test-pipecat-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-pipecat == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build pipecat integration
working-directory: ./hindsight-integrations/pipecat
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/pipecat
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/pipecat
run: uv run pytest tests -v
test-google-adk-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-google-adk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build google-adk integration
working-directory: ./hindsight-integrations/google-adk
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/google-adk
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/google-adk
run: uv run pytest tests -v
test-gemini-spark-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-gemini-spark == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install dependencies
working-directory: ./hindsight-integrations/gemini-spark
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/gemini-spark
run: uv run pytest tests -v
test-roo-code-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-roo-code == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build roo-code integration
working-directory: ./hindsight-integrations/roo-code
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/roo-code
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/roo-code
run: uv run pytest tests -v
build-control-plane:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.control-plane == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install SDK dependencies
run: npm ci --workspace=hindsight-clients/typescript
- name: Build SDK
run: npm run build --workspace=hindsight-clients/typescript
# Install control plane deps and fix hoisted lightningcss binary
# lightningcss gets hoisted to root node_modules, so we need to reinstall it there
- name: Install Control Plane dependencies
run: |
npm install --workspace=hindsight-control-plane
rm -rf node_modules/lightningcss node_modules/@tailwindcss
npm install lightningcss @tailwindcss/postcss @tailwindcss/node
- name: Test Control Plane
run: npm test --workspace=hindsight-control-plane
- name: Check i18n locale parity and hardcoded strings
run: npm run i18n:check --workspace=hindsight-control-plane
- name: Build Control Plane
run: npm run build --workspace=hindsight-control-plane
- name: Verify standalone build
run: |
test -f hindsight-control-plane/standalone/server.js || exit 1
test -d hindsight-control-plane/standalone/node_modules || exit 1
node hindsight-control-plane/bin/cli.js --help
- name: Smoke test - verify server starts
run: |
cd hindsight-control-plane
node bin/cli.js --port 9999 &
SERVER_PID=$!
sleep 5
if curl -sf http://localhost:9999 > /dev/null 2>&1; then
echo "Server started successfully"
kill $SERVER_PID 2>/dev/null || true
exit 0
else
echo "Server failed to respond"
kill $SERVER_PID 2>/dev/null || true
exit 1
fi
build-docs:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.docs == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
fetch-depth: 0 # fetch tags so check-released-integrations can see them
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
# Fail fast before the (slow) build: every integrations.json entry must have a
# doc page, and every released integration tag must be in integrations.json.
# Needs no npm install (pure Node) and uses the tags fetched above.
- name: Check integrations (single source of truth)
run: node hindsight-docs/scripts/check-integrations.mjs
- name: Install dependencies
run: npm ci --workspace=hindsight-docs
- name: Build docs
run: npm run build --workspace=hindsight-docs
test-rust-cli:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.cli == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-cli/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Run unit tests
working-directory: hindsight-cli
run: cargo test
- name: Build CLI
working-directory: hindsight-cli
run: cargo build --release
- name: Upload CLI artifact
uses: actions/upload-artifact@v7
with:
name: hindsight-cli
path: hindsight-cli/target/release/hindsight
retention-days: 1
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run CLI smoke test
run: |
HINDSIGHT_CLI=hindsight-cli/target/release/hindsight ./hindsight-cli/smoke-test.sh
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
lint-helm-chart:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.helm == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install Helm
uses: azure/setup-helm@v5
with:
version: 'latest'
- name: Lint Helm chart
run: helm lint helm/hindsight
test-standalone-start-script:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.docker == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Run standalone start script tests
run: bash docker/standalone/test-start-all.sh
build-docker-images:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.docker == 'true' ||
needs.detect-changes.outputs.control-plane == 'true' ||
needs.detect-changes.outputs.ci == 'true')
name: Build Docker (${{ matrix.name }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
include:
- target: api-only
name: api
variant: full
build_args: ""
- target: api-only
name: api-slim
variant: slim
build_args: |
INCLUDE_LOCAL_MODELS=false
PRELOAD_ML_MODELS=false
- target: cp-only
name: control-plane
variant: full
build_args: ""
- target: standalone
name: standalone
variant: full
build_args: ""
- target: standalone
name: standalone-slim
variant: slim
build_args: |
INCLUDE_LOCAL_MODELS=false
PRELOAD_ML_MODELS=false
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: true
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build ${{ matrix.name }} image (${{ matrix.variant }})
uses: docker/build-push-action@v7
with:
context: .
file: docker/standalone/Dockerfile
target: ${{ matrix.target }}
build-args: ${{ matrix.build_args }}
push: false
load: ${{ matrix.variant == 'slim' }}
tags: hindsight-${{ matrix.name }}:test
# Removed GitHub Actions cache (type=gha) - it frequently returns 502 errors
# causing buildx to fail with "failed to parse error response 502"
# Build will be slower but more reliable
# Only test slim variants to save disk space (they're much smaller)
# Slim variants require external embedding providers
- name: Setup GCP credentials for smoke test
if: matrix.variant == 'slim'
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Smoke test - verify container starts
if: matrix.variant == 'slim'
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID: ${{ env.HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID }}
HINDSIGHT_API_EMBEDDINGS_PROVIDER: cohere
HINDSIGHT_API_RERANKER_PROVIDER: cohere
HINDSIGHT_API_COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
run: ./docker/test-image.sh "hindsight-${{ matrix.name }}:test" "${{ matrix.target }}"
test-api:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
# Sharded with pytest-split. Splits the ~19-min pytest run across 3
# parallel jobs, cutting critical-path wall time to ~7-8 min/shard. The
# .venv cache lets shards 2+ skip the ~3-min `uv sync` once shard 1
# populates the key — same key on re-runs hits cache on all three.
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
name: test-api (${{ matrix.shard }}/3)
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION_NAME: ${{ secrets.AWS_REGION_NAME }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Cache hindsight-api-slim/.venv
# Keyed on the workspace lockfile, the API package's pyproject, and the
# pinned Python version — the only inputs that change the resolved env.
# `uv sync --frozen` still runs after restore but is a near-instant link
# check when the venv already matches the lock.
uses: actions/cache@v5
with:
path: hindsight-api-slim/.venv
key: ${{ runner.os }}-venv-test-api-${{ hashFiles('uv.lock', 'hindsight-api-slim/pyproject.toml', '.python-version') }}
restore-keys: |
${{ runner.os }}-venv-test-api-
- name: Install dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Run tests (shard ${{ matrix.shard }}/3)
working-directory: ./hindsight-api-slim
# `--with pytest-split` adds the plugin ad-hoc — no uv.lock churn.
# pytest-split filters at collection (before xdist takes over), so it
# composes cleanly with the `-n 8 --dist loadgroup` baked into addopts.
run: uv run --with pytest-split pytest tests -v -m "not hs_llm_mat and not hs_llm_core" --splits 3 --group ${{ matrix.shard }}
test-api-llm-core:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
name: Core LLM tests
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
SentenceTransformer('BAAI/bge-small-en-v1.5')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Run core LLM tests
working-directory: ./hindsight-api-slim
run: uv run pytest tests -v -m "hs_llm_core" --timeout 600
test-api-llm-acceptance:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
((github.event_name == 'pull_request_review' && github.event.review.state == 'approved') ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- provider: vertexai
model: google/gemini-2.5-flash-lite
- provider: gemini
model: gemini-2.5-flash-lite
api_key_secret: GEMINI_API_KEY
- provider: openai
model: gpt-4.1-nano
api_key_secret: OPENAI_API_KEY
- provider: groq
model: openai/gpt-oss-20b
api_key_secret: GROQ_API_KEY
- provider: bedrock
model: us.amazon.nova-2-lite-v1:0
- provider: litellmrouter
model: gpt-4.1-nano
# Single-deployment chain over OpenAI — verifies the Router-backed
# call path works end-to-end. Built from secrets in the step below.
name: LLM acceptance (${{ matrix.provider }}/${{ matrix.model }})
env:
HINDSIGHT_API_LLM_PROVIDER: ${{ matrix.provider }}
HINDSIGHT_API_LLM_MODEL: ${{ matrix.model }}
HINDSIGHT_API_LLM_API_KEY: ${{ matrix.api_key_secret && secrets[matrix.api_key_secret] || '' }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION_NAME: ${{ secrets.AWS_REGION_NAME }}
HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
SentenceTransformer('BAAI/bge-small-en-v1.5')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
"
- name: Build litellmrouter config
if: matrix.provider == 'litellmrouter'
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ROUTER_MODEL: ${{ matrix.model }}
run: |
cfg=$(jq -nc \
--arg model "openai/$ROUTER_MODEL" \
--arg key "$OPENAI_API_KEY" \
'{model_list: [{model_name: "default", litellm_params: {model: $model, api_key: $key}}]}')
echo "::add-mask::$cfg"
echo "HINDSIGHT_API_LLM_LITELLMROUTER_CONFIG=$cfg" >> "$GITHUB_ENV"
- name: Run LLM acceptance tests
working-directory: ./hindsight-api-slim
run: uv run pytest tests -v -m "hs_llm_mat" --timeout 600
test-api-oracle:
needs: [detect-changes]
# Gated behind the "oracle-tests" PR label so it doesn't run by default.
# Add the label to any PR that needs Oracle validation.
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
contains(github.event.pull_request.labels.*.name, 'oracle-tests') &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
HINDSIGHT_API_DATABASE_BACKEND: oracle
ORACLE_TEST_DSN: oracle+oracledb://hindsight_test:hindsight_test@localhost:1521/FREEPDB1
services:
oracle:
image: container-registry.oracle.com/database/free:latest
env:
ORACLE_PWD: oracle
ports:
- 1521:1521
options: >-
--health-cmd "echo 'SELECT 1 FROM DUAL;' | sqlplus -s system/oracle@localhost:1521/FREEPDB1 || exit 1"
--health-interval 30s
--health-timeout 10s
--health-retries 10
--health-start-period 120s
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup Oracle test user
# The SYSTEM tablespace uses manual segment space management which
# doesn't support VECTOR types. Create an ASSM tablespace and a
# dedicated test user so VECTOR columns work correctly.
run: |
pip install oracledb
python3 -c "
import oracledb
conn = oracledb.connect(user='system', password='oracle', dsn='localhost:1521/FREEPDB1')
cursor = conn.cursor()
cursor.execute(\"\"\"
CREATE BIGFILE TABLESPACE hindsight_ts
DATAFILE 'hindsight_ts.dbf' SIZE 2G AUTOEXTEND ON NEXT 500M MAXSIZE UNLIMITED
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO
\"\"\")
cursor.execute(\"\"\"
CREATE USER hindsight_test IDENTIFIED BY hindsight_test
DEFAULT TABLESPACE hindsight_ts
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON hindsight_ts
\"\"\")
cursor.execute('GRANT CONNECT, RESOURCE, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW, CREATE PROCEDURE TO hindsight_test')
cursor.execute('GRANT CTXAPP TO hindsight_test')
conn.commit()
conn.close()
print('Oracle test user created successfully')
"
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Run Oracle tests
working-directory: ./hindsight-api-slim
# -n0: run sequentially to avoid ORA-00060 deadlocks from concurrent
# test transactions against the same Oracle Free container.
run: uv run pytest tests -v -m oracle -n0
test-python-client:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-python == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Build Python client
working-directory: ./hindsight-clients/python
run: uv build
- name: Install client test dependencies
working-directory: ./hindsight-clients/python
run: uv sync --frozen --extra test --index-strategy unsafe-best-match
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run Python client tests
working-directory: ./hindsight-clients/python
run: uv run pytest tests -v
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-typescript-client:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Install TypeScript client dependencies
working-directory: ./hindsight-clients/typescript
run: npm ci
- name: Build TypeScript client
working-directory: ./hindsight-clients/typescript
run: npm run build
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run TypeScript client tests
working-directory: ./hindsight-clients/typescript
run: npm test
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
# -----------------------------------------------------------------
# Client integration tests against Oracle 23ai.
#
# Same shape as test-python-client / test-typescript-client but with the
# API server pointed at an Oracle 23ai service container. Catches API
# changes that work on PG but break on Oracle (or vice versa) before they
# ship — the unit tests use the abstraction layer, but only the client
# tests exercise full HTTP round-trips with real serialized payloads.
#
# Runs on every API/clients change (no label needed). The "oracle-tests"
# label is reserved for test-api-oracle — the full unit suite is too
# heavy to run on every PR.
# -----------------------------------------------------------------
test-python-client-oracle:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-python == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
HINDSIGHT_API_DATABASE_BACKEND: oracle
HINDSIGHT_API_DATABASE_URL: oracle+oracledb://hindsight_test:hindsight_test@localhost:1521/FREEPDB1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
services:
oracle:
image: container-registry.oracle.com/database/free:latest
env:
ORACLE_PWD: oracle
ports:
- 1521:1521
options: >-
--health-cmd "echo 'SELECT 1 FROM DUAL;' | sqlplus -s system/oracle@localhost:1521/FREEPDB1 || exit 1"
--health-interval 30s
--health-timeout 10s
--health-retries 10
--health-start-period 120s
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup Oracle test user
run: |
pip install oracledb
python3 -c "
import oracledb
conn = oracledb.connect(user='system', password='oracle', dsn='localhost:1521/FREEPDB1')
cursor = conn.cursor()
cursor.execute(\"\"\"
CREATE BIGFILE TABLESPACE hindsight_ts
DATAFILE 'hindsight_ts.dbf' SIZE 2G AUTOEXTEND ON NEXT 500M MAXSIZE UNLIMITED
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO
\"\"\")
cursor.execute(\"\"\"
CREATE USER hindsight_test IDENTIFIED BY hindsight_test
DEFAULT TABLESPACE hindsight_ts
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON hindsight_ts
\"\"\")
cursor.execute('GRANT CONNECT, RESOURCE, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW, CREATE PROCEDURE TO hindsight_test')
cursor.execute('GRANT CTXAPP TO hindsight_test')
conn.commit()
conn.close()
print('Oracle test user created successfully')
"
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Build Python client
working-directory: ./hindsight-clients/python
run: uv build
- name: Install client test dependencies
working-directory: ./hindsight-clients/python
run: uv sync --frozen --extra test --index-strategy unsafe-best-match
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
# The Oracle DB env vars need to be in .env for start-api.sh to pick them
# up — it source's the file before exec'ing the API.
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
HINDSIGHT_API_DATABASE_BACKEND=${{ env.HINDSIGHT_API_DATABASE_BACKEND }}
HINDSIGHT_API_DATABASE_URL=${{ env.HINDSIGHT_API_DATABASE_URL }}
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run Python client tests (Oracle, sequential)
working-directory: ./hindsight-clients/python
# -n0 overrides the "-n auto" default in pyproject.toml. Oracle's
# row-level locking deadlocks (ORA-00060) under concurrent retain
# cleanup — same reason test-api-oracle runs sequentially.
run: uv run pytest tests -v -n0
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-typescript-client-oracle:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
HINDSIGHT_API_DATABASE_BACKEND: oracle
HINDSIGHT_API_DATABASE_URL: oracle+oracledb://hindsight_test:hindsight_test@localhost:1521/FREEPDB1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
services:
oracle:
image: container-registry.oracle.com/database/free:latest
env:
ORACLE_PWD: oracle
ports:
- 1521:1521
options: >-
--health-cmd "echo 'SELECT 1 FROM DUAL;' | sqlplus -s system/oracle@localhost:1521/FREEPDB1 || exit 1"
--health-interval 30s
--health-timeout 10s
--health-retries 10
--health-start-period 120s
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup Oracle test user
run: |
pip install oracledb
python3 -c "
import oracledb
conn = oracledb.connect(user='system', password='oracle', dsn='localhost:1521/FREEPDB1')
cursor = conn.cursor()
cursor.execute(\"\"\"
CREATE BIGFILE TABLESPACE hindsight_ts
DATAFILE 'hindsight_ts.dbf' SIZE 2G AUTOEXTEND ON NEXT 500M MAXSIZE UNLIMITED
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO
\"\"\")
cursor.execute(\"\"\"
CREATE USER hindsight_test IDENTIFIED BY hindsight_test
DEFAULT TABLESPACE hindsight_ts
TEMPORARY TABLESPACE temp
QUOTA UNLIMITED ON hindsight_ts
\"\"\")
cursor.execute('GRANT CONNECT, RESOURCE, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW, CREATE PROCEDURE TO hindsight_test')
cursor.execute('GRANT CTXAPP TO hindsight_test')
conn.commit()
conn.close()
print('Oracle test user created successfully')
"
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Install TypeScript client dependencies
working-directory: ./hindsight-clients/typescript
run: npm ci
- name: Build TypeScript client
working-directory: ./hindsight-clients/typescript
run: npm run build
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
HINDSIGHT_API_DATABASE_BACKEND=${{ env.HINDSIGHT_API_DATABASE_BACKEND }}
HINDSIGHT_API_DATABASE_URL=${{ env.HINDSIGHT_API_DATABASE_URL }}
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run TypeScript client tests
working-directory: ./hindsight-clients/typescript
run: npm test
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-typescript-client-deno:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Set up Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Install TypeScript client dependencies
working-directory: ./hindsight-clients/typescript
run: npm ci
- name: Build TypeScript client
working-directory: ./hindsight-clients/typescript
run: npm run build
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run TypeScript client tests (Deno)
working-directory: ./hindsight-clients/typescript
run: npm run test:deno
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
build-rust-cli-arm64:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.cli == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-cli/target
key: linux-arm64-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build CLI
working-directory: hindsight-cli
run: cargo build --release --target aarch64-unknown-linux-gnu
test-rust-client:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-rust == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-clients/rust/target
key: ${{ runner.os }}-cargo-client-${{ hashFiles('hindsight-clients/rust/Cargo.lock') }}
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run Rust client tests
working-directory: ./hindsight-clients/rust
run: cargo test --lib
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-go-client:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-go == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Prefer CPU-only PyTorch in CI (but keep PyPI for everything else)
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: '1.23'
cache-dependency-path: hindsight-clients/go/go.sum
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Build Go client
working-directory: ./hindsight-clients/go
run: go build ./...
- name: Run Go client tests
working-directory: ./hindsight-clients/go
run: go test -v -tags=integration
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-openclaw-integration:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.integrations-openclaw == 'true' ||
needs.detect-changes.outputs.embed == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
HINDSIGHT_EMBED_PACKAGE_PATH: ${{ github.workspace }}/hindsight-embed
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install embed dependencies
working-directory: ./hindsight-embed
run: uv sync --frozen --index-strategy unsafe-best-match
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
# openclaw depends on @vectorize-io/hindsight-client and
# @vectorize-io/hindsight-all via `file:` — their `dist/` directories are
# gitignored and must be built before openclaw's npm ci copies them.
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (openclaw dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw integration dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run openclaw integration tests
working-directory: ./hindsight-integrations/openclaw
run: npm run test:integration
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-integration:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.integration-tests == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build API
working-directory: ./hindsight-api-slim
run: uv build
- name: Install integration test dependencies
working-directory: ./hindsight-integration-tests
run: uv sync --frozen
- name: Install API dependencies
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Run integration tests
working-directory: ./hindsight-integration-tests
run: uv run pytest tests/ -v
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-ag2-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-ag2 == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build ag2 integration
working-directory: ./hindsight-integrations/ag2
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/ag2
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/ag2
run: uv run pytest tests -v
test-aider-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-aider == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build aider integration
working-directory: ./hindsight-integrations/aider
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/aider
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/aider
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-autogen-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-autogen == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build autogen integration
working-directory: ./hindsight-integrations/autogen
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/autogen
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/autogen
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-composio-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-composio == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build composio integration
working-directory: ./hindsight-integrations/composio
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/composio
run: uv sync --frozen
- name: Lint
working-directory: ./hindsight-integrations/composio
run: uv run ruff check .
- name: Run tests
working-directory: ./hindsight-integrations/composio
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-continue-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-continue == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build continue integration
working-directory: ./hindsight-integrations/continue
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/continue
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/continue
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-smolagents-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-smolagents == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build smolagents integration
working-directory: ./hindsight-integrations/smolagents
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/smolagents
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/smolagents
run: uv run pytest tests -v
test-dify-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-dify == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
# Dify plugin runtime targets Python 3.12 (see manifest.yaml)
python-version: '3.12'
- name: Install dependencies
working-directory: ./hindsight-integrations/dify
run: uv pip install --system -e . pytest pytest-mock
- name: Run tests
working-directory: ./hindsight-integrations/dify
run: pytest tests -v
test-flowise-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-flowise == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/flowise
run: npm install --no-audit --no-fund
- name: Type check
working-directory: ./hindsight-integrations/flowise
run: npx tsc --noEmit
- name: Run tests
working-directory: ./hindsight-integrations/flowise
run: npm test
test-obsidian-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-obsidian == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
working-directory: ./hindsight-integrations/obsidian
run: npm install --no-audit --no-fund
- name: Type check
working-directory: ./hindsight-integrations/obsidian
run: npx tsc --noEmit
- name: Build
working-directory: ./hindsight-integrations/obsidian
run: npm run build
- name: Run tests
working-directory: ./hindsight-integrations/obsidian
run: npm test
test-agent-framework-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-agent-framework == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build agent-framework integration
working-directory: ./hindsight-integrations/agent-framework
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/agent-framework
run: uv sync --frozen
- name: Lint
working-directory: ./hindsight-integrations/agent-framework
run: uv run ruff check .
- name: Run tests
working-directory: ./hindsight-integrations/agent-framework
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-crewai-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-crewai == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build crewai integration
working-directory: ./hindsight-integrations/crewai
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/crewai
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/crewai
run: uv run pytest tests -v
test-vapi-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-vapi == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build vapi integration
working-directory: ./hindsight-integrations/vapi
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/vapi
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/vapi
run: uv run pytest tests -v
test-superagent-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-superagent == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build superagent integration
working-directory: ./hindsight-integrations/superagent
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/superagent
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/superagent
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs live Hindsight + provider keys and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-litellm-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-litellm == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build litellm integration
working-directory: ./hindsight-integrations/litellm
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/litellm
run: uv sync --frozen --extra dev
- name: Run tests
working-directory: ./hindsight-integrations/litellm
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs live Hindsight + provider keys and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-pydantic-ai-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-pydantic-ai == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build pydantic-ai integration
working-directory: ./hindsight-integrations/pydantic-ai
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/pydantic-ai
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/pydantic-ai
run: uv run pytest tests -v
test-langgraph-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-langgraph == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build langgraph integration
working-directory: ./hindsight-integrations/langgraph
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/langgraph
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/langgraph
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-llamaindex-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-llamaindex == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build llamaindex integration
working-directory: ./hindsight-integrations/llamaindex
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/llamaindex
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/llamaindex
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-haystack-integration:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-haystack == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build haystack integration
working-directory: ./hindsight-integrations/haystack
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/haystack
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/haystack
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-openai-agents-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-openai-agents == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build openai-agents integration
working-directory: ./hindsight-integrations/openai-agents
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/openai-agents
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/openai-agents
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-openhands-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-openhands == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build openhands integration
working-directory: ./hindsight-integrations/openhands
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/openhands
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/openhands
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-claude-agent-sdk-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-claude-agent-sdk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build claude-agent-sdk integration
working-directory: ./hindsight-integrations/claude-agent-sdk
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/claude-agent-sdk
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/claude-agent-sdk
# PR CI runs only the deterministic bucket; the real-LLM E2E bucket
# (requires_real_llm) needs a live Hindsight server and runs separately.
run: uv run pytest tests -v -m "not requires_real_llm"
test-agentcore-integration:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.integrations-agentcore == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Build agentcore integration
working-directory: ./hindsight-integrations/agentcore
run: uv build
- name: Install dependencies
working-directory: ./hindsight-integrations/agentcore
run: uv sync --frozen
- name: Run tests
working-directory: ./hindsight-integrations/agentcore
run: uv run pytest tests -v
test-pip-slim:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_EMBEDDINGS_PROVIDER: cohere
HINDSIGHT_API_RERANKER_PROVIDER: cohere
HINDSIGHT_API_COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install hindsight-api-slim (embedded-db only, no local ML)
working-directory: ./hindsight-api-slim
run: uv sync --frozen --extra embedded-db --index-strategy unsafe-best-match
- name: Start API server
working-directory: ./hindsight-api-slim
run: |
uv run hindsight-api --port 8888 > /tmp/slim-api-server.log 2>&1 &
for i in $(seq 1 60); do
if curl -s http://localhost:8888/health | grep -q "healthy"; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 60 ]; then
echo "API server failed to start after 60s"
cat /tmp/slim-api-server.log
exit 1
fi
sleep 1
done
- name: Smoke test - retain and recall
run: ./scripts/smoke-test-slim.sh http://localhost:8888
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/slim-api-server.log 2>/dev/null || true
verify-embed-control-center-bundle:
# The control center UI (Preact + Tailwind) is built with Vite and its static
# output is committed (served as-is by the embed's Python http.server, no Node
# at runtime). We can't byte-diff the committed bundle against a fresh build —
# Vite's content-hashed asset filenames aren't reproducible across the CI
# runner's OS/arch vs the committer's. So instead verify: (1) the committed
# bundle is a real, wired Vite build (index.html references JS/CSS that exist),
# and (2) the source still builds cleanly.
needs: [detect-changes]
if: >-
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.embed == 'true' ||
needs.detect-changes.outputs.ci == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
# Check the committed bundle BEFORE building (the build overwrites static/).
- name: Verify the committed bundle is wired
working-directory: ./hindsight-embed/hindsight_embed/control_center
run: |
test -f static/index.html || { echo "::error::static/index.html missing — run 'npm run build' in control_center/ui and commit static/"; exit 1; }
js=$(grep -oE 'assets/[A-Za-z0-9_.-]+\.js' static/index.html | head -1)
css=$(grep -oE 'assets/[A-Za-z0-9_.-]+\.css' static/index.html | head -1)
{ [ -n "$js" ] && [ -f "static/$js" ]; } || { echo "::error::index.html does not reference a committed JS bundle — rebuild the UI and commit static/"; exit 1; }
{ [ -n "$css" ] && [ -f "static/$css" ]; } || { echo "::error::index.html does not reference a committed CSS bundle — rebuild the UI and commit static/"; exit 1; }
echo "committed bundle is wired ✓"
- name: Verify the source builds cleanly
working-directory: ./hindsight-embed/hindsight_embed/control_center/ui
run: |
npm ci
npm run build
echo "control center UI builds ✓"
test-embed:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.embed == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
# Prefer CPU-only PyTorch in CI
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install dependencies
working-directory: ./hindsight-embed
run: uv sync --frozen --index-strategy unsafe-best-match
- name: Install API dependencies (with local-ml and embedded-db for smoke test)
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-embed-${{ hashFiles('hindsight-embed/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-embed-
${{ runner.os }}-huggingface-
- name: Run unit and integration tests
working-directory: ./hindsight-embed
run: uv run pytest tests/ -v
- name: Run smoke test
working-directory: ./hindsight-embed
run: ./test.sh
test-embed-windows:
# Windows coverage for hindsight-embed. Runs the same unit tests + smoke
# test as the Linux `test-embed` job, plus a `uv pip install --target`
# sanity check that validates the sibling-binary resolution used by
# users who install via `uv pip install hindsight-all` on Windows
# (closes #1240).
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.embed == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: windows-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: ${{ github.workspace }}/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
# Force UTF-8 I/O so the CLI's ✓/box-drawing output doesn't crash the
# default Windows cp1252 codec. Also applied at runtime via
# sys.stdout.reconfigure in cli.py; this belt-and-suspenders covers
# subprocesses the daemon spawns.
PYTHONIOENCODING: utf-8
PYTHONUTF8: "1"
# pg0-embedded unpacks Postgres on first boot — noticeably slower on a
# cold Windows runner than POSIX. Double the embed startup budget.
HINDSIGHT_EMBED_DAEMON_STARTUP_TIMEOUT: "360"
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
shell: bash
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> "$GITHUB_ENV"
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install embed dependencies
working-directory: ./hindsight-embed
run: uv sync --frozen --index-strategy unsafe-best-match
- name: Install API dependencies (with local-ml and embedded-db for smoke test)
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-embed-${{ hashFiles('hindsight-embed/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-embed-
${{ runner.os }}-huggingface-
- name: Run unit and integration tests
working-directory: ./hindsight-embed
run: uv run pytest tests/ -v
# Smoke test's retain/recall commands delegate to the Rust hindsight CLI.
# On POSIX, hindsight-embed auto-installs the CLI via curl|bash; on
# Windows that installer isn't available (and `bash` on windows-latest
# routes to WSL which isn't provisioned). Build the CLI from source and
# drop it into ~/.local/bin where find_cli_binary() looks first.
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo build
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-cli/target
key: ${{ runner.os }}-cargo-embed-smoke-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-embed-smoke-
${{ runner.os }}-cargo-
- name: Build hindsight CLI
working-directory: ./hindsight-cli
run: cargo build --release
- name: Stage hindsight CLI where find_cli_binary expects it
shell: bash
run: |
set -euo pipefail
install_dir="$HOME/.local/bin"
mkdir -p "$install_dir"
cp hindsight-cli/target/release/hindsight.exe "$install_dir/hindsight.exe"
"$install_dir/hindsight.exe" --version
- name: Run smoke test
shell: bash
working-directory: ./hindsight-embed
run: ./test.sh
# Real-world install test for issue #1240: drop both packages into a
# --target directory (the layout you get from `uv pip install hindsight-all`
# or NixOS) and verify the sibling binary is discovered (not the uvx
# fallback). Exercises a different code path than the smoke test, which
# uses `uv run --project` via the monorepo branch of _find_api_command.
#
# IMPORTANT: install outside the repo checkout. `_find_api_command` first
# probes `<pkg>/../../hindsight-api-slim` for dev mode; if the target dir
# lives inside the monorepo, that branch matches and we never exercise
# the sibling-binary path we actually want to test.
- name: Install hindsight-embed and hindsight-api into --target directory
shell: bash
run: |
set -euo pipefail
target="$RUNNER_TEMP/install-test"
rm -rf "$target"
mkdir -p "$target"
uv pip install --target "$target" ./hindsight-embed ./hindsight-api-slim
- name: Verify sibling hindsight-api.exe is present
shell: bash
run: |
set -euo pipefail
target="$RUNNER_TEMP/install-test"
if [ -f "$target/Scripts/hindsight-api.exe" ]; then
echo "Found $target/Scripts/hindsight-api.exe"
elif [ -f "$target/bin/hindsight-api.exe" ]; then
echo "Found $target/bin/hindsight-api.exe"
else
echo "::error::hindsight-api.exe not found in install target"
ls "$target/"
exit 1
fi
- name: Verify _find_api_command resolves the sibling binary (not uvx)
shell: bash
run: |
set -euo pipefail
target="$RUNNER_TEMP/install-test"
PYTHONPATH="$target" python -c "
from hindsight_embed.daemon_embed_manager import DaemonEmbedManager
# api_version is only used for the uvx fallback; the binary branch ignores it.
cmd = DaemonEmbedManager()._find_api_command('0.0.0')
print('Resolved command:', cmd)
assert len(cmd) == 1 and cmd[0].endswith('hindsight-api.exe'), (
f'Expected sibling hindsight-api.exe, got {cmd!r}. '
'Falling back to uvx on --target installs reintroduces issue #1240.'
)
"
- name: Smoke-check installed hindsight-embed binary runs
shell: bash
run: |
set -euo pipefail
target="$RUNNER_TEMP/install-test"
export PYTHONPATH="$target"
if [ -f "$target/Scripts/hindsight-embed.exe" ]; then
"$target/Scripts/hindsight-embed.exe" --help
else
"$target/bin/hindsight-embed.exe" --help
fi
- name: Collect daemon logs on failure
if: failure()
shell: bash
run: |
for f in ~/.hindsight/daemon.log ~/.hindsight/profiles/*.log ~/.hindsight/profiles/*.stderr.log; do
if [ -f "$f" ]; then
echo "=== $f ==="
cat "$f"
fi
done || true
test-hindsight-all:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.hindsight-all == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
# For test_server_integration.py compatibility
HINDSIGHT_LLM_PROVIDER: vertexai
HINDSIGHT_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_LLM_MODEL: google/gemini-2.5-flash-lite
# Prefer CPU-only PyTorch in CI
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install SDK dependencies
run: npm ci --workspace=hindsight-clients/typescript
- name: Build SDK
run: npm run build --workspace=hindsight-clients/typescript
- name: Install Control Plane dependencies
run: |
npm install --workspace=hindsight-control-plane
rm -rf node_modules/lightningcss node_modules/@tailwindcss
npm install lightningcss @tailwindcss/postcss @tailwindcss/node
- name: Test Control Plane
run: npm test --workspace=hindsight-control-plane
- name: Check i18n locale parity and hardcoded strings
run: npm run i18n:check --workspace=hindsight-control-plane
- name: Build Control Plane
run: npm run build --workspace=hindsight-control-plane
- name: Build hindsight-all
working-directory: ./hindsight-all
run: uv build
- name: Install dependencies
working-directory: ./hindsight-all
run: uv sync --frozen --extra test --index-strategy unsafe-best-match
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-all-${{ hashFiles('hindsight-all/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-all-
${{ runner.os }}-huggingface-
- name: Run unit tests
working-directory: ./hindsight-all
run: uv run pytest tests/ -v
test-doc-examples:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.clients-ts == 'true' ||
needs.detect-changes.outputs.clients-python == 'true' ||
needs.detect-changes.outputs.clients-go == 'true' ||
needs.detect-changes.outputs.cli == 'true' ||
needs.detect-changes.outputs.docs == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
language: [python, node, cli, go]
name: test-doc-examples (${{ matrix.language }})
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
HINDSIGHT_API_URL: http://localhost:8888
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Install Rust
if: matrix.language == 'cli'
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
if: matrix.language == 'cli'
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
hindsight-cli/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build CLI
if: matrix.language == 'cli'
working-directory: hindsight-cli
run: |
cargo build --release
cp target/release/hindsight /usr/local/bin/hindsight
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
if: matrix.language == 'node'
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install Python client dependencies
if: matrix.language == 'python'
working-directory: ./hindsight-clients/python
run: uv sync --frozen --extra test --index-strategy unsafe-best-match
- name: Build and install API
working-directory: ./hindsight-api-slim
run: |
uv build
uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Install TypeScript client
if: matrix.language == 'node'
run: |
npm ci --workspace=hindsight-clients/typescript
npm run build --workspace=hindsight-clients/typescript
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading reranker model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Create .env file
run: |
cat > .env << EOF
HINDSIGHT_API_LLM_PROVIDER=${{ env.HINDSIGHT_API_LLM_PROVIDER }}
HINDSIGHT_API_LLM_MODEL=${{ env.HINDSIGHT_API_LLM_MODEL }}
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY=/tmp/gcp-credentials.json
HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID
EOF
- name: Start API server
run: |
./scripts/dev/start-api.sh > /tmp/api-server.log 2>&1 &
echo "Waiting for API server to be ready..."
for i in {1..120}; do
if curl -sf http://localhost:8888/health > /dev/null 2>&1; then
echo "API server is ready after ${i}s"
break
fi
if [ $i -eq 120 ]; then
echo "API server failed to start after 120s"
cat /tmp/api-server.log
exit 1
fi
sleep 1
done
- name: Configure CLI
if: matrix.language == 'cli'
run: hindsight configure --api-url http://localhost:8888
- name: Run doc examples (${{ matrix.language }})
run: ./scripts/test-doc-examples.sh --lang ${{ matrix.language }}
- name: Show API server logs
if: always()
run: |
echo "=== API Server Logs ==="
cat /tmp/api-server.log || echo "No API server log found"
test-upgrade:
needs: [detect-changes]
if: >-
needs.detect-changes.outputs.has_secrets == 'true' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.dev == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
HINDSIGHT_API_LLM_PROVIDER: vertexai
HINDSIGHT_API_LLM_VERTEXAI_SERVICE_ACCOUNT_KEY: /tmp/gcp-credentials.json
HINDSIGHT_API_LLM_MODEL: google/gemini-2.5-flash-lite
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
fetch-depth: 0 # Full history needed for git clone of tags
- name: Setup GCP credentials
run: |
printf '%s' '${{ secrets.GCP_VERTEXAI_CREDENTIALS }}' > /tmp/gcp-credentials.json
PROJECT_ID=$(jq -r '.project_id' /tmp/gcp-credentials.json)
echo "HINDSIGHT_API_LLM_VERTEXAI_PROJECT_ID=$PROJECT_ID" >> $GITHUB_ENV
- name: Fetch tags
run: git fetch --tags
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
prune-cache: false
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Cache HuggingFace models
uses: actions/cache@v5
with:
path: ~/.cache/huggingface
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-huggingface-
- name: Install hindsight-dev dependencies
working-directory: ./hindsight-dev
run: uv sync --frozen --extra test --index-strategy unsafe-best-match
- name: Install current hindsight-api
working-directory: ./hindsight-api-slim
run: uv sync --frozen --all-extras --index-strategy unsafe-best-match
- name: Pre-download models
working-directory: ./hindsight-api-slim
run: |
uv run python -c "
from sentence_transformers import SentenceTransformer, CrossEncoder
print('Downloading embedding model...')
SentenceTransformer('BAAI/bge-small-en-v1.5')
print('Downloading cross-encoder model...')
CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
print('Models downloaded successfully')
"
- name: Run upgrade tests
working-directory: ./hindsight-dev
run: uv run pytest upgrade_tests/ -v --tb=short
- name: Show upgrade test logs
if: always()
run: |
echo "=== Upgrade Test Server Logs ==="
for log in /tmp/upgrade-test-*.log; do
if [ -f "$log" ]; then
echo ""
echo "--- $log ---"
tail -500 "$log"
fi
done
# Dead-code detection beyond what ruff's F401/F841 catch (those are already
# BLOCKING via the ruff config + the verify-generated-files job).
#
# - knip (control plane): BLOCKING on unused files / dependencies / unlisted
# dependencies. These are unambiguous — an orphaned file or a dead
# package.json entry — so they fail the build.
# - vulture (Python) + knip unused *exports*: ADVISORY only. vulture's
# function/argument heuristics false-positive on FastAPI/SQLAlchemy/Pydantic
# patterns, and the control plane intentionally keeps an unused shadcn/ui
# component surface, so these are surfaced in the step summary, not gated.
check-unused-code:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.control-plane == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install Control Plane dependencies
run: npm install --workspace=hindsight-control-plane
- name: knip — unused files / dependencies (blocking)
working-directory: hindsight-control-plane
run: npx --yes knip@5 --no-progress --include files,dependencies,unlisted
- name: Advisory scan — vulture + knip exports
continue-on-error: true
run: |
{
echo '## Dead-code scan (advisory)'
echo ''
echo '```'
./scripts/hooks/check-unused.sh 2>&1 | sed 's/\x1b\[[0-9;]*m//g'
echo '```'
} | tee -a "$GITHUB_STEP_SUMMARY"
verify-generated-files:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
UV_FROZEN: "1"
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-gen-${{ hashFiles('**/Cargo.lock') }}
- name: Install Node dependencies
run: npm ci
- name: Install Python dependencies
run: |
cd hindsight-dev && uv sync --frozen --index-strategy unsafe-best-match
cd ../hindsight-api && uv sync --frozen --index-strategy unsafe-best-match
cd ../hindsight-embed && uv sync --frozen --index-strategy unsafe-best-match
- name: Run generate-openapi
run: ./scripts/generate-openapi.sh
- name: Run generate-bank-template-schema
run: ./scripts/generate-bank-template-schema.sh
- name: Run generate-clients
run: ./scripts/generate-clients.sh
- name: Run generate-docs-skill
run: ./scripts/generate-docs-skill.sh
- name: Run lint
run: ./scripts/hooks/lint.sh
- name: Verify no uncommitted changes
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "❌ Error: Generated files are out of sync with committed files."
echo ""
echo "The following files have changed after running generation scripts:"
git status --porcelain
echo ""
echo "Please run the following commands locally and commit the changes:"
echo " ./scripts/generate-openapi.sh"
echo " ./scripts/generate-bank-template-schema.sh"
echo " ./scripts/generate-clients.sh"
echo " ./scripts/generate-docs-skill.sh"
echo " ./scripts/hooks/lint.sh"
echo ""
git diff --stat
exit 1
fi
echo "✓ All generated files are up to date"
check-openapi-compatibility:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
fetch-depth: 0 # Fetch full git history to access base branch
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install hindsight-dev dependencies
run: |
cd hindsight-dev && uv sync --frozen --index-strategy unsafe-best-match
- name: Check OpenAPI compatibility with base branch
run: |
# Get the base branch (usually main)
BASE_BRANCH="${{ github.base_ref }}"
if [ -z "$BASE_BRANCH" ]; then
echo "⚠️ Warning: No base branch found (not a PR?). Skipping compatibility check."
exit 0
fi
echo "Checking OpenAPI compatibility against base branch: $BASE_BRANCH"
# Extract the old OpenAPI spec from base branch
git show "origin/$BASE_BRANCH:hindsight-docs/static/openapi.json" > /tmp/old-openapi.json
if [ ! -s /tmp/old-openapi.json ]; then
echo "⚠️ Warning: Could not find OpenAPI spec in base branch. Skipping compatibility check."
exit 0
fi
# Check compatibility using our tool
cd hindsight-dev
uv run check-openapi-compatibility /tmp/old-openapi.json ../hindsight-docs/static/openapi.json
check-cli-coverage:
needs: [detect-changes]
if: >-
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.core == 'true' ||
needs.detect-changes.outputs.cli == 'true' ||
needs.detect-changes.outputs.dev == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version-file: ".python-version"
- name: Install hindsight-dev dependencies
run: |
cd hindsight-dev && uv sync --frozen --index-strategy unsafe-best-match
- name: Check CLI covers every OpenAPI operation
run: |
cd hindsight-dev
uv run cli-coverage-check
# Report CI status back to the PR for pull_request_review events.
# GitHub does not automatically link pull_request_review check runs to the PR,
# so we create a commit status on the PR head SHA and post a comment.
report-pr-status:
if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && always()
needs:
- detect-changes
- check-integration-lockfiles
- build-api-python-versions
- build-typescript-client
- build-openclaw-integration
- smoke-openclaw-install
- test-claude-code-integration
- test-cursor-integration
- test-cline-integration
- test-codex-integration
- test-cursor-cli-integration
- build-ai-sdk-integration
- test-ai-sdk-integration-deno
- test-opencode-integration
- test-eve-integration
- test-omo-integration
- test-cloudflare-oauth-proxy-integration
- build-chat-integration
- test-paperclip-integration
- test-pipecat-integration
- test-gemini-spark-integration
- test-vapi-integration
- test-google-adk-integration
- test-roo-code-integration
- build-control-plane
- build-docs
- test-rust-cli
- lint-helm-chart
- test-standalone-start-script
- build-docker-images
- test-api
- test-api-oracle
- test-python-client
- test-python-client-oracle
- test-typescript-client
- test-typescript-client-oracle
- test-typescript-client-deno
- build-rust-cli-arm64
- test-rust-client
- test-go-client
- test-openclaw-integration
- test-integration
- test-ag2-integration
- test-aider-integration
- test-autogen-integration
- test-continue-integration
- test-smolagents-integration
- test-dify-integration
- test-flowise-integration
- test-obsidian-integration
- test-agent-framework-integration
- test-crewai-integration
- test-langgraph-integration
- test-superagent-integration
- test-litellm-integration
- test-pydantic-ai-integration
- test-llamaindex-integration
- test-openai-agents-integration
- test-openhands-integration
- test-agentcore-integration
- test-haystack-integration
- test-pip-slim
- test-embed
- test-embed-windows
- verify-embed-control-center-bundle
- test-hindsight-all
- test-hindsight-agent-sdk
- test-claude-agent-sdk-integration
- test-doc-examples
- test-upgrade
- verify-generated-files
- check-openapi-compatibility
- check-cli-coverage
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
statuses: write
pull-requests: write
steps:
- name: Determine overall result
id: result
uses: actions/github-script@v9
with:
script: |
const needs = ${{ toJSON(needs) }};
const entries = Object.entries(needs);
const failed = entries.filter(([, v]) => v.result === 'failure');
const skipped = entries.filter(([, v]) => v.result === 'skipped');
const succeeded = entries.filter(([, v]) => v.result === 'success');
const overall = failed.length > 0 ? 'failure' : 'success';
const icon = overall === 'success' ? '✅' : '❌';
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
let body = `## ${icon} CI results\n\n`;
body += `| Status | Count |\n|--------|-------|\n`;
body += `| ✅ Passed | ${succeeded.length} |\n`;
if (failed.length > 0) body += `| ❌ Failed | ${failed.length} |\n`;
if (skipped.length > 0) body += `| ⏭️ Skipped | ${skipped.length} |\n`;
if (failed.length > 0) {
body += `\n### Failed jobs\n`;
for (const [name] of failed) {
body += `- \`${name}\`\n`;
}
}
body += `\n[View full run](${runUrl})`;
core.setOutput('state', overall);
core.setOutput('description', failed.length > 0 ? 'Some CI checks failed' : 'All CI checks passed');
core.setOutput('body', body);
core.setOutput('run_url', runUrl);
- name: Report status to PR
uses: actions/github-script@v9
with:
script: |
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha: context.payload.pull_request.head.sha,
state: '${{ steps.result.outputs.state }}',
context: 'CI / full-tests',
description: '${{ steps.result.outputs.description }}',
target_url: '${{ steps.result.outputs.run_url }}'
});
- name: Comment on PR
uses: actions/github-script@v9
with:
script: |
const prNumber = context.payload.pull_request.number;
const marker = '<!-- ci-full-test-report -->';
const body = `${marker}\n${{ steps.result.outputs.body }}`;
// Update existing comment if present, otherwise create new one
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}