palaia 2.4.dev1__tar.gz → 2.4.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {palaia-2.4.dev1/palaia.egg-info → palaia-2.4.dev2}/PKG-INFO +50 -36
- {palaia-2.4.dev1 → palaia-2.4.dev2}/README.md +49 -35
- {palaia-2.4.dev1 → palaia-2.4.dev2}/SKILL.md +1 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/SKILL.md +1 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/__init__.py +1 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_args.py +2 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_commands.py +14 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embed_server.py +4 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/mcp/server.py +2 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/search.py +4 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/query.py +4 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/write.py +6 -1
- {palaia-2.4.dev1 → palaia-2.4.dev2/palaia.egg-info}/PKG-INFO +50 -36
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/SOURCES.txt +1 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/pyproject.toml +1 -1
- palaia-2.4.dev2/tests/test_search_cache.py +101 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_temporal_queries.py +49 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/CHANGELOG.md +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/LICENSE +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/MANIFEST.in +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/__main__.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/__init__.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/migrate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/postgres.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/protocol.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/sqlite.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/bm25.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_helpers.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_nudge.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/config.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/curate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/decay.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/__init__.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/checks.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/fixes.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/report.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embed_client.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embeddings.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/entry.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/enums.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/frontmatter.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/index.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/ingest.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/lock.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/locking.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/mcp/__init__.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/memo.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/metadata_index.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/migrate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/nudge.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/packages.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/priorities.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/process_runner.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/project.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/project_lock.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/scope.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/__init__.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/admin.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/curate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/ingest.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/memo.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/misc.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/package.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/priorities.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/process.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/project.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/status.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/significance.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/store.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/sync.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/ui.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/wal.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/dependency_links.txt +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/entry_points.txt +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/requires.txt +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/top_level.txt +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/setup.cfg +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_agent_aliases.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_agent_flag.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_audit_fixes.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_auto_title.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_migrate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_postgres.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_sqlite.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_bm25.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_bounded_gc.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_capture_level.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_chain_cli.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_concurrent_writes.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_config.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_config_detection.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_cross_platform_lock.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_cross_project.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_curate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_decay.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_detect.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_fix_chain.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_memos.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_staleness.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_version.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embed_server.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embed_server_socket.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embeddings.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_entry_classes.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_frontmatter.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_gemini_embeddings.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_hit_decay.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_incremental_indexing.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_index.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ingest.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_init_gatekeeper.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_init_no_agent.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_list_filters.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_locking.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_mcp_server.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_memo.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_metadata_index.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_migrate.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_multi_agent.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_nudge_tracker.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_packages.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_plugin_defaults_upgrade.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_priorities.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_process_nudge.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_process_runner.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_project.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_project_owner.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_scope.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_scope_enforcement.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_search.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_search_chain.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_significance.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_skill.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ssrf.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_status_index_hint.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_store.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_sync.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_unicode.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ux_improvements.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_wal.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_wal_corruption.py +0 -0
- {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_warmup_reindex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: palaia
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.dev2
|
|
4
4
|
Summary: Local, cloud-free memory for OpenClaw agents.
|
|
5
5
|
Author-email: byte5 GmbH <hello@byte5.de>
|
|
6
6
|
License: MIT
|
|
@@ -51,7 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
# Palaia — The Knowledge System for AI Agent Teams
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**Your agents forget. Palaia doesn't.**
|
|
55
55
|
|
|
56
56
|
[](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
|
|
57
57
|
[](https://pypi.org/project/palaia/)
|
|
@@ -61,6 +61,54 @@ Dynamic: license-file
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
+
## What Palaia Does
|
|
65
|
+
|
|
66
|
+
AI agents are stateless by default. Every session starts from scratch — no memory of past decisions, no shared knowledge between agents, no context that survives a restart.
|
|
67
|
+
|
|
68
|
+
Palaia gives your agents a persistent, searchable knowledge store. They save what they learn. They find it again by meaning, not keyword. They share it across tools and sessions — automatically.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## What Palaia Is Not
|
|
73
|
+
|
|
74
|
+
- Not a chatbot or prompt manager
|
|
75
|
+
- Not a cloud service (everything runs locally)
|
|
76
|
+
- Not a vector database you manage yourself (it manages itself)
|
|
77
|
+
- Not limited to one tool — works with OpenClaw, Claude Desktop, Cursor, and any MCP client
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## What You Get
|
|
82
|
+
|
|
83
|
+
| Capability | What it means |
|
|
84
|
+
|------------|---------------|
|
|
85
|
+
| **Agents remember across sessions** | Knowledge survives restarts, tool switches, and team handoffs |
|
|
86
|
+
| **Find anything by meaning** | Hybrid BM25 + vector search across 6 embedding providers |
|
|
87
|
+
| **Zero-config local setup** | SQLite with native SIMD vector search — no separate database process |
|
|
88
|
+
| **Works everywhere via MCP** | One memory store for OpenClaw, Claude Desktop, Cursor, and more |
|
|
89
|
+
| **Multi-agent ready** | Private, team, and public scopes — agents see what they should |
|
|
90
|
+
| **Crash-safe by default** | SQLite WAL mode survives power loss, kills, OOM |
|
|
91
|
+
| **Fast** | Embed server keeps model in RAM — CLI queries ~1.5s, MCP/Plugin <500ms |
|
|
92
|
+
| **Scales when needed** | Swap to PostgreSQL + pgvector for distributed teams, no code changes |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Comparison
|
|
97
|
+
|
|
98
|
+
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
99
|
+
|---------|--------|-----------|------|--------------|
|
|
100
|
+
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
101
|
+
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
102
|
+
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
103
|
+
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
104
|
+
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
105
|
+
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
106
|
+
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
107
|
+
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
108
|
+
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
64
112
|
## Install
|
|
65
113
|
|
|
66
114
|
### Recommended: Tell your agent
|
|
@@ -122,40 +170,6 @@ palaia status # Check health
|
|
|
122
170
|
|
|
123
171
|
---
|
|
124
172
|
|
|
125
|
-
## Why Palaia?
|
|
126
|
-
|
|
127
|
-
| Feature | Details |
|
|
128
|
-
|---------|---------|
|
|
129
|
-
| **Semantic Search** | Hybrid BM25 + vector embeddings. 6 providers: fastembed, sentence-transformers, Ollama, OpenAI, Gemini, BM25. |
|
|
130
|
-
| **Native Vector Search** | sqlite-vec (SIMD KNN) or pgvector (ANN/HNSW). Not Python cosine — real database-level acceleration. |
|
|
131
|
-
| **MCP Server** | `palaia-mcp` — standalone memory for Claude Desktop, Cursor, any MCP host. No OpenClaw required. |
|
|
132
|
-
| **Multi-Backend** | SQLite (default, zero-config) or PostgreSQL + pgvector for distributed teams. |
|
|
133
|
-
| **Crash-Safe** | SQLite WAL mode — survives power loss, kills, OOM. |
|
|
134
|
-
| **Auto-Capture** | OpenClaw plugin captures significant exchanges automatically. |
|
|
135
|
-
| **Structured Types** | memory, process, task — with status, priority, assignee, due date. |
|
|
136
|
-
| **Multi-Agent** | Shared store, scopes (private/team/public), agent aliases, per-agent injection priorities. |
|
|
137
|
-
| **Smart Tiering** | HOT/WARM/COLD rotation based on decay scores and access patterns. |
|
|
138
|
-
| **Embed Server** | Background process holds model in RAM. CLI queries: ~1.5s (was ~3-5s). MCP/Plugin: <500ms. |
|
|
139
|
-
| **Zero-Cloud** | Everything local. No API keys needed for core functionality. |
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Comparison
|
|
144
|
-
|
|
145
|
-
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
146
|
-
|---------|--------|-----------|------|--------------|
|
|
147
|
-
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
148
|
-
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
149
|
-
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
150
|
-
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
151
|
-
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
152
|
-
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
153
|
-
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
154
|
-
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
155
|
-
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
173
|
## Documentation
|
|
160
174
|
|
|
161
175
|
| Document | Description |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Palaia — The Knowledge System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Your agents forget. Palaia doesn't.**
|
|
4
4
|
|
|
5
5
|
[](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
|
|
6
6
|
[](https://pypi.org/project/palaia/)
|
|
@@ -10,6 +10,54 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## What Palaia Does
|
|
14
|
+
|
|
15
|
+
AI agents are stateless by default. Every session starts from scratch — no memory of past decisions, no shared knowledge between agents, no context that survives a restart.
|
|
16
|
+
|
|
17
|
+
Palaia gives your agents a persistent, searchable knowledge store. They save what they learn. They find it again by meaning, not keyword. They share it across tools and sessions — automatically.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What Palaia Is Not
|
|
22
|
+
|
|
23
|
+
- Not a chatbot or prompt manager
|
|
24
|
+
- Not a cloud service (everything runs locally)
|
|
25
|
+
- Not a vector database you manage yourself (it manages itself)
|
|
26
|
+
- Not limited to one tool — works with OpenClaw, Claude Desktop, Cursor, and any MCP client
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## What You Get
|
|
31
|
+
|
|
32
|
+
| Capability | What it means |
|
|
33
|
+
|------------|---------------|
|
|
34
|
+
| **Agents remember across sessions** | Knowledge survives restarts, tool switches, and team handoffs |
|
|
35
|
+
| **Find anything by meaning** | Hybrid BM25 + vector search across 6 embedding providers |
|
|
36
|
+
| **Zero-config local setup** | SQLite with native SIMD vector search — no separate database process |
|
|
37
|
+
| **Works everywhere via MCP** | One memory store for OpenClaw, Claude Desktop, Cursor, and more |
|
|
38
|
+
| **Multi-agent ready** | Private, team, and public scopes — agents see what they should |
|
|
39
|
+
| **Crash-safe by default** | SQLite WAL mode survives power loss, kills, OOM |
|
|
40
|
+
| **Fast** | Embed server keeps model in RAM — CLI queries ~1.5s, MCP/Plugin <500ms |
|
|
41
|
+
| **Scales when needed** | Swap to PostgreSQL + pgvector for distributed teams, no code changes |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Comparison
|
|
46
|
+
|
|
47
|
+
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
48
|
+
|---------|--------|-----------|------|--------------|
|
|
49
|
+
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
50
|
+
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
51
|
+
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
52
|
+
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
53
|
+
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
54
|
+
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
55
|
+
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
56
|
+
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
57
|
+
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
13
61
|
## Install
|
|
14
62
|
|
|
15
63
|
### Recommended: Tell your agent
|
|
@@ -71,40 +119,6 @@ palaia status # Check health
|
|
|
71
119
|
|
|
72
120
|
---
|
|
73
121
|
|
|
74
|
-
## Why Palaia?
|
|
75
|
-
|
|
76
|
-
| Feature | Details |
|
|
77
|
-
|---------|---------|
|
|
78
|
-
| **Semantic Search** | Hybrid BM25 + vector embeddings. 6 providers: fastembed, sentence-transformers, Ollama, OpenAI, Gemini, BM25. |
|
|
79
|
-
| **Native Vector Search** | sqlite-vec (SIMD KNN) or pgvector (ANN/HNSW). Not Python cosine — real database-level acceleration. |
|
|
80
|
-
| **MCP Server** | `palaia-mcp` — standalone memory for Claude Desktop, Cursor, any MCP host. No OpenClaw required. |
|
|
81
|
-
| **Multi-Backend** | SQLite (default, zero-config) or PostgreSQL + pgvector for distributed teams. |
|
|
82
|
-
| **Crash-Safe** | SQLite WAL mode — survives power loss, kills, OOM. |
|
|
83
|
-
| **Auto-Capture** | OpenClaw plugin captures significant exchanges automatically. |
|
|
84
|
-
| **Structured Types** | memory, process, task — with status, priority, assignee, due date. |
|
|
85
|
-
| **Multi-Agent** | Shared store, scopes (private/team/public), agent aliases, per-agent injection priorities. |
|
|
86
|
-
| **Smart Tiering** | HOT/WARM/COLD rotation based on decay scores and access patterns. |
|
|
87
|
-
| **Embed Server** | Background process holds model in RAM. CLI queries: ~1.5s (was ~3-5s). MCP/Plugin: <500ms. |
|
|
88
|
-
| **Zero-Cloud** | Everything local. No API keys needed for core functionality. |
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Comparison
|
|
93
|
-
|
|
94
|
-
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
95
|
-
|---------|--------|-----------|------|--------------|
|
|
96
|
-
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
97
|
-
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
98
|
-
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
99
|
-
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
100
|
-
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
101
|
-
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
102
|
-
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
103
|
-
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
104
|
-
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
122
|
## Documentation
|
|
109
123
|
|
|
110
124
|
| Document | Description |
|
|
@@ -98,6 +98,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
98
98
|
)
|
|
99
99
|
p_query.add_argument("--assignee", default=None, help="Filter by assignee")
|
|
100
100
|
p_query.add_argument("--instance", default=None, help="Filter by session identity")
|
|
101
|
+
p_query.add_argument("--tags", default=None, help="Filter by tags (comma-separated, AND logic)")
|
|
101
102
|
p_query.add_argument("--before", default=None, help="Only entries created before this ISO timestamp")
|
|
102
103
|
p_query.add_argument("--after", default=None, help="Only entries created after this ISO timestamp")
|
|
103
104
|
p_query.add_argument(
|
|
@@ -135,6 +136,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
135
136
|
p_list = sub.add_parser("list", help="List entries in a tier")
|
|
136
137
|
p_list.add_argument("--tier", default=None, choices=["hot", "warm", "cold"], help="Tier to list (default: hot)")
|
|
137
138
|
p_list.add_argument("--all", action="store_true", help="List across all tiers (hot+warm+cold)")
|
|
139
|
+
p_list.add_argument("--limit", type=int, default=None, help="Max entries to return")
|
|
138
140
|
p_list.add_argument("--project", default=None, help="Filter by project")
|
|
139
141
|
p_list.add_argument("--tag", default=None, action="append", help="Filter by tag (repeatable, AND logic)")
|
|
140
142
|
p_list.add_argument("--scope", default=None, help="Filter by scope")
|
|
@@ -58,6 +58,15 @@ def cmd_query(args):
|
|
|
58
58
|
has_embeddings = svc_result["has_embeddings"]
|
|
59
59
|
bm25_only = svc_result["bm25_only"]
|
|
60
60
|
|
|
61
|
+
# Post-filter by --tags if specified (AND logic, comma-separated)
|
|
62
|
+
tags_filter = getattr(args, "tags", None)
|
|
63
|
+
if tags_filter:
|
|
64
|
+
required_tags = [t.strip() for t in tags_filter.split(",") if t.strip()]
|
|
65
|
+
results = [
|
|
66
|
+
r for r in results
|
|
67
|
+
if all(tag in (r.get("tags") or []) for tag in required_tags)
|
|
68
|
+
]
|
|
69
|
+
|
|
61
70
|
# --- Adaptive Nudging for query (Issue #68) ---
|
|
62
71
|
query_nudge_messages = []
|
|
63
72
|
try:
|
|
@@ -234,6 +243,11 @@ def cmd_list(args):
|
|
|
234
243
|
tier_label = svc_result["tier"]
|
|
235
244
|
entries_with_tier = svc_result["entries_with_tier"]
|
|
236
245
|
|
|
246
|
+
# Apply --limit if specified
|
|
247
|
+
limit = getattr(args, "limit", None)
|
|
248
|
+
if limit is not None and limit > 0:
|
|
249
|
+
entries_with_tier = entries_with_tier[:limit]
|
|
250
|
+
|
|
237
251
|
if json_out(
|
|
238
252
|
{
|
|
239
253
|
"tier": tier_label,
|
|
@@ -242,6 +242,8 @@ class EmbedServer:
|
|
|
242
242
|
instance = params.get("instance")
|
|
243
243
|
include_cold = params.get("include_cold", False)
|
|
244
244
|
cross_project = params.get("cross_project", False)
|
|
245
|
+
before = params.get("before")
|
|
246
|
+
after = params.get("after")
|
|
245
247
|
|
|
246
248
|
engine = self._bm25_engine if self._warming_up else self.engine
|
|
247
249
|
results = engine.search(
|
|
@@ -255,6 +257,8 @@ class EmbedServer:
|
|
|
255
257
|
priority=priority,
|
|
256
258
|
assignee=assignee,
|
|
257
259
|
instance=instance,
|
|
260
|
+
before=before,
|
|
261
|
+
after=after,
|
|
258
262
|
cross_project=cross_project,
|
|
259
263
|
)
|
|
260
264
|
|
|
@@ -123,7 +123,8 @@ def create_server(root: Path, read_only: bool = False) -> FastMCP:
|
|
|
123
123
|
header_parts = [
|
|
124
124
|
f"ID: {result['id']}",
|
|
125
125
|
f"Title: {meta.get('title', 'untitled')}",
|
|
126
|
-
f"Type: {meta.get('
|
|
126
|
+
f"Type: {meta.get('type', 'memory')}",
|
|
127
|
+
f"Scope: {meta.get('scope', 'team')}",
|
|
127
128
|
f"Tier: {meta.get('tier', '?')}",
|
|
128
129
|
f"Tags: {', '.join(meta.get('tags', []))}",
|
|
129
130
|
f"Created: {meta.get('created', '?')}",
|
|
@@ -73,6 +73,7 @@ class SearchEngine:
|
|
|
73
73
|
self._provider = None
|
|
74
74
|
self._index_cache: list[tuple[str, str, dict]] | None = None
|
|
75
75
|
self._index_dirty = True
|
|
76
|
+
self._index_cache_key: tuple | None = None # (include_cold, agent) for cache validity
|
|
76
77
|
|
|
77
78
|
@property
|
|
78
79
|
def provider(self):
|
|
@@ -95,7 +96,8 @@ class SearchEngine:
|
|
|
95
96
|
Uses a cached index when available. Call invalidate_index() after
|
|
96
97
|
write/edit/gc operations to force a rebuild.
|
|
97
98
|
"""
|
|
98
|
-
|
|
99
|
+
cache_key = (include_cold, agent)
|
|
100
|
+
if not self._index_dirty and self._index_cache is not None and self._index_cache_key == cache_key:
|
|
99
101
|
# Re-index BM25 from cache (cheap — no disk reads)
|
|
100
102
|
self.bm25.index([(did, text) for did, text, _meta in self._index_cache])
|
|
101
103
|
return list(self._index_cache)
|
|
@@ -112,6 +114,7 @@ class SearchEngine:
|
|
|
112
114
|
docs_with_meta.append((doc_id, full_text, meta))
|
|
113
115
|
self.bm25.index(docs)
|
|
114
116
|
self._index_cache = docs_with_meta
|
|
117
|
+
self._index_cache_key = cache_key
|
|
115
118
|
self._index_dirty = False
|
|
116
119
|
return docs_with_meta
|
|
117
120
|
|
|
@@ -80,6 +80,10 @@ def search_entries(
|
|
|
80
80
|
params["instance"] = instance
|
|
81
81
|
if cross_project:
|
|
82
82
|
params["cross_project"] = True
|
|
83
|
+
if before:
|
|
84
|
+
params["before"] = before
|
|
85
|
+
if after:
|
|
86
|
+
params["after"] = after
|
|
83
87
|
result = client.query(params, timeout=5.0)
|
|
84
88
|
chain_cfg = _config.get("embedding_chain", [])
|
|
85
89
|
bm25_only = not chain_cfg or chain_cfg == ["bm25"]
|
|
@@ -62,7 +62,8 @@ def write_entry(
|
|
|
62
62
|
instance=instance,
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
-
# Check dedup
|
|
65
|
+
# Check dedup: Store.write() returns existing ID on hash collision
|
|
66
|
+
# Compare with a fresh read to detect if this was a dedup or new write
|
|
66
67
|
entry = store.read(entry_id)
|
|
67
68
|
tier = "hot"
|
|
68
69
|
actual_scope = scope or "team"
|
|
@@ -74,6 +75,10 @@ def write_entry(
|
|
|
74
75
|
if (root / t / f"{entry_id}.md").exists():
|
|
75
76
|
tier = t
|
|
76
77
|
break
|
|
78
|
+
# If entry existed before write (non-hot tier or older timestamp),
|
|
79
|
+
# it was deduplicated. Hot + just-created = new entry.
|
|
80
|
+
if tier != "hot":
|
|
81
|
+
deduplicated = True
|
|
77
82
|
|
|
78
83
|
# --- Adaptive Nudging (Issue #68) ---
|
|
79
84
|
nudge_messages: list[str] = []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: palaia
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.dev2
|
|
4
4
|
Summary: Local, cloud-free memory for OpenClaw agents.
|
|
5
5
|
Author-email: byte5 GmbH <hello@byte5.de>
|
|
6
6
|
License: MIT
|
|
@@ -51,7 +51,7 @@ Dynamic: license-file
|
|
|
51
51
|
|
|
52
52
|
# Palaia — The Knowledge System for AI Agent Teams
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**Your agents forget. Palaia doesn't.**
|
|
55
55
|
|
|
56
56
|
[](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
|
|
57
57
|
[](https://pypi.org/project/palaia/)
|
|
@@ -61,6 +61,54 @@ Dynamic: license-file
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
+
## What Palaia Does
|
|
65
|
+
|
|
66
|
+
AI agents are stateless by default. Every session starts from scratch — no memory of past decisions, no shared knowledge between agents, no context that survives a restart.
|
|
67
|
+
|
|
68
|
+
Palaia gives your agents a persistent, searchable knowledge store. They save what they learn. They find it again by meaning, not keyword. They share it across tools and sessions — automatically.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## What Palaia Is Not
|
|
73
|
+
|
|
74
|
+
- Not a chatbot or prompt manager
|
|
75
|
+
- Not a cloud service (everything runs locally)
|
|
76
|
+
- Not a vector database you manage yourself (it manages itself)
|
|
77
|
+
- Not limited to one tool — works with OpenClaw, Claude Desktop, Cursor, and any MCP client
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## What You Get
|
|
82
|
+
|
|
83
|
+
| Capability | What it means |
|
|
84
|
+
|------------|---------------|
|
|
85
|
+
| **Agents remember across sessions** | Knowledge survives restarts, tool switches, and team handoffs |
|
|
86
|
+
| **Find anything by meaning** | Hybrid BM25 + vector search across 6 embedding providers |
|
|
87
|
+
| **Zero-config local setup** | SQLite with native SIMD vector search — no separate database process |
|
|
88
|
+
| **Works everywhere via MCP** | One memory store for OpenClaw, Claude Desktop, Cursor, and more |
|
|
89
|
+
| **Multi-agent ready** | Private, team, and public scopes — agents see what they should |
|
|
90
|
+
| **Crash-safe by default** | SQLite WAL mode survives power loss, kills, OOM |
|
|
91
|
+
| **Fast** | Embed server keeps model in RAM — CLI queries ~1.5s, MCP/Plugin <500ms |
|
|
92
|
+
| **Scales when needed** | Swap to PostgreSQL + pgvector for distributed teams, no code changes |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Comparison
|
|
97
|
+
|
|
98
|
+
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
99
|
+
|---------|--------|-----------|------|--------------|
|
|
100
|
+
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
101
|
+
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
102
|
+
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
103
|
+
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
104
|
+
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
105
|
+
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
106
|
+
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
107
|
+
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
108
|
+
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
64
112
|
## Install
|
|
65
113
|
|
|
66
114
|
### Recommended: Tell your agent
|
|
@@ -122,40 +170,6 @@ palaia status # Check health
|
|
|
122
170
|
|
|
123
171
|
---
|
|
124
172
|
|
|
125
|
-
## Why Palaia?
|
|
126
|
-
|
|
127
|
-
| Feature | Details |
|
|
128
|
-
|---------|---------|
|
|
129
|
-
| **Semantic Search** | Hybrid BM25 + vector embeddings. 6 providers: fastembed, sentence-transformers, Ollama, OpenAI, Gemini, BM25. |
|
|
130
|
-
| **Native Vector Search** | sqlite-vec (SIMD KNN) or pgvector (ANN/HNSW). Not Python cosine — real database-level acceleration. |
|
|
131
|
-
| **MCP Server** | `palaia-mcp` — standalone memory for Claude Desktop, Cursor, any MCP host. No OpenClaw required. |
|
|
132
|
-
| **Multi-Backend** | SQLite (default, zero-config) or PostgreSQL + pgvector for distributed teams. |
|
|
133
|
-
| **Crash-Safe** | SQLite WAL mode — survives power loss, kills, OOM. |
|
|
134
|
-
| **Auto-Capture** | OpenClaw plugin captures significant exchanges automatically. |
|
|
135
|
-
| **Structured Types** | memory, process, task — with status, priority, assignee, due date. |
|
|
136
|
-
| **Multi-Agent** | Shared store, scopes (private/team/public), agent aliases, per-agent injection priorities. |
|
|
137
|
-
| **Smart Tiering** | HOT/WARM/COLD rotation based on decay scores and access patterns. |
|
|
138
|
-
| **Embed Server** | Background process holds model in RAM. CLI queries: ~1.5s (was ~3-5s). MCP/Plugin: <500ms. |
|
|
139
|
-
| **Zero-Cloud** | Everything local. No API keys needed for core functionality. |
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Comparison
|
|
144
|
-
|
|
145
|
-
| Feature | Palaia | claude-mem | Mem0 | Stock Memory |
|
|
146
|
-
|---------|--------|-----------|------|--------------|
|
|
147
|
-
| Local-first | Yes | Yes | No (cloud) | Yes |
|
|
148
|
-
| Cross-tool (MCP) | Yes (any MCP client) | No (Claude Code only) | No | No |
|
|
149
|
-
| Native Vector Search | sqlite-vec / pgvector | ChromaDB (separate) | Cloud | No |
|
|
150
|
-
| Structured Types | memory/process/task | decisions/bugfixes | No | No |
|
|
151
|
-
| Multi-Agent Scopes | private/team/public | No | Per-user | No |
|
|
152
|
-
| Smart Tiering | HOT/WARM/COLD | No | No | No |
|
|
153
|
-
| Embedding Providers | 6 (configurable) | 1 (fixed) | Cloud | None |
|
|
154
|
-
| Open Source | MIT | AGPL-3.0 | Partial | N/A |
|
|
155
|
-
| Crash-safe (WAL) | Yes | Partial | N/A | No |
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
173
|
## Documentation
|
|
160
174
|
|
|
161
175
|
| Document | Description |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Tests for SearchEngine cache invalidation by corpus params."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from palaia.config import DEFAULT_CONFIG, save_config
|
|
8
|
+
from palaia.search import SearchEngine
|
|
9
|
+
from palaia.store import Store
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def palaia_root(tmp_path):
|
|
14
|
+
root = tmp_path / ".palaia"
|
|
15
|
+
root.mkdir()
|
|
16
|
+
for d in ("hot", "warm", "cold", "wal", "index"):
|
|
17
|
+
(root / d).mkdir()
|
|
18
|
+
config = dict(DEFAULT_CONFIG)
|
|
19
|
+
config["store_version"] = "2.0.0"
|
|
20
|
+
save_config(root, config)
|
|
21
|
+
return root
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def store(palaia_root):
|
|
26
|
+
return Store(palaia_root)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestSearchEngineCacheInvalidation:
|
|
30
|
+
"""Verify that build_index() rebuilds when corpus params change."""
|
|
31
|
+
|
|
32
|
+
def test_cache_hit_same_params(self, store):
|
|
33
|
+
store.write("Test entry for caching", tags=["test"], title="Cache Test")
|
|
34
|
+
engine = SearchEngine(store)
|
|
35
|
+
|
|
36
|
+
# First build — should read from disk
|
|
37
|
+
docs1 = engine.build_index(include_cold=False, agent=None)
|
|
38
|
+
assert len(docs1) > 0
|
|
39
|
+
|
|
40
|
+
# Second build with same params — should use cache
|
|
41
|
+
docs2 = engine.build_index(include_cold=False, agent=None)
|
|
42
|
+
assert len(docs2) == len(docs1)
|
|
43
|
+
assert not engine._index_dirty
|
|
44
|
+
|
|
45
|
+
def test_cache_miss_on_include_cold_change(self, store):
|
|
46
|
+
store.write("Hot entry", tags=["test"], title="Hot")
|
|
47
|
+
engine = SearchEngine(store)
|
|
48
|
+
|
|
49
|
+
# Build with include_cold=False
|
|
50
|
+
engine.build_index(include_cold=False, agent=None)
|
|
51
|
+
assert engine._index_cache_key == (False, None)
|
|
52
|
+
|
|
53
|
+
# Build with include_cold=True — cache key differs, should rebuild
|
|
54
|
+
engine.build_index(include_cold=True, agent=None)
|
|
55
|
+
assert engine._index_cache_key == (True, None)
|
|
56
|
+
|
|
57
|
+
def test_cache_miss_on_agent_change(self, store):
|
|
58
|
+
store.write("Entry by agent-a", tags=["test"], agent="agent-a", title="A")
|
|
59
|
+
store.write("Entry by agent-b", tags=["test"], agent="agent-b", title="B")
|
|
60
|
+
engine = SearchEngine(store)
|
|
61
|
+
|
|
62
|
+
# Build for agent-a
|
|
63
|
+
docs_a = engine.build_index(include_cold=False, agent="agent-a")
|
|
64
|
+
key_a = engine._index_cache_key
|
|
65
|
+
assert key_a == (False, "agent-a")
|
|
66
|
+
|
|
67
|
+
# Build for agent-b — cache key changes, should rebuild
|
|
68
|
+
docs_b = engine.build_index(include_cold=False, agent="agent-b")
|
|
69
|
+
key_b = engine._index_cache_key
|
|
70
|
+
assert key_b == (False, "agent-b")
|
|
71
|
+
assert key_a != key_b
|
|
72
|
+
|
|
73
|
+
def test_invalidate_index_forces_rebuild(self, store):
|
|
74
|
+
store.write("Test entry", tags=["test"], title="Invalidation Test")
|
|
75
|
+
engine = SearchEngine(store)
|
|
76
|
+
|
|
77
|
+
engine.build_index()
|
|
78
|
+
assert not engine._index_dirty
|
|
79
|
+
|
|
80
|
+
engine.invalidate_index()
|
|
81
|
+
assert engine._index_dirty
|
|
82
|
+
assert engine._index_cache is None
|
|
83
|
+
|
|
84
|
+
# Rebuild after invalidation
|
|
85
|
+
docs = engine.build_index()
|
|
86
|
+
assert not engine._index_dirty
|
|
87
|
+
assert len(docs) > 0
|
|
88
|
+
|
|
89
|
+
def test_cache_key_combo_cold_and_agent(self, store):
|
|
90
|
+
"""Different (include_cold, agent) combos produce different cache keys."""
|
|
91
|
+
store.write("Entry", tags=["test"], title="Combo Test")
|
|
92
|
+
engine = SearchEngine(store)
|
|
93
|
+
|
|
94
|
+
engine.build_index(include_cold=False, agent=None)
|
|
95
|
+
assert engine._index_cache_key == (False, None)
|
|
96
|
+
|
|
97
|
+
engine.build_index(include_cold=True, agent="bot")
|
|
98
|
+
assert engine._index_cache_key == (True, "bot")
|
|
99
|
+
|
|
100
|
+
engine.build_index(include_cold=False, agent="bot")
|
|
101
|
+
assert engine._index_cache_key == (False, "bot")
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
|
|
5
7
|
import pytest
|
|
6
8
|
|
|
7
9
|
from palaia.config import DEFAULT_CONFIG, save_config
|
|
10
|
+
from palaia.embed_server import EmbedServer
|
|
8
11
|
from palaia.entry import parse_entry, serialize_entry
|
|
9
12
|
from palaia.search import SearchEngine
|
|
10
13
|
from palaia.store import Store
|
|
@@ -108,3 +111,49 @@ class TestTemporalSearch:
|
|
|
108
111
|
results = engine.search("Late Plan", after="2026-03-15T10:00:00+00:00")
|
|
109
112
|
result_ids = [r["id"] for r in results]
|
|
110
113
|
assert ids[2] not in result_ids
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TestEmbedServerTemporalQuery:
|
|
117
|
+
"""Verify embed-server _handle_query forwards before/after to SearchEngine."""
|
|
118
|
+
|
|
119
|
+
def test_before_param_forwarded(self, temporal_store):
|
|
120
|
+
store, ids = temporal_store
|
|
121
|
+
server = EmbedServer(store.root)
|
|
122
|
+
response = server.handle_request({
|
|
123
|
+
"method": "query",
|
|
124
|
+
"params": {"text": "decision update plan", "before": "2026-02-01"},
|
|
125
|
+
})
|
|
126
|
+
assert "result" in response
|
|
127
|
+
result_ids = [r["id"] for r in response["result"]["results"]]
|
|
128
|
+
assert ids[0] in result_ids
|
|
129
|
+
assert ids[1] not in result_ids
|
|
130
|
+
assert ids[2] not in result_ids
|
|
131
|
+
|
|
132
|
+
def test_after_param_forwarded(self, temporal_store):
|
|
133
|
+
store, ids = temporal_store
|
|
134
|
+
server = EmbedServer(store.root)
|
|
135
|
+
response = server.handle_request({
|
|
136
|
+
"method": "query",
|
|
137
|
+
"params": {"text": "decision update plan", "after": "2026-03-01"},
|
|
138
|
+
})
|
|
139
|
+
assert "result" in response
|
|
140
|
+
result_ids = [r["id"] for r in response["result"]["results"]]
|
|
141
|
+
assert ids[2] in result_ids
|
|
142
|
+
assert ids[0] not in result_ids
|
|
143
|
+
|
|
144
|
+
def test_before_and_after_combined(self, temporal_store):
|
|
145
|
+
store, ids = temporal_store
|
|
146
|
+
server = EmbedServer(store.root)
|
|
147
|
+
response = server.handle_request({
|
|
148
|
+
"method": "query",
|
|
149
|
+
"params": {
|
|
150
|
+
"text": "decision update plan",
|
|
151
|
+
"after": "2026-02-01",
|
|
152
|
+
"before": "2026-03-01",
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
assert "result" in response
|
|
156
|
+
result_ids = [r["id"] for r in response["result"]["results"]]
|
|
157
|
+
assert ids[1] in result_ids
|
|
158
|
+
assert ids[0] not in result_ids
|
|
159
|
+
assert ids[2] not in result_ids
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|