thoughtflow 0.2.2__tar.gz → 0.2.4__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 (87) hide show
  1. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/CHANGELOG.md +14 -1
  2. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/PKG-INFO +2 -2
  3. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/README.md +1 -1
  4. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/pyproject.toml +1 -1
  5. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/__init__.py +1 -1
  6. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/memory.py +25 -16
  7. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/message.py +5 -6
  8. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_memory.py +8 -9
  9. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/.gitignore +0 -0
  10. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/LICENSE +0 -0
  11. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/_cron_expr.py +0 -0
  12. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/_util.py +0 -0
  13. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/action.py +0 -0
  14. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/__init__.py +0 -0
  15. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/_http.py +0 -0
  16. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/_substitution.py +0 -0
  17. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/ask.py +0 -0
  18. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/call.py +0 -0
  19. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/fetch.py +0 -0
  20. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/noop.py +0 -0
  21. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/notify.py +0 -0
  22. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/post.py +0 -0
  23. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/read_file.py +0 -0
  24. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/run.py +0 -0
  25. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/say.py +0 -0
  26. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/scrape.py +0 -0
  27. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/search.py +0 -0
  28. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/sleep.py +0 -0
  29. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/wait.py +0 -0
  30. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/actions/write_file.py +0 -0
  31. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/agent.py +0 -0
  32. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/agents/__init__.py +0 -0
  33. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/agents/planact.py +0 -0
  34. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/agents/react.py +0 -0
  35. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/agents/reflect.py +0 -0
  36. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/chat.py +0 -0
  37. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/chron.py +0 -0
  38. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/delegate.py +0 -0
  39. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/embed.py +0 -0
  40. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/eval/__init__.py +0 -0
  41. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/eval/harness.py +0 -0
  42. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/llm.py +0 -0
  43. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/llm_cache.py +0 -0
  44. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/llm_params.py +0 -0
  45. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/mcp.py +0 -0
  46. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/providers/README.md +0 -0
  47. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/providers/__init__.py +0 -0
  48. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/providers/cerebras.py +0 -0
  49. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/providers/groq.py +0 -0
  50. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/providers/openrouter.py +0 -0
  51. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/py.typed +0 -0
  52. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/this.py +0 -0
  53. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/thought.py +0 -0
  54. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/__init__.py +0 -0
  55. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/decide.py +0 -0
  56. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/plan.py +0 -0
  57. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/tool.py +0 -0
  58. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/src/thoughtflow/workflow.py +0 -0
  59. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/__init__.py +0 -0
  60. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/conftest.py +0 -0
  61. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/__init__.py +0 -0
  62. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/smoke_helpers.py +0 -0
  63. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/test_chron.py +0 -0
  64. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/test_embed_providers.py +0 -0
  65. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/test_llm_providers.py +0 -0
  66. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/test_scrape.py +0 -0
  67. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/integration/test_search_providers.py +0 -0
  68. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/__init__.py +0 -0
  69. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_action.py +0 -0
  70. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_actions.py +0 -0
  71. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_agent.py +0 -0
  72. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_chat.py +0 -0
  73. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_chron.py +0 -0
  74. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_delegate.py +0 -0
  75. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_embed.py +0 -0
  76. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_eval.py +0 -0
  77. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_foundation_agent_response.py +0 -0
  78. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_llm.py +0 -0
  79. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_llm_provider_integration.py +0 -0
  80. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_mcp.py +0 -0
  81. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_message.py +0 -0
  82. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_replay.py +0 -0
  83. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_this.py +0 -0
  84. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_thought.py +0 -0
  85. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_tool.py +0 -0
  86. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_util.py +0 -0
  87. {thoughtflow-0.2.2 → thoughtflow-0.2.4}/tests/unit/test_workflow.py +0 -0
@@ -5,6 +5,17 @@ All notable changes to ThoughtFlow will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.4] - 2026-07-11
9
+
10
+ ### Fixed
11
+ - MEMORY index tie-breaking: events with identical `dt_utc` now preserve insertion order (fixes flaky replay and get_events tests on CI)
12
+ - `add_exchange()` stamp now includes response payload so duplicate-key recordings are not overwritten
13
+
14
+ ## [0.2.3] - 2026-07-11
15
+
16
+ ### Fixed
17
+ - Python 3.9 import failure: `TypeAlias` removed from `message.py` (stdlib support starts at 3.10)
18
+
8
19
  ## [0.2.2] - 2026-07-10
9
20
 
10
21
  Production-hardening release: THOUGHT/PLAN parsing improvements (Foundation Agent feedback) plus unified LLM param pipeline, prompt caching, and native provider convenience classes.
@@ -197,7 +208,9 @@ grown from MEMORY itself, with zero new concepts.
197
208
  ---
