thoughtflow 0.2.3__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.
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/CHANGELOG.md +8 -1
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/PKG-INFO +2 -2
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/README.md +1 -1
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/pyproject.toml +1 -1
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/__init__.py +1 -1
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/memory.py +25 -16
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_memory.py +8 -9
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/.gitignore +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/LICENSE +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/_cron_expr.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/_util.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/action.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/_http.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/_substitution.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/ask.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/call.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/fetch.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/noop.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/notify.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/post.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/read_file.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/run.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/say.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/scrape.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/search.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/sleep.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/wait.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/actions/write_file.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/agent.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/agents/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/agents/planact.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/agents/react.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/agents/reflect.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/chat.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/chron.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/delegate.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/embed.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/eval/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/eval/harness.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/llm.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/llm_cache.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/llm_params.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/mcp.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/message.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/providers/README.md +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/providers/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/providers/cerebras.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/providers/groq.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/providers/openrouter.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/py.typed +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/this.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/thought.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/decide.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/thoughts/plan.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/tool.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/src/thoughtflow/workflow.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/conftest.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/smoke_helpers.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/test_chron.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/test_embed_providers.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/test_llm_providers.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/test_scrape.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/integration/test_search_providers.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/__init__.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_action.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_actions.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_agent.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_chat.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_chron.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_delegate.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_embed.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_eval.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_foundation_agent_response.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_llm.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_llm_provider_integration.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_mcp.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_message.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_replay.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_this.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_thought.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_tool.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_util.py +0 -0
- {thoughtflow-0.2.3 → thoughtflow-0.2.4}/tests/unit/test_workflow.py +0 -0
|
@@ -5,6 +5,12 @@ 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
|
+
|
|
8
14
|
## [0.2.3] - 2026-07-11
|
|
9
15
|
|
|
10
16
|
### Fixed
|
|
@@ -202,7 +208,8 @@ grown from MEMORY itself, with zero new concepts.
|
|
|
202
208
|
---
|
|
203
209
|
|
|
204
210
|
<!-- Release links -->
|
|
205
|
-
[Unreleased]: https://github.com/jrolf/thoughtflow/compare/v0.2.
|
|
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
|
|
206
213
|
[0.2.3]: https://github.com/jrolf/thoughtflow/compare/v0.2.2...v0.2.3
|
|
207
214
|
[0.2.2]: https://github.com/jrolf/thoughtflow/compare/v0.2.0...v0.2.2
|
|
208
215
|
[0.2.0]: https://github.com/jrolf/thoughtflow/compare/v0.1.3...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thoughtflow
|
|
3
|
-
Version: 0.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.
|
|
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.
|
|
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.
|
|
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"
|
|
@@ -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]
|
|
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]
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
289
|
-
|
|
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
|
|
1371
|
-
|
|
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':
|
|
@@ -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
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
assert
|
|
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
|
|
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
|