selectools 0.16.0__tar.gz → 0.16.2__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 (113) hide show
  1. {selectools-0.16.0/src/selectools.egg-info → selectools-0.16.2}/PKG-INFO +3 -3
  2. {selectools-0.16.0 → selectools-0.16.2}/README.md +2 -2
  3. {selectools-0.16.0 → selectools-0.16.2}/pyproject.toml +1 -1
  4. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/__init__.py +1 -1
  5. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/core.py +12 -2
  6. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/entity_memory.py +31 -25
  7. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/knowledge.py +12 -9
  8. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/knowledge_graph.py +5 -3
  9. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/memory.py +1 -0
  10. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/sessions.py +3 -3
  11. {selectools-0.16.0 → selectools-0.16.2/src/selectools.egg-info}/PKG-INFO +3 -3
  12. {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/SOURCES.txt +6 -0
  13. selectools-0.16.2/tests/test_consolidation_regression.py +414 -0
  14. selectools-0.16.2/tests/test_memory_async.py +238 -0
  15. selectools-0.16.2/tests/test_memory_boundary.py +127 -0
  16. selectools-0.16.2/tests/test_memory_integration.py +271 -0
  17. selectools-0.16.2/tests/test_sessions_edge_cases.py +245 -0
  18. selectools-0.16.2/tests/test_sessions_redis.py +354 -0
  19. {selectools-0.16.0 → selectools-0.16.2}/LICENSE +0 -0
  20. {selectools-0.16.0 → selectools-0.16.2}/setup.cfg +0 -0
  21. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/__init__.py +0 -0
  22. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/agent/config.py +0 -0
  23. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/analytics.py +0 -0
  24. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/audit.py +0 -0
  25. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cache.py +0 -0
  26. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cache_redis.py +0 -0
  27. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/cli.py +0 -0
  28. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/coherence.py +0 -0
  29. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/__init__.py +0 -0
  30. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/anthropic.py +0 -0
  31. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/cohere.py +0 -0
  32. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/gemini.py +0 -0
  33. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/openai.py +0 -0
  34. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/embeddings/provider.py +0 -0
  35. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/env.py +0 -0
  36. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/exceptions.py +0 -0
  37. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/__init__.py +0 -0
  38. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/base.py +0 -0
  39. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/format.py +0 -0
  40. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/length.py +0 -0
  41. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/pii.py +0 -0
  42. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/pipeline.py +0 -0
  43. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/topic.py +0 -0
  44. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/guardrails/toxicity.py +0 -0
  45. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/models.py +0 -0
  46. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/observer.py +0 -0
  47. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/parser.py +0 -0
  48. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/policy.py +0 -0
  49. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/pricing.py +0 -0
  50. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/prompt.py +0 -0
  51. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/__init__.py +0 -0
  52. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/anthropic_provider.py +0 -0
  53. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/base.py +0 -0
  54. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/fallback.py +0 -0
  55. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/gemini_provider.py +0 -0
  56. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/ollama_provider.py +0 -0
  57. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/openai_provider.py +0 -0
  58. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/providers/stubs.py +0 -0
  59. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/__init__.py +0 -0
  60. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/bm25.py +0 -0
  61. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/chunking.py +0 -0
  62. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/hybrid.py +0 -0
  63. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/loaders.py +0 -0
  64. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/reranker.py +0 -0
  65. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/__init__.py +0 -0
  66. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/chroma.py +0 -0
  67. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/memory.py +0 -0
  68. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/pinecone.py +0 -0
  69. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/stores/sqlite.py +0 -0
  70. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/tools.py +0 -0
  71. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/rag/vector_store.py +0 -0
  72. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/security.py +0 -0
  73. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/structured.py +0 -0
  74. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/__init__.py +0 -0
  75. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/data_tools.py +0 -0
  76. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/datetime_tools.py +0 -0
  77. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/file_tools.py +0 -0
  78. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/memory_tools.py +0 -0
  79. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/text_tools.py +0 -0
  80. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/toolbox/web_tools.py +0 -0
  81. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/__init__.py +0 -0
  82. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/base.py +0 -0
  83. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/decorators.py +0 -0
  84. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/loader.py +0 -0
  85. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/tools/registry.py +0 -0
  86. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/trace.py +0 -0
  87. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/types.py +0 -0
  88. {selectools-0.16.0 → selectools-0.16.2}/src/selectools/usage.py +0 -0
  89. {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/dependency_links.txt +0 -0
  90. {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/entry_points.txt +0 -0
  91. {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/requires.txt +0 -0
  92. {selectools-0.16.0 → selectools-0.16.2}/src/selectools.egg-info/top_level.txt +0 -0
  93. {selectools-0.16.0 → selectools-0.16.2}/tests/test_audit.py +0 -0
  94. {selectools-0.16.0 → selectools-0.16.2}/tests/test_cache.py +0 -0
  95. {selectools-0.16.0 → selectools-0.16.2}/tests/test_cache_redis.py +0 -0
  96. {selectools-0.16.0 → selectools-0.16.2}/tests/test_cli.py +0 -0
  97. {selectools-0.16.0 → selectools-0.16.2}/tests/test_coherence.py +0 -0
  98. {selectools-0.16.0 → selectools-0.16.2}/tests/test_entity_memory.py +0 -0
  99. {selectools-0.16.0 → selectools-0.16.2}/tests/test_env.py +0 -0
  100. {selectools-0.16.0 → selectools-0.16.2}/tests/test_guardrails.py +0 -0
  101. {selectools-0.16.0 → selectools-0.16.2}/tests/test_knowledge.py +0 -0
  102. {selectools-0.16.0 → selectools-0.16.2}/tests/test_knowledge_graph.py +0 -0
  103. {selectools-0.16.0 → selectools-0.16.2}/tests/test_memory.py +0 -0
  104. {selectools-0.16.0 → selectools-0.16.2}/tests/test_parser.py +0 -0
  105. {selectools-0.16.0 → selectools-0.16.2}/tests/test_policy.py +0 -0
  106. {selectools-0.16.0 → selectools-0.16.2}/tests/test_prompt.py +0 -0
  107. {selectools-0.16.0 → selectools-0.16.2}/tests/test_routing_mode.py +0 -0
  108. {selectools-0.16.0 → selectools-0.16.2}/tests/test_security.py +0 -0
  109. {selectools-0.16.0 → selectools-0.16.2}/tests/test_sessions.py +0 -0
  110. {selectools-0.16.0 → selectools-0.16.2}/tests/test_structured.py +0 -0
  111. {selectools-0.16.0 → selectools-0.16.2}/tests/test_summarize_on_trim.py +0 -0
  112. {selectools-0.16.0 → selectools-0.16.2}/tests/test_trace.py +0 -0
  113. {selectools-0.16.0 → selectools-0.16.2}/tests/test_v016_regression.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selectools
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking.
5
5
  Author-email: John <johnnichev@gmail.com>
6
6
  License-Expression: LGPL-3.0-or-later
@@ -65,7 +65,7 @@ Dynamic: license-file
65
65
  - **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
66
66
  - **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
67
67
  - **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
68
- - **182 new tests** (total: 1365)
68
+ - **182 new tests** (total: 1421 with v0.16.1 consolidation)
69
69
 
70
70
  > Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
71
71
 
@@ -135,7 +135,7 @@ Dynamic: license-file
135
135
  - **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
136
136
  - **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
137
137
  - **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
138
- - **1365 Tests**: Unit, integration, regression, and E2E with real API calls
138
+ - **1421 Tests**: Unit, integration, regression, and E2E with real API calls
139
139
 
140
140
  ## Install
141
141
 
@@ -16,7 +16,7 @@
16
16
  - **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
17
17
  - **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
18
18
  - **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
19
- - **182 new tests** (total: 1365)
19
+ - **182 new tests** (total: 1421 with v0.16.1 consolidation)
20
20
 
21
21
  > Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
22
22
 
@@ -86,7 +86,7 @@
86
86
  - **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
87
87
  - **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
88
88
  - **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
89
- - **1365 Tests**: Unit, integration, regression, and E2E with real API calls
89
+ - **1421 Tests**: Unit, integration, regression, and E2E with real API calls
90
90
 
91
91
  ## Install
92
92
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "selectools"
7
- version = "0.16.0"
7
+ version = "0.16.2"
8
8
  description = "Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  """Public exports for the selectools package."""
2
2
 
3
- __version__ = "0.16.0"
3
+ __version__ = "0.16.2"
4
4
 
5
5
  # Import submodules (lazy loading for optional dependencies)
6
6
  from . import embeddings, guardrails, models, rag, toolbox
@@ -1976,6 +1976,7 @@ class Agent:
1976
1976
  _history_checkpoint = len(self._history)
1977
1977
  iteration = 0
1978
1978
 
1979
+ original_system_prompt = self._system_prompt
1979
1980
  self._wire_fallback_observer(run_id)
1980
1981
  self._notify_observers("on_run_start", run_id, messages, self._system_prompt)
1981
1982
 
@@ -2112,8 +2113,8 @@ class Agent:
2112
2113
  usage=copy.copy(self.usage),
2113
2114
  )
2114
2115
  self._notify_observers("on_run_end", run_id, _result)
2115
- yield _result
2116
2116
  self._call_hook("on_agent_end", final_response, self.usage)
2117
+ yield _result
2117
2118
  return
2118
2119
 
2119
2120
  if self.config.routing_only:
@@ -2260,9 +2261,9 @@ class Agent:
2260
2261
  usage=copy.copy(self.usage),
2261
2262
  )
2262
2263
  self._notify_observers("on_run_end", run_id, _result)
2263
- yield _result
2264
2264
  self._memory_add(final_msg, run_id)
2265
2265
  self._call_hook("on_agent_end", final_msg, self.usage)
2266
+ yield _result
2266
2267
  return
2267
2268
  except Exception as exc:
2268
2269
  if not self.memory:
@@ -2274,6 +2275,7 @@ class Agent:
2274
2275
  raise
2275
2276
  finally:
2276
2277
  self._unwire_fallback_observer()
2278
+ self._system_prompt = original_system_prompt
2277
2279
 
2278
2280
  async def arun(
2279
2281
  self,
@@ -2637,6 +2639,14 @@ class Agent:
2637
2639
  cost=0.0,
2638
2640
  chunk_count=chunk_counter["count"],
2639
2641
  )
2642
+ if self.usage.iterations:
2643
+ self.usage.tool_usage[tool.name] = (
2644
+ self.usage.tool_usage.get(tool.name, 0) + 1
2645
+ )
2646
+ self.usage.tool_tokens[tool.name] = (
2647
+ self.usage.tool_tokens.get(tool.name, 0)
2648
+ + self.usage.iterations[-1].total_tokens
2649
+ )
2640
2650
  except Exception as exc:
2641
2651
  duration = time.time() - start_time
2642
2652
  self._call_hook("on_tool_error", tool.name, exc, parameters)
@@ -8,6 +8,7 @@ extraction, deduplication, and LRU pruning.
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
+ import threading
11
12
  import time
12
13
  from dataclasses import dataclass, field
13
14
  from typing import Any, Dict, List, Optional
@@ -92,15 +93,17 @@ class EntityMemory:
92
93
  self._max_entities = max_entities
93
94
  self._relevance_window = relevance_window
94
95
  self._entities: Dict[str, Entity] = {}
96
+ self._lock = threading.Lock()
95
97
 
96
98
  @property
97
99
  def entities(self) -> List[Entity]:
98
100
  """All tracked entities, sorted by most recently mentioned."""
99
- return sorted(
100
- self._entities.values(),
101
- key=lambda e: e.last_mentioned,
102
- reverse=True,
103
- )
101
+ with self._lock:
102
+ return sorted(
103
+ self._entities.values(),
104
+ key=lambda e: e.last_mentioned,
105
+ reverse=True,
106
+ )
104
107
 
105
108
  def extract_entities(
106
109
  self,
@@ -174,28 +177,31 @@ class EntityMemory:
174
177
 
175
178
  Deduplicates by name (case-insensitive), updates mention counts
176
179
  and attributes, and prunes if over ``max_entities``.
180
+
181
+ Thread-safe: protected by an internal lock.
177
182
  """
178
183
  now = time.time()
179
- for entity in entities:
180
- key = entity.name.lower()
181
- if key in self._entities:
182
- existing = self._entities[key]
183
- existing.mention_count += 1
184
- existing.last_mentioned = now
185
- existing.attributes.update(entity.attributes)
186
- else:
187
- entity.last_mentioned = now
188
- self._entities[key] = entity
189
-
190
- # LRU prune: remove least recently mentioned
191
- if len(self._entities) > self._max_entities:
192
- sorted_keys = sorted(
193
- self._entities.keys(),
194
- key=lambda k: self._entities[k].last_mentioned,
195
- )
196
- excess = len(self._entities) - self._max_entities
197
- for k in sorted_keys[:excess]:
198
- del self._entities[k]
184
+ with self._lock:
185
+ for entity in entities:
186
+ key = entity.name.lower()
187
+ if key in self._entities:
188
+ existing = self._entities[key]
189
+ existing.mention_count += 1
190
+ existing.last_mentioned = now
191
+ existing.attributes.update(entity.attributes)
192
+ else:
193
+ entity.last_mentioned = now
194
+ self._entities[key] = entity
195
+
196
+ # LRU prune: remove least recently mentioned
197
+ if len(self._entities) > self._max_entities:
198
+ sorted_keys = sorted(
199
+ self._entities.keys(),
200
+ key=lambda k: self._entities[k].last_mentioned,
201
+ )
202
+ excess = len(self._entities) - self._max_entities
203
+ for k in sorted_keys[:excess]:
204
+ del self._entities[k]
199
205
 
200
206
  def build_context(self) -> str:
201
207
  """Build a context string for prompt injection.
@@ -9,6 +9,7 @@ for long-term facts.
9
9
  from __future__ import annotations
10
10
 
11
11
  import os
12
+ import threading
12
13
  import time
13
14
  from datetime import datetime, timedelta
14
15
  from typing import Any, Dict, List, Optional
@@ -39,6 +40,7 @@ class KnowledgeMemory:
39
40
  self._directory = directory
40
41
  self._recent_days = recent_days
41
42
  self._max_context_chars = max_context_chars
43
+ self._lock = threading.Lock()
42
44
  os.makedirs(directory, exist_ok=True)
43
45
 
44
46
  @property
@@ -66,17 +68,18 @@ class KnowledgeMemory:
66
68
  timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
67
69
  entry = f"[{timestamp}] [{category}] {content}"
68
70
 
69
- # Write to daily log
71
+ # Write to daily log (thread-safe)
70
72
  today = now.strftime("%Y-%m-%d")
71
73
  log_path = os.path.join(self._directory, f"{today}.log")
72
- with open(log_path, "a", encoding="utf-8") as f:
73
- f.write(entry + "\n")
74
-
75
- # Optionally write to persistent memory
76
- if persistent:
77
- mem_path = os.path.join(self._directory, "MEMORY.md")
78
- with open(mem_path, "a", encoding="utf-8") as f:
79
- f.write(f"- [{category}] {content}\n")
74
+ with self._lock:
75
+ with open(log_path, "a", encoding="utf-8") as f:
76
+ f.write(entry + "\n")
77
+
78
+ # Optionally write to persistent memory
79
+ if persistent:
80
+ mem_path = os.path.join(self._directory, "MEMORY.md")
81
+ with open(mem_path, "a", encoding="utf-8") as f:
82
+ f.write(f"- [{category}] {content}\n")
80
83
 
81
84
  return f"Remembered: {content}"
82
85
 
@@ -133,7 +133,9 @@ class SQLiteTripleStore:
133
133
  self._ensure_table()
134
134
 
135
135
  def _connect(self) -> sqlite3.Connection:
136
- return sqlite3.connect(self._db_path)
136
+ conn = sqlite3.connect(self._db_path)
137
+ conn.execute("PRAGMA journal_mode=WAL")
138
+ return conn
137
139
 
138
140
  def _ensure_table(self) -> None:
139
141
  conn = self._connect()
@@ -249,7 +251,7 @@ class SQLiteTripleStore:
249
251
  def count(self) -> int:
250
252
  conn = self._connect()
251
253
  try:
252
- return conn.execute("SELECT COUNT(*) FROM triples").fetchone()[0]
254
+ return int(conn.execute("SELECT COUNT(*) FROM triples").fetchone()[0])
253
255
  finally:
254
256
  conn.close()
255
257
 
@@ -416,7 +418,7 @@ class KnowledgeGraphMemory:
416
418
  keywords = [w for w in query.lower().split() if len(w) > 2]
417
419
  if not keywords:
418
420
  return []
419
- results = self._store.query(keywords)
421
+ results: List[Triple] = self._store.query(keywords)
420
422
  return results[: self._max_context_triples]
421
423
 
422
424
  def build_context(self, query: str = "") -> str:
@@ -156,6 +156,7 @@ class ConversationMemory:
156
156
  mem._messages = [Message.from_dict(m) for m in data.get("messages", [])]
157
157
  mem._summary = data.get("summary")
158
158
  mem._last_trimmed = []
159
+ mem._fix_tool_pair_boundary()
159
160
  return mem
160
161
 
161
162
  def _enforce_limits(self) -> None:
@@ -92,7 +92,7 @@ class JsonFileSessionStore:
92
92
  def _is_expired(self, data: Dict[str, Any]) -> bool:
93
93
  if self._default_ttl is None:
94
94
  return False
95
- updated_at = data.get("updated_at", data.get("created_at", 0))
95
+ updated_at: float = data.get("updated_at", data.get("created_at", 0))
96
96
  return (time.time() - updated_at) > self._default_ttl
97
97
 
98
98
  # -- public API --------------------------------------------------------
@@ -304,7 +304,7 @@ class SQLiteSessionStore:
304
304
  try:
305
305
  cursor = conn.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,))
306
306
  conn.commit()
307
- return cursor.rowcount > 0
307
+ return int(cursor.rowcount) > 0
308
308
  finally:
309
309
  conn.close()
310
310
 
@@ -433,7 +433,7 @@ class RedisSessionStore:
433
433
  key = self._key(session_id)
434
434
  meta_key = self._meta_key(session_id)
435
435
  removed = self._client.delete(key, meta_key)
436
- return removed > 0
436
+ return int(removed) > 0
437
437
 
438
438
  def exists(self, session_id: str) -> bool:
439
439
  return bool(self._client.exists(self._key(session_id)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: selectools
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking.
5
5
  Author-email: John <johnnichev@gmail.com>
6
6
  License-Expression: LGPL-3.0-or-later
@@ -65,7 +65,7 @@ Dynamic: license-file
65
65
  - **Entity Memory** — Auto-extract named entities (person, org, project) across turns with LRU-pruned registry and system prompt injection.
66
66
  - **Knowledge Graph** — Relationship triple extraction with in-memory and SQLite storage. Query-relevant triples auto-injected into prompts.
67
67
  - **Cross-Session Knowledge** — Daily logs + persistent facts with auto-registered `remember` tool. Give your agent durable memory across conversations.
68
- - **182 new tests** (total: 1365)
68
+ - **182 new tests** (total: 1421 with v0.16.1 consolidation)
69
69
 
70
70
  > Full changelog: [CHANGELOG.md](https://github.com/johnnichev/selectools/blob/main/CHANGELOG.md)
71
71
 
@@ -135,7 +135,7 @@ Dynamic: license-file
135
135
  - **Cross-Session Knowledge**: Daily logs + persistent memory with `remember` tool
136
136
  - **37 Examples**: RAG, hybrid search, streaming, structured output, traces, batch, policy, observer, guardrails, audit, sessions, entity memory, knowledge graph, and more
137
137
  - **AgentObserver Protocol**: 19 lifecycle events with `run_id` correlation, `LoggingObserver`, OTel export
138
- - **1365 Tests**: Unit, integration, regression, and E2E with real API calls
138
+ - **1421 Tests**: Unit, integration, regression, and E2E with real API calls
139
139
 
140
140
  ## Install
141
141
 
@@ -87,18 +87,24 @@ tests/test_cache.py
87
87
  tests/test_cache_redis.py
88
88
  tests/test_cli.py
89
89
  tests/test_coherence.py
90
+ tests/test_consolidation_regression.py
90
91
  tests/test_entity_memory.py
91
92
  tests/test_env.py
92
93
  tests/test_guardrails.py
93
94
  tests/test_knowledge.py
94
95
  tests/test_knowledge_graph.py
95
96
  tests/test_memory.py
97
+ tests/test_memory_async.py
98
+ tests/test_memory_boundary.py
99
+ tests/test_memory_integration.py
96
100
  tests/test_parser.py
97
101
  tests/test_policy.py
98
102
  tests/test_prompt.py
99
103
  tests/test_routing_mode.py
100
104
  tests/test_security.py
101
105
  tests/test_sessions.py
106
+ tests/test_sessions_edge_cases.py
107
+ tests/test_sessions_redis.py
102
108
  tests/test_structured.py
103
109
  tests/test_summarize_on_trim.py
104
110
  tests/test_trace.py