198
209
 
199
210
  <!-- Release links -->
200
- [Unreleased]: https://github.com/jrolf/thoughtflow/compare/v0.2.2...HEAD
211
+ [Unreleased]: https://github.com/jrolf/thoughtflow/compare/v0.2.4...HEAD
212
+ [0.2.4]: https://github.com/jrolf/thoughtflow/compare/v0.2.3...v0.2.4
213
+ [0.2.3]: https://github.com/jrolf/thoughtflow/compare/v0.2.2...v0.2.3
201
214
  [0.2.2]: https://github.com/jrolf/thoughtflow/compare/v0.2.0...v0.2.2
202
215
  [0.2.0]: https://github.com/jrolf/thoughtflow/compare/v0.1.3...v0.2.0
203
216
  [0.1.3]: https://github.com/jrolf/thoughtflow/compare/v0.1.2...v0.1.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thoughtflow
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Powerful AI systems from simple parts. Composable cognitive primitives, event-sourced memory, and autonomous agents in pure Python with zero dependencies.
5
5
  Project-URL: Homepage, https://github.com/jrolf/thoughtflow
6
6
  Project-URL: Documentation, https://thoughtflow.dev
@@ -164,7 +164,7 @@ supply-chain anxiety. The core library uses only Python's standard library —
164
164
  pip install --upgrade thoughtflow
165
165
 
166
166
  # Pin to a specific version for stability
167
- pip install thoughtflow==0.2.2
167
+ pip install thoughtflow==0.2.4
168
168
 
169
169
  # Check your installed version
170
170
  python -c "import thoughtflow; print(thoughtflow.__version__)"
@@ -93,7 +93,7 @@ supply-chain anxiety. The core library uses only Python's standard library —
93
93
  pip install --upgrade thoughtflow
94
94
 
95
95
  # Pin to a specific version for stability
96
- pip install thoughtflow==0.2.2
96
+ pip install thoughtflow==0.2.4
97
97
 
98
98
  # Check your installed version
99
99
  python -c "import thoughtflow; print(thoughtflow.__version__)"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "thoughtflow"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Powerful AI systems from simple parts. Composable cognitive primitives, event-sourced memory, and autonomous agents in pure Python with zero dependencies."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -105,7 +105,7 @@ try:
105
105
 
106
106
  __version__ = _get_version("thoughtflow")
107
107
  except Exception:
108
- __version__ = "0.2.2"
108
+ __version__ = "0.2.4"
109
109
 
110
110
  # Public API
