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.
Files changed (144) hide show
  1. {palaia-2.4.dev1/palaia.egg-info → palaia-2.4.dev2}/PKG-INFO +50 -36
  2. {palaia-2.4.dev1 → palaia-2.4.dev2}/README.md +49 -35
  3. {palaia-2.4.dev1 → palaia-2.4.dev2}/SKILL.md +1 -1
  4. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/SKILL.md +1 -1
  5. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/__init__.py +1 -1
  6. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_args.py +2 -0
  7. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_commands.py +14 -0
  8. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embed_server.py +4 -0
  9. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/mcp/server.py +2 -1
  10. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/search.py +4 -1
  11. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/query.py +4 -0
  12. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/write.py +6 -1
  13. {palaia-2.4.dev1 → palaia-2.4.dev2/palaia.egg-info}/PKG-INFO +50 -36
  14. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/SOURCES.txt +1 -0
  15. {palaia-2.4.dev1 → palaia-2.4.dev2}/pyproject.toml +1 -1
  16. palaia-2.4.dev2/tests/test_search_cache.py +101 -0
  17. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_temporal_queries.py +49 -0
  18. {palaia-2.4.dev1 → palaia-2.4.dev2}/CHANGELOG.md +0 -0
  19. {palaia-2.4.dev1 → palaia-2.4.dev2}/LICENSE +0 -0
  20. {palaia-2.4.dev1 → palaia-2.4.dev2}/MANIFEST.in +0 -0
  21. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/__main__.py +0 -0
  22. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/__init__.py +0 -0
  23. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/migrate.py +0 -0
  24. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/postgres.py +0 -0
  25. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/protocol.py +0 -0
  26. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/backends/sqlite.py +0 -0
  27. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/bm25.py +0 -0
  28. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli.py +0 -0
  29. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_helpers.py +0 -0
  30. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/cli_nudge.py +0 -0
  31. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/config.py +0 -0
  32. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/curate.py +0 -0
  33. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/decay.py +0 -0
  34. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/__init__.py +0 -0
  35. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/checks.py +0 -0
  36. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/fixes.py +0 -0
  37. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/doctor/report.py +0 -0
  38. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embed_client.py +0 -0
  39. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/embeddings.py +0 -0
  40. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/entry.py +0 -0
  41. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/enums.py +0 -0
  42. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/frontmatter.py +0 -0
  43. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/index.py +0 -0
  44. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/ingest.py +0 -0
  45. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/lock.py +0 -0
  46. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/locking.py +0 -0
  47. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/mcp/__init__.py +0 -0
  48. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/memo.py +0 -0
  49. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/metadata_index.py +0 -0
  50. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/migrate.py +0 -0
  51. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/nudge.py +0 -0
  52. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/packages.py +0 -0
  53. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/priorities.py +0 -0
  54. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/process_runner.py +0 -0
  55. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/project.py +0 -0
  56. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/project_lock.py +0 -0
  57. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/scope.py +0 -0
  58. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/__init__.py +0 -0
  59. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/admin.py +0 -0
  60. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/curate.py +0 -0
  61. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/ingest.py +0 -0
  62. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/memo.py +0 -0
  63. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/misc.py +0 -0
  64. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/package.py +0 -0
  65. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/priorities.py +0 -0
  66. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/process.py +0 -0
  67. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/project.py +0 -0
  68. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/services/status.py +0 -0
  69. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/significance.py +0 -0
  70. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/store.py +0 -0
  71. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/sync.py +0 -0
  72. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/ui.py +0 -0
  73. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia/wal.py +0 -0
  74. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/dependency_links.txt +0 -0
  75. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/entry_points.txt +0 -0
  76. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/requires.txt +0 -0
  77. {palaia-2.4.dev1 → palaia-2.4.dev2}/palaia.egg-info/top_level.txt +0 -0
  78. {palaia-2.4.dev1 → palaia-2.4.dev2}/setup.cfg +0 -0
  79. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_agent_aliases.py +0 -0
  80. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_agent_flag.py +0 -0
  81. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_audit_fixes.py +0 -0
  82. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_auto_title.py +0 -0
  83. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_migrate.py +0 -0
  84. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_postgres.py +0 -0
  85. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_backend_sqlite.py +0 -0
  86. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_bm25.py +0 -0
  87. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_bounded_gc.py +0 -0
  88. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_capture_level.py +0 -0
  89. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_chain_cli.py +0 -0
  90. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_concurrent_writes.py +0 -0
  91. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_config.py +0 -0
  92. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_config_detection.py +0 -0
  93. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_cross_platform_lock.py +0 -0
  94. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_cross_project.py +0 -0
  95. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_curate.py +0 -0
  96. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_decay.py +0 -0
  97. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_detect.py +0 -0
  98. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor.py +0 -0
  99. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_fix_chain.py +0 -0
  100. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_memos.py +0 -0
  101. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_staleness.py +0 -0
  102. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_doctor_version.py +0 -0
  103. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embed_server.py +0 -0
  104. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embed_server_socket.py +0 -0
  105. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_embeddings.py +0 -0
  106. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_entry_classes.py +0 -0
  107. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_frontmatter.py +0 -0
  108. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_gemini_embeddings.py +0 -0
  109. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_hit_decay.py +0 -0
  110. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_incremental_indexing.py +0 -0
  111. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_index.py +0 -0
  112. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ingest.py +0 -0
  113. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_init_gatekeeper.py +0 -0
  114. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_init_no_agent.py +0 -0
  115. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_list_filters.py +0 -0
  116. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_locking.py +0 -0
  117. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_mcp_server.py +0 -0
  118. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_memo.py +0 -0
  119. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_metadata_index.py +0 -0
  120. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_migrate.py +0 -0
  121. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_multi_agent.py +0 -0
  122. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_nudge_tracker.py +0 -0
  123. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_packages.py +0 -0
  124. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_plugin_defaults_upgrade.py +0 -0
  125. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_priorities.py +0 -0
  126. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_process_nudge.py +0 -0
  127. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_process_runner.py +0 -0
  128. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_project.py +0 -0
  129. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_project_owner.py +0 -0
  130. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_scope.py +0 -0
  131. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_scope_enforcement.py +0 -0
  132. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_search.py +0 -0
  133. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_search_chain.py +0 -0
  134. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_significance.py +0 -0
  135. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_skill.py +0 -0
  136. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ssrf.py +0 -0
  137. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_status_index_hint.py +0 -0
  138. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_store.py +0 -0
  139. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_sync.py +0 -0
  140. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_unicode.py +0 -0
  141. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_ux_improvements.py +0 -0
  142. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_wal.py +0 -0
  143. {palaia-2.4.dev1 → palaia-2.4.dev2}/tests/test_wal_corruption.py +0 -0
  144. {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.dev1
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
- **Crash-safe. Local-first. Zero-cloud. The memory system that makes your agents smarter over time.**
54
+ **Your agents forget. Palaia doesn't.**
55
55
 
56
56
  [![CI](https://github.com/byte5ai/palaia/actions/workflows/ci.yml/badge.svg)](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
57
57
  [![PyPI](https://img.shields.io/pypi/v/palaia)](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
- **Crash-safe. Local-first. Zero-cloud. The memory system that makes your agents smarter over time.**
3
+ **Your agents forget. Palaia doesn't.**
4
4
 
5
5
  [![CI](https://github.com/byte5ai/palaia/actions/workflows/ci.yml/badge.svg)](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
6
6
  [![PyPI](https://img.shields.io/pypi/v/palaia)](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 |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: palaia
3
- version: "2.4.dev1"
3
+ version: "2.4.dev2"
4
4
  description: >
5
5
  Local, crash-safe persistent memory for OpenClaw agents.
6
6
  SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: palaia
3
- version: "2.4.dev1"
3
+ version: "2.4.dev2"
4
4
  description: >
5
5
  Local, crash-safe persistent memory for OpenClaw agents.
6
6
  SQLite-backed by default. Semantic search, projects, scopes, auto-capture.
@@ -4,5 +4,5 @@ Palaia — Local, cloud-free memory for OpenClaw agents.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- __version__ = "2.4.dev1"
7
+ __version__ = "2.4.dev2"
8
8
  __author__ = "byte5 GmbH"
@@ -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('scope', 'team')}",
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
- if not self._index_dirty and self._index_cache is not None:
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 and tier
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.dev1
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
- **Crash-safe. Local-first. Zero-cloud. The memory system that makes your agents smarter over time.**
54
+ **Your agents forget. Palaia doesn't.**
55
55
 
56
56
  [![CI](https://github.com/byte5ai/palaia/actions/workflows/ci.yml/badge.svg)](https://github.com/byte5ai/palaia/actions/workflows/ci.yml)
57
57
  [![PyPI](https://img.shields.io/pypi/v/palaia)](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 |
@@ -126,6 +126,7 @@ tests/test_project_owner.py
126
126
  tests/test_scope.py
127
127
  tests/test_scope_enforcement.py
128
128
  tests/test_search.py
129
+ tests/test_search_cache.py
129
130
  tests/test_search_chain.py
130
131
  tests/test_significance.py
131
132
  tests/test_skill.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "palaia"
7
- version = "2.4.dev1"
7
+ version = "2.4.dev2"
8
8
  description = "Local, cloud-free memory for OpenClaw agents."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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