Skip to content

Commit cde955f

Browse files
DK09876claude
andcommitted
Merge origin/main into feature/database-abstraction
Resolves conflicts in: - memory_engine.py: integrated statement_timeout init callback with backend.initialize() abstraction - retrieval.py: added extra_where param to dialect build_semantic_arm() and build_bm25_arm() methods to support created_after/created_before time range filtering from main - openapi.json: took main's version (0.5.4) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2 parents 98333df + 0830480 commit cde955f

398 files changed

Lines changed: 9218 additions & 820 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
env:
3535
UMAMI_URL: https://analytics.hindsight.vectorize.io
3636
UMAMI_WEBSITE_ID: ${{ secrets.UMAMI_WEBSITE_ID }}
37-
- uses: actions/upload-pages-artifact@v4
37+
- uses: actions/upload-pages-artifact@v5
3838
with:
3939
path: hindsight-docs/build
4040
deploy:

.github/workflows/perf-test.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: Performance Tests
2+
3+
on:
4+
schedule:
5+
# Run daily at 06:00 UTC
6+
- cron: "0 6 * * *"
7+
workflow_dispatch:
8+
inputs:
9+
scale:
10+
description: "Test scale"
11+
type: choice
12+
options:
13+
- tiny
14+
- small
15+
- medium
16+
- large
17+
default: large
18+
suite:
19+
description: "Suite to run (blank = all)"
20+
type: choice
21+
options:
22+
- ""
23+
- retain
24+
- recall
25+
default: ""
26+
ref:
27+
description: "Git ref to test (branch, tag, or SHA). Defaults to main."
28+
type: string
29+
default: ""
30+
31+
concurrency:
32+
group: perf-test
33+
cancel-in-progress: true
34+
35+
jobs:
36+
perf-test:
37+
runs-on: ubuntu-latest
38+
steps:
39+
- uses: actions/checkout@v6
40+
with:
41+
ref: ${{ inputs.ref || github.ref }}
42+
43+
- name: Install uv
44+
uses: astral-sh/setup-uv@v7
45+
with:
46+
enable-cache: true
47+
prune-cache: false
48+
49+
- name: Set up Python
50+
uses: actions/setup-python@v6
51+
with:
52+
python-version-file: ".python-version"
53+
54+
- name: Cache HuggingFace models
55+
uses: actions/cache@v5
56+
with:
57+
path: ~/.cache/huggingface
58+
key: ${{ runner.os }}-huggingface-${{ hashFiles('hindsight-api-slim/pyproject.toml') }}
59+
restore-keys: |
60+
${{ runner.os }}-huggingface-
61+
62+
- name: Pre-download models
63+
working-directory: ./hindsight-api-slim
64+
run: |
65+
uv run --frozen --all-extras --index-strategy unsafe-best-match python -c "
66+
from sentence_transformers import SentenceTransformer
67+
print('Downloading embedding model...')
68+
SentenceTransformer('BAAI/bge-small-en-v1.5')
69+
print('Model downloaded successfully')
70+
"
71+
72+
- name: Install hindsight-dev dependencies
73+
run: |
74+
cd hindsight-dev && uv sync --frozen --all-extras --index-strategy unsafe-best-match
75+
76+
- name: Run perf tests
77+
run: |
78+
SUITE_ARG=""
79+
if [ -n "${{ inputs.suite }}" ]; then
80+
SUITE_ARG="--suite ${{ inputs.suite }}"
81+
fi
82+
./scripts/benchmarks/run-perf-test.sh \
83+
--scale ${{ inputs.scale || 'large' }} \
84+
$SUITE_ARG \
85+
--output perf-results.json
86+
87+
- name: Upload perf results
88+
if: always()
89+
uses: actions/upload-artifact@v4
90+
with:
91+
name: perf-results-${{ github.sha }}
92+
path: hindsight-dev/perf-results.json
93+
retention-days: 90

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ cd hindsight-control-plane && npm run dev
6868
./scripts/benchmarks/run-locomo.sh
6969

7070
# Performance benchmarks
71+
./scripts/benchmarks/run-perf-test.sh # System perf (mock LLM + pg0)
72+
./scripts/benchmarks/run-perf-test.sh --scale tiny # Quick smoke test
7173
./scripts/benchmarks/run-consolidation.sh
72-
./scripts/benchmarks/run-retain-perf.sh --document <path> # Requires API server running
7374