111
111
  __all__ = [
@@ -183,7 +183,8 @@ class MEMORY:
183
183
  self.idx_refs = [] # Reflection [timestamp, stamp] pairs
184
184
  self.idx_logs = [] # Log [timestamp, stamp] pairs
185
185
  self.idx_vars = [] # Variable-change [timestamp, stamp] pairs
186
- self.idx_all = [] # Master index (all [timestamp, stamp] pairs)
186
+ self.idx_all = [] # Master index (all [timestamp, seq, stamp] triples)
187
+ self._index_seq = 0 # Insertion tie-breaker when dt_utc collides
187
188
 
188
189
  # VARIABLE LAYER: Full history with timestamps
189
190
  # vars[key] = [[stamp1, value1], [stamp2, value2], ...]
@@ -235,15 +236,17 @@ class MEMORY:
235
236
 
236
237
  def _add_to_index(self, index_list, timestamp, stamp):
237
238
  """
238
- Insert [timestamp, stamp] pair maintaining sorted order by timestamp.
239
-
240
- Args:
241
- index_list: One of the idx_* lists
242
- timestamp: ISO timestamp string (dt_utc)
243
- stamp: Event stamp ID
239
+ Insert [timestamp, seq, stamp] maintaining sorted order by timestamp.
240
+
241
+ When two events share the same dt_utc string (common in tight loops),
242
+ seq preserves insertion order instead of falling back to stamp sorting.
244
243
  """
245
- # bisect.insort sorts by first element of tuple/list (timestamp)
246
- bisect.insort(index_list, [timestamp, stamp])
244
+ self._index_seq += 1
245
+ bisect.insort(index_list, [timestamp, self._index_seq, stamp])
246
+
247
+ def _stamp_from_index_pair(self, pair):
248
+ """Return the stamp from a 2- or 3-element index pair (legacy compat)."""
249
+ return pair[-1]
247
250
 
248
251
  def _store_event(self, event_type, obj):
249
252
  """
@@ -278,15 +281,18 @@ class MEMORY:
278
281
  Get events from an index, optionally limited to last N.
279
282
 
280
283
  Args:
281
- index: One of the idx_* lists (format: [[timestamp, stamp], ...])
284
+ index: One of the idx_* lists ([timestamp, stamp] or [timestamp, seq, stamp])
282
285
  limit: Max events to return (-1 = all)
283
286
 
284
287
  Returns:
285
288
  List of event dicts
286
289
  """
287
290
  pairs = index if limit <= 0 else index[-limit:]
288
- # Extract stamp (second element) from each [timestamp, stamp] pair
289
- return [self.events[ts_stamp[1]] for ts_stamp in pairs if ts_stamp[1] in self.events]
291
+ return [
292
+ self.events[self._stamp_from_index_pair(ts_stamp)]
293
+ for ts_stamp in pairs
294
+ if self._stamp_from_index_pair(ts_stamp) in self.events
295
+ ]
290
296
 
291
297
  def _get_latest_desc(self, key):
292
298
  """
@@ -425,7 +431,7 @@ class MEMORY:
425
431
  request: JSON-serializable dict describing the request.
426
432
  response: JSON-serializable response payload.
427
433
  """
428
- stamp = event_stamp({'key': key, 'kind': kind})
434
+ stamp = event_stamp({'key': key, 'kind': kind, 'response': response})
429
435
  exchange = {
430
436
  'stamp' : stamp,
431
437
  'type' : 'llm',
@@ -1206,6 +1212,7 @@ class MEMORY:
1206
1212
  self.idx_logs = mem.idx_logs
1207
1213
  self.idx_vars = mem.idx_vars
1208
1214
  self.idx_all = mem.idx_all
1215
+ self._index_seq = mem._index_seq
1209
1216
  self.vars = mem.vars
1210
1217
  self.var_desc_history = mem.var_desc_history
1211
1218
  self.objects = mem.objects
@@ -1328,7 +1335,8 @@ class MEMORY:
1328
1335
  mem.idx_logs = data.get('idx_logs', [])
1329
1336
  mem.idx_vars = data.get('idx_vars', [])
1330
1337
  mem.idx_all = data.get('idx_all', [])
1331
-
1338
+ mem._index_seq = len(mem.idx_all)
1339
+
1332
1340
  return mem
1333
1341
 
1334
1342
  @classmethod
@@ -1367,8 +1375,9 @@ class MEMORY:
1367
1375
  # Store in data layer
1368
1376
  mem.events[stamp] = ev
1369
1377
 
1370
- # Create [timestamp, stamp] pair for indexes
1371
- ts_pair = [timestamp, stamp]
1378
+ # Create index pair; seq is assigned on load for stable ordering
1379
+ seq = len(mem.idx_all) + 1
1380
+ ts_pair = [timestamp, seq, stamp]
1372
1381
 
1373
1382
  # Add to appropriate index (direct append since already sorted by timestamp)
1374
1383
  if event_type == 'msg':
@@ -14,13 +14,12 @@ Typical structure:
14
14
  from __future__ import annotations
15
15
 
16
16
  from dataclasses import dataclass, field
17
- from typing import Any, Literal, TypeAlias
17
+ from typing import Any, Literal
18
18
 
19
-
20
- # Type aliases for clarity
21
- Role: TypeAlias = Literal["system", "user", "assistant", "tool"]
22
- MessageDict: TypeAlias = dict[str, Any]
23
- MessageList: TypeAlias = list[MessageDict]
19
+ # Plain aliases (no TypeAlias — added in Python 3.10; we support 3.9+).
20
+ Role = Literal["system", "user", "assistant", "tool"]
21
+ MessageDict = dict[str, Any]
22
+ MessageList = list[MessageDict]
24
23
 
25
24
 
26
25
  @dataclass
@@ -1308,20 +1308,19 @@ class TestGetEvents:
1308
1308
 
1309
1309
  def test_get_events_returns_chronological_order(self, memory):
1310
1310
  """
1311
- get_events must return events in chronological order.
1312
-
1313
- Order is determined by event stamps.
1314
-
1315
- Remove this test if: We change ordering.
1311
+ get_events must return events in insertion order.
1312
+
1313
+ Stamps embed a random component and are not guaranteed to sort
1314
+ lexicographically when events are created in the same clock tick.
1316
1315
  """
1317
1316
  memory.add_msg('user', 'First', channel='webapp')
1318
1317
  memory.add_log('Second')
1319
1318
  memory.add_ref('Third')
1320
-
1319
+
1321
1320
  events = memory.get_events()
1322
- stamps = [e['stamp'] for e in events]
1323
-
1324
- assert stamps == sorted(stamps)
1321
+ contents = [e['content'] for e in events]
1322
+
1323
+ assert contents == ['First', 'Second', 'Third']
1325
1324
 
1326
1325
  def test_get_events_respects_limit(self, memory):
1327
1326
  """
File without changes
File without changes