sibyllabs Claude Opus 4.7 commited on
Commit
b605fc1
·
1 Parent(s): 9bbb2ee

fix: cross-tier timestamp precision mismatch (client 0.4.3)

Browse files

_utc_now_iso() produced 6-digit microsecond timestamps while SQL
DEFAULTs produced 3-digit milliseconds. The width difference broke
lexicographic sorting across tiers ('Z' 0x5A > '3' 0x33 at position
24). Truncated to 3-digit milliseconds matching SQLite output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

sibyl-memory-client/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to `sibyl-memory-client` are recorded here. Format
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning
5
  follows [SemVer](https://semver.org/).
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ## [0.4.2] - 2026-05-22
8
 
9
  `_sanitize_fts5_query` default mode flipped from phrase-match to
 
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning
5
  follows [SemVer](https://semver.org/).
6
 
7
+ ## [0.4.3] - 2026-05-26
8
+
9
+ ### Fixed
10
+
11
+ - **Cross-tier timestamp precision mismatch.** `_utc_now_iso()` produced
12
+ 6-digit microsecond timestamps (`45.525358Z`) while every SQL DEFAULT
13
+ used SQLite's 3-digit milliseconds (`45.525Z`). The width difference
14
+ broke lexicographic sorting across tiers: `'Z'` (0x5A) > `'3'` (0x33)
15
+ at position 24, so a journal event written 0.358 ms after an entity
16
+ update would sort *before* it in any `ORDER BY ts` merge. Now truncated
17
+ to 3-digit milliseconds to match SQLite output. Affects journal_events,
18
+ revenue_events, error_events, learning_runs.completed_at, and
19
+ skill_proposals.reviewed_at. Existing rows retain their original
20
+ precision (cosmetic, sort-correct within their own tier). Reported by
21
+ external tester smoke test on sibyl-memory-mcp 0.1.2.
22
+
23
  ## [0.4.2] - 2026-05-22
24
 
25
  `_sanitize_fts5_query` default mode flipped from phrase-match to
sibyl-memory-client/README.md CHANGED
@@ -47,13 +47,15 @@ Most agent-memory products store everything on someone else's servers, treat eve
47
  | Frozen things, kept but out of the way | ARCHIVE | `archive_entity(kind, name)` |
48
  | Search across everything | FTS5 | `search_entities(query)` |
49
 
50
- ## What's in v0.2.x
51
 
52
  - The full five-tier memory model and the API surface above.
 
53
  - Multi-tenant isolation: one machine can hold separate memory for separate identities.
54
  - Self-learning module (paid-tier): the agent watches your patterns and proposes reusable skills.
55
  - Memory linter (paid-tier): a health check on the local database.
56
  - Tier gating: free-tier callers get clear errors pointing at the upgrade page; paid-tier callers get full access.
 
57
 
58
  ## Tier model
59
 
 
47
  | Frozen things, kept but out of the way | ARCHIVE | `archive_entity(kind, name)` |
48
  | Search across everything | FTS5 | `search_entities(query)` |
49
 
50
+ ## What's in v0.4.x
51
 
52
  - The full five-tier memory model and the API surface above.
53
+ - Cross-tier FTS5 search across entities, state, reference, and journal tiers.
54
  - Multi-tenant isolation: one machine can hold separate memory for separate identities.
55
  - Self-learning module (paid-tier): the agent watches your patterns and proposes reusable skills.
56
  - Memory linter (paid-tier): a health check on the local database.
57
  - Tier gating: free-tier callers get clear errors pointing at the upgrade page; paid-tier callers get full access.
58
+ - Uniform millisecond timestamp precision across all tiers (v0.4.3).
59
 
60
  ## Tier model
61
 
sibyl-memory-client/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "sibyl-memory-client"
7
- version = "0.4.2"
8
  description = "Local-first agentic memory SDK. SQLite-backed five-tier hierarchical schema, FTS5 search, multi-tenant, with self-learning skill detection and local memory linter. Foundation of the Sibyl Memory Plugin family."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
 
4
 
5
  [project]
6
  name = "sibyl-memory-client"
7
+ version = "0.4.3"
8
  description = "Local-first agentic memory SDK. SQLite-backed five-tier hierarchical schema, FTS5 search, multi-tenant, with self-learning skill detection and local memory linter. Foundation of the Sibyl Memory Plugin family."
9
  authors = [{ name = "SIBYL, Sibyl Labs LLC", email = "sibyl@sibyllabs.org" }]
10
  license = { text = "MIT" }
sibyl-memory-client/src/sibyl_memory_client/storage.py CHANGED
@@ -40,13 +40,15 @@ _DB_SIDECAR_SUFFIXES = ("-wal", "-shm")
40
 
41
 
42
  def _utc_now_iso() -> str:
43
- """Return current UTC time in ISO 8601 microsecond-precision format.
44
-
45
- Higher precision than the schema's millisecond default (`%f` in
46
- SQLite's strftime) reduces ts collisions when multiple writes land in
47
- the same millisecond. Microsecond-precision strings sort correctly
48
- lexically since the format is fixed-width."""
49
- return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
 
 
50
 
51
 
52
  def new_id() -> str:
 
40
 
41
 
42
  def _utc_now_iso() -> str:
43
+ """Return current UTC time in ISO 8601 millisecond-precision format.
44
+
45
+ Matches the 3-digit precision of SQLite's ``strftime('%f')`` so that
46
+ timestamps produced by Python and by SQL DEFAULTs sort identically in
47
+ lexicographic comparisons. Prior versions emitted 6-digit microseconds
48
+ which broke cross-tier ``ORDER BY ts`` merges ('Z' > '3' at position 24).
49
+ Fixed in 0.4.3."""
50
+ now = datetime.now(timezone.utc)
51
+ return now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
52
 
53
 
54
  def new_id() -> str: