Compare commits

...
Author SHA1 Message Date
Nicolò Boschi a104e616d3 docker fixes 2025-12-18 11:43:08 +01:00
Nicolò Boschi 2b24ff85d5 docker fixes 2025-12-18 11:37:52 +01:00
Nicolò Boschi 29b7955a55 docker fixes 2025-12-18 11:31:14 +01:00
Nicolò Boschi 0a8ac1d050 Merge branch 'main' into smokeci 2025-12-18 11:22:03 +01:00
Nicolò Boschi 1949c28ad8 docker fixes 2025-12-18 11:15:50 +01:00
Nicolò Boschi 0e73bbf3a0 docker fixes 2025-12-18 11:00:49 +01:00
Nicolò Boschi 7982c48d3d comment out 2025-12-18 10:23:06 +01:00
Nicolò Boschi 3d5d360806 fix space 2025-12-18 09:05:06 +01:00
Nicolò Boschi 9de2e90c46 fix space 2025-12-18 09:04:31 +01:00
Nicolò Boschi b0dbbc51bb fix 2025-12-17 22:57:50 +01:00
Nicolò Boschi 3b479ebee0 fix alpine version 2025-12-17 22:15:20 +01:00
Nicolò Boschi 95127e13be ci: add docker smoke test to ci 2025-12-17 22:03:53 +01:00
12 changed files with 205 additions and 73 deletions
+21 -21
View File
@@ -222,7 +222,7 @@ jobs:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
tool-cache: true
android: true
dotnet: true
haskell: true
@@ -258,28 +258,28 @@ jobs:
type=semver,pattern={{major}},value=${{ steps.get_version.outputs.VERSION }}
type=raw,value=latest
# Step 1: Build for local testing (single platform, no push)
# This creates an identical image to what will be released, just for one platform
- name: Build image for testing
uses: docker/build-push-action@v6
with:
context: .
file: docker/standalone/Dockerfile
target: ${{ matrix.target }}
push: false
load: true
tags: ${{ matrix.image_name }}:test
cache-from: type=gha
cache-to: type=gha,mode=max
# TODO: Re-enable smoke test when disk space issue is resolved
# # Step 1: Build for local testing (single platform, no push)
# # This creates an identical image to what will be released, just for one platform
# - name: Build image for testing
# uses: docker/build-push-action@v6
# with:
# context: .
# file: docker/standalone/Dockerfile
# target: ${{ matrix.target }}
# push: false
# load: true
# tags: ${{ matrix.image_name }}:test
# cache-from: type=gha
# cache-to: type=gha,mode=max
# Step 2: Test the image before pushing anything
- name: Smoke test - verify container starts
env:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
run: ./scripts/docker-smoke-test.sh "${{ matrix.image_name }}:test" "${{ matrix.target }}"
# # Step 2: Test the image before pushing anything
# - name: Smoke test - verify container starts
# env:
# GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
# run: ./scripts/docker-smoke-test.sh "${{ matrix.image_name }}:test" "${{ matrix.target }}"
# Step 3: Only if smoke test passed, build multi-platform and push to release tags
# Build layers are cached, so this is fast - just builds the other platform
# Build multi-platform and push to release tags
- name: Build and push release images
uses: docker/build-push-action@v6
with:
+22 -5
View File
@@ -93,13 +93,21 @@ jobs:
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install dependencies
run: npm ci
- name: Install SDK dependencies
run: npm ci --workspace=hindsight-clients/typescript
- name: Build TypeScript client (dependency)
- name: Build SDK
run: npm run build --workspace=hindsight-clients/typescript
- name: Build control plane
# 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: Build Control Plane
run: npm run build --workspace=hindsight-control-plane
- name: Verify standalone build
@@ -180,7 +188,7 @@ jobs:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
tool-cache: true
android: true
dotnet: true
haskell: true
@@ -198,6 +206,15 @@ jobs:
file: docker/standalone/Dockerfile
target: ${{ matrix.target }}
push: false
load: false
cache-from: type=gha
cache-to: type=gha,mode=max
# TODO: Re-enable smoke test when disk space issue is resolved
# - name: Smoke test - verify container starts
# env:
# GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
# run: ./scripts/docker-smoke-test.sh "hindsight-${{ matrix.name }}:test" "${{ matrix.target }}"
test-api:
runs-on: ubuntu-latest
+4 -2
View File
@@ -114,6 +114,7 @@ FROM python:3.11-slim AS api-only
WORKDIR /app
# Install pg0 dependencies (procps provides 'kill' command needed by pg0)
# Note: libicu version varies by Debian version - try common versions in order
RUN apt-get update && apt-get install -y \
curl \
procps \
@@ -121,7 +122,7 @@ RUN apt-get update && apt-get install -y \
libssl3 \
libgssapi-krb5-2 \
libossp-uuid16 \
&& apt-get install -y libicu72 || apt-get install -y libicu74 || apt-get install -y libicu* \
&& (apt-get install -y libicu72 2>/dev/null || apt-get install -y libicu74 2>/dev/null || apt-get install -y libicu76 2>/dev/null || true) \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir uv
@@ -211,6 +212,7 @@ FROM python:3.11-slim AS standalone
WORKDIR /app
# Install Node.js, curl, uv, and pg0 dependencies (procps provides 'kill' command needed by pg0)
# Note: libicu version varies by Debian version - try common versions in order
RUN apt-get update && apt-get install -y \
curl \
procps \
@@ -218,7 +220,7 @@ RUN apt-get update && apt-get install -y \
libssl3 \
libgssapi-krb5-2 \
libossp-uuid16 \
&& apt-get install -y libicu72 || apt-get install -y libicu74 || apt-get install -y libicu* \
&& (apt-get install -y libicu72 2>/dev/null || apt-get install -y libicu74 2>/dev/null || apt-get install -y libicu76 2>/dev/null || true) \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/* \
+3 -2
View File
@@ -23,7 +23,8 @@ PIDS=()
# Start API if enabled
if [ "$ENABLE_API" = "true" ]; then
cd /app/api
hindsight-api 2>&1 | sed -u 's/^/[api] /' &
# Run API directly - Python's PYTHONUNBUFFERED=1 handles output buffering
hindsight-api &
API_PID=$!
PIDS+=($API_PID)
@@ -42,7 +43,7 @@ fi
if [ "$ENABLE_CP" = "true" ]; then
echo "🎛️ Starting Control Plane..."
cd /app/control-plane
PORT=9999 node server.js 2>&1 | grep -v -E "^[[:space:]]*(▲|✓|-|$)" | sed -u 's/^/[control-plane] /' &
PORT=9999 node server.js &
CP_PID=$!
PIDS+=($CP_PID)
else
+7
View File
@@ -1,7 +1,14 @@
import type { NextConfig } from "next";
import path from "path";
const nextConfig: NextConfig = {
output: 'standalone',
// Disable request logging in production
logging: false,
// Set the monorepo root explicitly to avoid detecting wrong lockfiles in parent directories
turbopack: {
root: path.resolve(__dirname, '..'),
},
};
export default nextConfig;
@@ -0,0 +1,35 @@
import { NextResponse } from "next/server";
import { sdk, lowLevelClient } from "@/lib/hindsight-client";
export async function GET() {
const status: {
status: string;
service: string;
dataplane?: {
status: string;
url: string;
error?: string;
};
} = {
status: "ok",
service: "hindsight-control-plane",
};
// Check dataplane connectivity
const dataplaneUrl = process.env.HINDSIGHT_CP_DATAPLANE_API_URL || "http://localhost:8888";
try {
await sdk.listBanks({ client: lowLevelClient });
status.dataplane = {
status: "connected",
url: dataplaneUrl,
};
} catch (error) {
status.dataplane = {
status: "disconnected",
url: dataplaneUrl,
error: error instanceof Error ? error.message : String(error),
};
}
return NextResponse.json(status, { status: 200 });
}
@@ -7,17 +7,6 @@ export async function POST(request: NextRequest) {
const bankId = body.bank_id || body.agent_id || "default";
const { query, types, fact_type, max_tokens, trace, budget, include, query_timestamp } = body;
console.log("[Recall API] Request:", {
bankId,
query,
types: types || fact_type,
max_tokens,
trace,
budget,
query_timestamp,
});
console.log("[Recall API] Include options:", JSON.stringify(include, null, 2));
const response = await sdk.recallMemories({
client: lowLevelClient,
path: { bank_id: bankId },
@@ -37,18 +26,6 @@ export async function POST(request: NextRequest) {
throw new Error(`API returned no data: ${JSON.stringify(response.error || "Unknown error")}`);
}
console.log("[Recall API] Response structure:", {
hasResults: !!response.data?.results,
resultsCount: response.data?.results?.length,
hasTrace: !!response.data?.trace,
hasEntities: !!response.data?.entities,
entitiesType: typeof response.data?.entities,
entitiesKeys: response.data?.entities ? Object.keys(response.data.entities) : null,
hasChunks: !!response.data?.chunks,
chunksType: typeof response.data?.chunks,
chunksKeys: response.data?.chunks ? Object.keys(response.data.chunks) : null,
});
// Return a clean JSON object by spreading the response
// This ensures any non-serializable properties are excluded
const jsonResponse = {
@@ -102,12 +102,6 @@ export function DataView({ factType }: DataViewProps) {
bank_id: currentBank,
type: factType,
});
console.log("Loaded graph data:", {
total_units: graphData.total_units,
nodes: graphData.nodes?.length,
edges: graphData.edges?.length,
table_rows: graphData.table_rows?.length,
});
setData(graphData);
} catch (error) {
console.error("Error loading data:", error);
@@ -191,10 +185,6 @@ export function DataView({ factType }: DataViewProps) {
otherTypes[type] = (otherTypes[type] || 0) + 1;
}
});
console.log("Graph link stats:", { semantic, temporal, entity, causal, total });
if (Object.keys(otherTypes).length > 0) {
console.log("Other link types:", otherTypes);
}
return { semantic, temporal, entity, causal, total, otherTypes };
}, [graph2DData]);
@@ -53,7 +53,7 @@ export HINDSIGHT_API_LLM_MODEL=gemini-2.0-flash
# Ollama (local, no API key)
export HINDSIGHT_API_LLM_PROVIDER=ollama
export HINDSIGHT_API_LLM_BASE_URL=http://localhost:11434/v1
export HINDSIGHT_API_LLM_MODEL=llama3.1
export HINDSIGHT_API_LLM_MODEL=gpt-oss-20b
# OpenAI-compatible endpoint
export HINDSIGHT_API_LLM_PROVIDER=openai
+1 -1
View File
@@ -64,7 +64,7 @@ export HINDSIGHT_API_LLM_MODEL=gemini-2.0-flash
# Ollama (local)
export HINDSIGHT_API_LLM_PROVIDER=ollama
export HINDSIGHT_API_LLM_BASE_URL=http://localhost:11434/v1
export HINDSIGHT_API_LLM_MODEL=llama3.1
export HINDSIGHT_API_LLM_MODEL=gpt-oss-20b
```
**Note:** The LLM is the primary bottleneck for retain operations. See [Performance](./performance) for optimization strategies.
+105 -4
View File
@@ -19624,8 +19624,6 @@
},
"node_modules/raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"license": "MIT",
"dependencies": {
"loader-utils": "^2.0.0",
@@ -19644,8 +19642,6 @@
},
"node_modules/raw-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.8",
@@ -23999,6 +23995,111 @@
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.10.tgz",
"integrity": "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.10.tgz",
"integrity": "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.10.tgz",
"integrity": "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.10.tgz",
"integrity": "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.10.tgz",
"integrity": "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.10.tgz",
"integrity": "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.0.10",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz",
"integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}
+6 -4
View File
@@ -67,9 +67,11 @@ fi
# Determine health endpoint based on target
if [ "$TARGET" = "cp-only" ]; then
HEALTH_PORT=9999
HEALTH_PATH="/api/health"
NEEDS_LLM=false
else
HEALTH_PORT=8888
HEALTH_PATH="/health"
NEEDS_LLM=true
fi
@@ -92,7 +94,7 @@ trap cleanup EXIT
echo -e "${YELLOW}Starting smoke test for: ${IMAGE}${NC}"
echo " Target: $TARGET"
echo " Health port: $HEALTH_PORT"
echo " Health endpoint: http://localhost:${HEALTH_PORT}${HEALTH_PATH}"
echo " Timeout: ${TIMEOUT}s"
echo ""
@@ -115,18 +117,18 @@ else
fi
# Wait for health endpoint
echo "Waiting for health endpoint on port ${HEALTH_PORT}..."
echo "Waiting for health endpoint at http://localhost:${HEALTH_PORT}${HEALTH_PATH}..."
start_time=$(date +%s)
for i in $(seq 1 "$TIMEOUT"); do
if curl -sf "http://localhost:${HEALTH_PORT}/health" > /dev/null 2>&1; then
if curl -sf "http://localhost:${HEALTH_PORT}${HEALTH_PATH}" > /dev/null 2>&1; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo ""
echo -e "${GREEN}Container is healthy after ${duration}s${NC}"
echo ""
echo "=== Health Response ==="
curl -s "http://localhost:${HEALTH_PORT}/health" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:${HEALTH_PORT}/health"
curl -s "http://localhost:${HEALTH_PORT}${HEALTH_PATH}" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:${HEALTH_PORT}${HEALTH_PATH}"
echo ""
echo ""
echo "=== Container Logs (last 50 lines) ==="