7475
# Results viewer
7576
./scripts/benchmarks/start-visualizer.sh # View results at localhost:8001

helm/hindsight/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: hindsight
33
description: Hindsight helm chart
44
type: application
5-
version: 0.5.3
6-
appVersion: "0.5.3"
5+
version: 0.5.4
6+
appVersion: "0.5.4"
77
keywords:
88
- ai
99
- memory

hindsight-all-npm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vectorize-io/hindsight-all",
3-
"version": "0.5.3",
3+
"version": "0.5.4",
44
"description": "Node.js programmatic lifecycle manager for Hindsight — embeds a local hindsight daemon in a Node application. Pair with @vectorize-io/hindsight-client for memory operations.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

hindsight-all-slim/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "hindsight-all-slim"
7-
version = "0.5.3"
7+
version = "0.5.4"
88
description = "Hindsight: Agent Memory That Works Like Human Memory - Slim All-in-One Bundle"
99
readme = "README.md"
1010
requires-python = ">=3.11"

hindsight-all/hindsight/embedded.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class HindsightEmbedded:
7171
llm_model: Model name to use
7272
llm_base_url: Optional custom base URL for LLM API
7373
database_url: Optional database URL override (default: profile-specific pg0)
74-
idle_timeout: Seconds before daemon auto-exits when idle (default: 300)
74+
idle_timeout: Seconds before daemon auto-exits when idle (default: 0, disabled)
7575
log_level: Daemon log level (default: "info")
7676
ui: Whether to start the control plane web UI alongside the daemon (default: False)
7777
ui_port: Port for the UI. Defaults to daemon_port + 10000.
@@ -86,7 +86,7 @@ def __init__(
8686
llm_model: str = "openai/gpt-oss-120b",
8787
llm_base_url: Optional[str] = None,
8888
database_url: Optional[str] = None,
89-
idle_timeout: int = 300,
89+
idle_timeout: int = 0,
9090
log_level: str = "info",
9191
ui: bool = False,
9292
ui_port: Optional[int] = None,
@@ -102,7 +102,7 @@ def __init__(
102102
llm_model: Model name to use
103103
llm_base_url: Optional custom base URL for LLM API
104104
database_url: Optional database URL override
105-
idle_timeout: Seconds before daemon auto-exits when idle
105+
idle_timeout: Seconds before daemon auto-exits when idle (0 = disabled)
106106
log_level: Daemon log level
107107
ui: Whether to start the control plane web UI alongside the daemon
108108
ui_port: Port for the UI (defaults to daemon_port + 10000)
@@ -142,14 +142,37 @@ def __init__(
142142
self._memories_api: Optional[MemoriesAPI] = None
143143

144144
def _ensure_started(self):
145-
"""Ensure daemon is running (thread-safe)."""
145+
"""Ensure daemon is running (thread-safe), restarting if crashed."""
146146
if self._started and self._client is not None:
147-
return
147+
if self._manager.is_running(self.profile):
148+
return
149+
# Daemon crashed — reset state and fall through to restart
150+
logger.warning(
151+
"Daemon for profile '%s' is no longer responsive, restarting...",
152+
self.profile,
153+
)
154+
try:
155+
self._client.close()
156+
except Exception:
157+
logger.debug("Error closing stale client", exc_info=True)
158+
self._client = None
159+
self._started = False
148160

149161
with self._lock:
150162
# Double-check after acquiring lock
151163
if self._started and self._client is not None:
152-
return
164+
if self._manager.is_running(self.profile):
165+
return
166+
logger.warning(
167+
"Daemon for profile '%s' is no longer responsive (lock path), restarting...",
168+
self.profile,
169+
)
170+
try:
171+
self._client.close()
172+
except Exception:
173+
logger.debug("Error closing stale client", exc_info=True)
174+
self._client = None
175+
self._started = False
153176

154177
if self._closed:
155178
raise RuntimeError(
@@ -253,23 +276,10 @@ def __getattr__(self, name: str):
253276
This allows HindsightEmbedded to expose all HindsightClient methods
254277
without manually wrapping each one.
255278
"""
256-
# Ensure server is started before proxying
279+
# Ensure server is started (and restart if crashed) before proxying
257280
self._ensure_started()
258281

259-
# Get the attribute from the underlying client
260-
attr = getattr(self._client, name)
261-
262-
# If it's a callable, wrap it to ensure server is started
263-
# (shouldn't be needed since _ensure_started already called, but defensive)
264-
if callable(attr):
265-
266-
def wrapper(*args, **kwargs):
267-
self._ensure_started()
268-
return attr(*args, **kwargs)
269-
270-
return wrapper
271-
272-
return attr
282+
return getattr(self._client, name)
273283

274284
def __enter__(self):
275285
"""Context manager entry - ensures server is started."""
@@ -394,11 +404,8 @@ def client(self) -> Hindsight:
394404
"""
395405
Get the underlying Hindsight client for direct access.
396406
397-
WARNING: Using this property directly means daemon restarts won't be
398-
handled automatically. Prefer using the API namespaces (banks, mental_models,
399-
directives, memories) or direct method calls on HindsightEmbedded instead.
400-
401-
Ensures daemon is started before returning the client.
407+
Ensures daemon is started (and restarts it if it has crashed) before
408+
returning the client.
402409
403410
Returns:
404411
Hindsight: The underlying client instance
@@ -409,9 +416,8 @@ def client(self) -> Hindsight:
409416
410417
embedded = HindsightEmbedded(profile="myapp", ...)
411418
412-
# Direct access (not recommended - daemon crashes won't be handled)
413419
client = embedded.client
414-
banks = client.list_banks() # If daemon crashes, this will fail
420+
banks = client.list_banks()
415421
```
416422
"""
417423
self._ensure_started()
@@ -425,8 +431,13 @@ def url(self) -> str:
425431

426432
@property
427433
def is_running(self) -> bool:
428-
"""Check if the client is initialized."""
429-
return self._started and not self._closed and self._client is not None
434+
"""Check if the client is initialized and the daemon is responsive."""
435+
return (
436+
self._started
437+
and not self._closed
438+
and self._client is not None
439+
and self._manager.is_running(self.profile)
440+
)
430441

431442
@property
432443
def ui_url(self) -> str:

hindsight-all/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "hindsight-all"
7-
version = "0.5.3"
7+
version = "0.5.4"
88
description = "Hindsight: Agent Memory That Works Like Human Memory - All-in-One Bundle"
99
readme = "README.md"
1010
requires-python = ">=3.11"

hindsight-all/tests/test_embedded.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,42 @@ def test_embedded_ui_flag(llm_config):
401401

402402
finally:
403403
client.close()
404+
405+
406+
def test_embedded_daemon_crash_recovery(llm_config):
407+
"""
408+
Test that HindsightEmbedded recovers when the daemon crashes.
409+
410+
Simulates a crash by stopping the daemon, then verifies
411+
that the next operation transparently restarts it.
412+
"""
413+
profile = f"test_crash_{uuid.uuid4().hex[:8]}"
414+
bank_id = f"bank_{uuid.uuid4().hex[:8]}"
415+
416+
client = HindsightEmbedded(profile=profile, log_level="info", **llm_config)
417+
418+
try:
419+
# Start daemon and store a memory
420+
result = client.retain(bank_id=bank_id, content="Before crash")
421+
assert result.success, "Initial retain should succeed"
422+
assert client.is_running, "Daemon should be running"
423+
424+
original_url = client.url
425+
426+
# Simulate daemon crash by stopping it
427+
client._manager.stop(client.profile)
428+
assert not client._manager.is_running(client.profile), (
429+
"Daemon should be stopped after simulated crash"
430+
)
431+
432+
# Next operation should transparently restart the daemon
433+
result2 = client.retain(bank_id=bank_id, content="After crash recovery")
434+
assert result2.success, "Retain after crash recovery should succeed"
435+
assert client.is_running, "Daemon should be running again after recovery"
436+
437+
# Verify recall still works
438+
recall_result = client.recall(bank_id=bank_id, query="crash")
439+
assert isinstance(recall_result.results, list), "Recall should return results"
440+
441+
finally:
442+
client.close()

hindsight-api-slim/hindsight_api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@
4646
"RemoteTEICrossEncoder",
4747
"LLMConfig",
4848
]
49-
__version__ = "0.5.3"
49+
__version__ = "0.5.4"

0 commit comments

Comments
 (0)