Skip to content

feat: implement SharedKnowledgeStore append-only + MVCC consistency model (Phase 1.5)#1134

Merged
Aureliolo merged 10 commits intomainfrom
feat/mvcc-org-memory
Apr 8, 2026
Merged

feat: implement SharedKnowledgeStore append-only + MVCC consistency model (Phase 1.5)#1134
Aureliolo merged 10 commits intomainfrom
feat/mvcc-org-memory

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

Replace SQLiteOrgFactStore internals with append-only operation log + materialized snapshot tables (D26 -- Phase 1.5). All writes append to org_facts_operation_log; a denormalized org_facts_snapshot table maintains the current committed state for fast reads.

Changes

  • Schema: Replace org_facts table with org_facts_operation_log (append-only log) + org_facts_snapshot (materialized snapshot)
  • Write path: save() appends PUBLISH to log + upserts snapshot; delete() appends RETRACT + marks snapshot retracted_at
  • Read path: get()/query()/list_by_category() filter retracted_at IS NULL (only active facts)
  • Time-travel: snapshot_at(timestamp) reconstructs point-in-time state from operation log; get_operation_log(fact_id) returns full audit trail
  • Models: Add OperationLogEntry and OperationLogSnapshot frozen Pydantic models with validators (content-operation alignment, created/retracted ordering)
  • OrgFactAuthor: Add autonomy_level: AutonomyLevel | None field (captured in operation log for audit)
  • OrgFact/OrgFactWriteRequest: Add tags: tuple[NotBlankStr, ...] field (metadata for cross-cutting concerns)
  • Backend wiring: HybridPromptRetrievalBackend.write() passes tags through to store
  • Observability: 4 new MVCC event constants in events.org_memory
  • Transactions: Explicit BEGIN IMMEDIATE in save/delete for defense-in-depth
  • Docs: Updated memory-consistency.md deployment section

Spec deviations (user-approved)

  • No feature flag: Pre-alpha with no users -- MVCC is the only implementation, no dual code paths
  • Methods on OrgFactStore: snapshot_at() and get_operation_log() placed on OrgFactStore protocol (not SharedKnowledgeStore, which is the separate Mem0 cross-agent protocol)

Test plan

  • 141 org memory unit tests (all passing)
  • 15,581 total unit tests (all passing, 5 skipped)
  • Tests cover: schema creation, publish/retract append semantics, version counter monotonicity, read filtering, time-travel queries, audit trail, concurrent publishes, model validation, tags persistence, error wrapping

Review coverage

Pre-reviewed by 6 agents (code-reviewer, type-design-analyzer, async-concurrency-reviewer, issue-resolution-verifier, docs-consistency, silent-failure-hunter). 7 findings addressed.

Closes #1130

Copilot AI review requested due to automatic review settings April 7, 2026 23:30
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 7, 2026 23:31 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Multi-Version Concurrency Control (MVCC) for the organizational memory system, transitioning the SQLite backend to an append-only operation log with a materialized snapshot. The changes introduce new models for operation logging, support for metadata tags, and functionality for point-in-time snapshots and audit trails. Feedback identifies critical improvements for the audit trail, specifically requiring the delete method to record the actual retracting author rather than the original author. Additionally, the snapshot logic needs adjustment to preserve the initial created_at timestamp during updates, and the operation log schema should be expanded to include all author metadata fields for complete historical reconstruction.

@@ -136,21 +194,68 @@ async def list_by_category(
...

async def delete(self, fact_id: NotBlankStr) -> bool:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The delete method should accept an author: OrgFactAuthor argument. Currently, the implementation reuses the author of the existing fact for the RETRACT operation log entry. This is incorrect for an audit trail, as it should record the identity of the agent or human who performed the retraction, not the original author of the fact.

Suggested change
async def delete(self, fact_id: NotBlankStr) -> bool:
async def delete(self, fact_id: NotBlankStr, author: OrgFactAuthor) -> bool:

Comment on lines 624 to 646
"INSERT OR REPLACE INTO org_facts_snapshot "
"(fact_id, content, category, tags, "
"author_agent_id, author_seniority, author_is_human, "
"author_autonomy_level, created_at, retracted_at, "
"version) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)",
(
fact.id,
fact.content,
fact.category.value,
_tags_to_json(fact.tags),
fact.author.agent_id,
fact.author.seniority.value if fact.author.seniority else None,
(fact.author.seniority.value if fact.author.seniority else None),
int(fact.author.is_human),
(
fact.author.autonomy_level.value
if fact.author.autonomy_level
else None
),
fact.created_at.isoformat(),
version,
),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The INSERT OR REPLACE statement overwrites the created_at field in the snapshot table with the current fact's timestamp. This violates the definition of created_at as the "Timestamp of first PUBLISH". When re-publishing (updating) a fact, the original creation timestamp should be preserved. Use INSERT INTO ... ON CONFLICT(fact_id) DO UPDATE to maintain the original created_at while updating other fields.

            await db.execute(
                "INSERT INTO org_facts_snapshot "
                "(fact_id, content, category, tags, "
                "author_agent_id, author_seniority, author_is_human, "
                "author_autonomy_level, created_at, retracted_at, "
                "version) "
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?) "
                "ON CONFLICT(fact_id) DO UPDATE SET "
                "content=excluded.content, "
                "category=excluded.category, "
                "tags=excluded.tags, "
                "author_agent_id=excluded.author_agent_id, "
                "author_seniority=excluded.author_seniority, "
                "author_is_human=excluded.author_is_human, "
                "author_autonomy_level=excluded.author_autonomy_level, "
                "retracted_at=NULL, "
                "version=excluded.version",
                (
                    fact.id,
                    fact.content,
                    fact.category.value,
                    _tags_to_json(fact.tags),
                    fact.author.agent_id,
                    (fact.author.seniority.value if fact.author.seniority else None),
                    int(fact.author.is_human),
                    (
                        fact.author.autonomy_level.value
                        if fact.author.autonomy_level
                        else None
                    ),
                    fact.created_at.isoformat(),
                    version,
                ),
            )

Comment on lines +690 to +702
version, now = await self._append_to_operation_log(
db,
fact_id=fact_id,
operation_type="RETRACT",
content=None,
tags=(),
author_agent_id=row["author_agent_id"],
author_autonomy_level=(
AutonomyLevel(row["author_autonomy_level"])
if row["author_autonomy_level"]
else None
),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

As noted in the protocol review, the RETRACT operation should use the author provided to the delete method rather than fetching the original author from the snapshot. This ensures the audit log correctly identifies who performed the retraction.

Comment on lines +53 to +64
CREATE TABLE IF NOT EXISTS org_facts_operation_log (
operation_id TEXT PRIMARY KEY,
fact_id TEXT NOT NULL,
operation_type TEXT NOT NULL CHECK(operation_type IN ('PUBLISH', 'RETRACT')),
content TEXT,
tags TEXT NOT NULL DEFAULT '[]',
author_agent_id TEXT,
author_autonomy_level TEXT,
timestamp TEXT NOT NULL,
version INTEGER NOT NULL,
UNIQUE(fact_id, version)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The org_facts_operation_log table is missing author_seniority and author_is_human columns. These fields are part of the OrgFactAuthor model and are necessary to fully reconstruct the author's state from the operation log (e.g., for historical auditing or time-travel queries).

_CREATE_OPERATION_LOG_SQL = """\
CREATE TABLE IF NOT EXISTS org_facts_operation_log (
    operation_id TEXT PRIMARY KEY,
    fact_id TEXT NOT NULL,
    operation_type TEXT NOT NULL CHECK(operation_type IN ('PUBLISH', 'RETRACT')),
    content TEXT,
    tags TEXT NOT NULL DEFAULT '[]',
    author_agent_id TEXT,
    author_seniority TEXT,
    author_is_human INTEGER NOT NULL DEFAULT 0,
    author_autonomy_level TEXT,
    timestamp TEXT NOT NULL,
    version INTEGER NOT NULL,
    UNIQUE(fact_id, version)
)
"""

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA f1c7733.
Ensure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice.

Scanned Files

None

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Phase 1.5 of the org memory consistency design by switching the SQLite org fact store to an append-only operation log + materialized snapshot (MVCC-style), adding audit/time-travel APIs, and extending org memory models/IO to include tags and author autonomy metadata.

Changes:

  • Replaced org_facts table semantics with org_facts_operation_log (append-only) + org_facts_snapshot (current state) and updated read/write paths accordingly.
  • Added MVCC/time-travel surface area (snapshot_at, get_operation_log), new MVCC Pydantic models, and new observability event constants.
  • Threaded new metadata through the stack (OrgFact.tags, OrgFactAuthor.autonomy_level) and expanded unit tests (including MVCC-specific test suite).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/synthorg/memory/org/store.py Core MVCC implementation: schema, append-only log writes, snapshot reads, time-travel queries, audit log retrieval.
src/synthorg/memory/org/models.py Adds tags/autonomy fields and introduces OperationLogEntry / OperationLogSnapshot models + validation.
src/synthorg/memory/org/hybrid_backend.py Passes tags from OrgFactWriteRequest into OrgFact persisted via the store.
src/synthorg/observability/events/org_memory.py Adds MVCC-specific structured logging event constants.
src/synthorg/memory/org/__init__.py Re-exports MVCC models in the org memory public API.
docs/design/memory-consistency.md Updates rollout narrative and clarifies MVCC APIs live on OrgFactStore.
CLAUDE.md Updates logging event-constants examples to include org memory MVCC events.
tests/unit/memory/org/test_store.py Updates/extends baseline store tests for MVCC behavior and tags.
tests/unit/memory/org/test_mvcc_store.py New MVCC-focused test suite (schema, versioning, retract semantics, snapshot_at, audit trail).
tests/unit/memory/org/test_models.py Adds tests for new author autonomy, tags, and MVCC model validations.
tests/unit/memory/org/test_hybrid_backend.py Adds coverage that backend writes persist and return tags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +611 to +616
db = self._require_connected()
try:
await db.execute("BEGIN IMMEDIATE")
version, _ = await self._append_to_operation_log(
db,
fact_id=fact.id,
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After BEGIN IMMEDIATE, if any sqlite3.Error occurs during the log append or snapshot upsert, the code raises without an explicit ROLLBACK. That can leave the connection in an open transaction and cause subsequent writes to fail. Add a rollback in the error path (e.g., except/finally) before raising OrgMemoryWriteError.

Copilot uses AI. Check for mistakes.
Comment on lines +677 to +690
db = self._require_connected()
try:
await db.execute("BEGIN IMMEDIATE")
cursor = await db.execute(
"SELECT fact_id, author_agent_id, author_autonomy_level "
"FROM org_facts_snapshot "
"WHERE fact_id = ? AND retracted_at IS NULL",
(fact_id,),
)
row = await cursor.fetchone()
if row is None:
await db.execute("ROLLBACK")
return False
version, now = await self._append_to_operation_log(
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete() opens BEGIN IMMEDIATE and then can raise on sqlite3.Error without rolling back the transaction. Please ensure a ROLLBACK happens on all error paths after the BEGIN (not only the row is None early return) so the connection doesn’t remain locked/in-transaction.

Copilot uses AI. Check for mistakes.
Comment on lines +680 to +701
cursor = await db.execute(
"SELECT fact_id, author_agent_id, author_autonomy_level "
"FROM org_facts_snapshot "
"WHERE fact_id = ? AND retracted_at IS NULL",
(fact_id,),
)
row = await cursor.fetchone()
if row is None:
await db.execute("ROLLBACK")
return False
version, now = await self._append_to_operation_log(
db,
fact_id=fact_id,
operation_type="RETRACT",
content=None,
tags=(),
author_agent_id=row["author_agent_id"],
author_autonomy_level=(
AutonomyLevel(row["author_autonomy_level"])
if row["author_autonomy_level"]
else None
),
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RETRACT log entry is currently populated with author_agent_id/author_autonomy_level read from the snapshot row (i.e., the last publisher), not the actor performing the retraction. Since OperationLogEntry.author_* is documented as “agent that performed the operation”, this will misattribute retract operations in the audit trail. Consider extending delete() to accept an OrgFactAuthor (passed from the caller) or storing NULL/system values for retract until the API can carry the retractor identity.

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +301
OrgMemoryQueryError: If the JSON is not a list.
"""
parsed = json.loads(raw)
if not isinstance(parsed, list):
msg = f"Tags must be a JSON array, got {type(parsed).__name__}"
raise OrgMemoryQueryError(msg)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_tags_from_json() raises OrgMemoryQueryError when the decoded JSON isn’t a list, but callers like _snapshot_row_to_org_fact() only catch KeyError/ValueError/ValidationError. A malformed-but-valid tags payload (e.g. {}) would bypass the row-parse warning log/wrapping path. Either raise ValueError from _tags_from_json() (and let callers wrap/log), or have callers handle OrgMemoryQueryError as well for consistent error reporting.

Suggested change
OrgMemoryQueryError: If the JSON is not a list.
"""
parsed = json.loads(raw)
if not isinstance(parsed, list):
msg = f"Tags must be a JSON array, got {type(parsed).__name__}"
raise OrgMemoryQueryError(msg)
ValueError: If the JSON is not a list.
"""
parsed = json.loads(raw)
if not isinstance(parsed, list):
msg = f"Tags must be a JSON array, got {type(parsed).__name__}"
raise ValueError(msg)

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 7, 2026

Codecov Report

❌ Patch coverage is 91.47982% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.98%. Comparing base (af11f0a) to head (0ebd038).
⚠️ Report is 7 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/synthorg/memory/org/sqlite_store.py 88.75% 16 Missing and 3 partials ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #1134    +/-   ##
========================================
  Coverage   88.98%   88.98%            
========================================
  Files         851      852     +1     
  Lines       49633    49733   +100     
  Branches     4977     4983     +6     
========================================
+ Hits        44164    44255    +91     
- Misses       4533     4541     +8     
- Partials      936      937     +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Aureliolo
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f4bb58a1-6b96-41c3-b3bc-87160ccf23bd

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebd038 and f1c7733.

📒 Files selected for processing (14)
  • CLAUDE.md
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py

Walkthrough

The PR implements an append-only MVCC design for organizational facts: adds Pydantic models OperationLogEntry and OperationLogSnapshot; extends OrgFact/OrgFactWriteRequest with tags; adds autonomy_level to OrgFactAuthor with validation. OrgFactStore becomes a protocol with save, delete(fact_id, *, author=...), snapshot_at(timestamp), and get_operation_log(fact_id) semantics and active-only reads. A new SQLiteOrgFactStore provides an MVCC SQLite implementation, observability event constants were added, and numerous tests and fixtures were introduced or updated.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.90% which is insufficient. The required threshold is 40.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the primary change: implementation of append-only and MVCC consistency model for organizational memory storage as Phase 1.5.
Description check ✅ Passed Description is well-related to the changeset, detailing schema changes, write/read paths, MVCC semantics, models, observability, and test coverage aligned with the PR modifications.
Linked Issues check ✅ Passed PR implements all core objectives from #1130: append-only operation log, materialized snapshot table, time-travel APIs (snapshot_at, get_operation_log), metadata persistence (autonomy_level, tags), transactional safety, and 141 passing tests.
Out of Scope Changes check ✅ Passed All changes are scoped to #1130 Phase 1.5 implementation: MVCC schema/internals, OrgMemoryBackend wiring, models, observability events, documentation, and comprehensive test coverage; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/memory/org/store.py (1)

612-655: ⚠️ Potential issue | 🔴 Critical

Rollback the transaction before re-raising write errors.

After BEGIN IMMEDIATE, both write paths log and raise without a rollback. A failure after the transaction starts leaves the connection inside that transaction, so the next write can hit cannot start a transaction within a transaction or commit work that should have been discarded.

Also applies to: 677-717

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/store.py` around lines 612 - 655, The exception
handler in the transaction block around _append_to_operation_log and the INSERT
into org_facts_snapshot must explicitly roll back the DB transaction before
re-raising; update the except sqlite3.Error as exc block (and the analogous
handler in the other block at lines ~677-717) to call await
db.execute("ROLLBACK") (or await db.rollback() if the DB wrapper exposes it)
prior to logging and raising OrgMemoryWriteError so the connection is not left
inside an open transaction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/memory-consistency.md`:
- Around line 108-110: Update this design page to reflect that MVCC and the
associated methods now live on the OrgFactStore protocol (not
SharedKnowledgeStore): replace or reframe mentions of SharedKnowledgeStore to
OrgFactStore, move descriptions and examples of publish/retract/search_shared to
the OrgFactStore section, remove or reconcile any leftover feature-flag
language, and update the implementation note to reference SQLiteOrgFactStore as
the MVCC owner; also add a short “Deviation note” explaining why the
implementation diverged from the original SharedKnowledgeStore plan so future
work targets the correct interface (OrgFactStore and SQLiteOrgFactStore) and
update any APIs or examples that still use publish/retract/search_shared to
point to the OrgFactStore equivalents.

In `@src/synthorg/memory/org/models.py`:
- Around line 206-230: The MVCC models OperationLogEntry and
OperationLogSnapshot are missing fact metadata (category, author_is_human,
author_seniority, and the fact's original created_at) which prevents
reconstructing true point-in-time state; update the Pydantic models
OperationLogEntry and OperationLogSnapshot to add fields for category: Category
| None, author_is_human: bool | None, author_seniority: Seniority | None, and
created_at: AwareDatetime | None (matching the fact model types), then update
every place that constructs these entries (e.g., the save()/append logic that
emits OperationLogEntry and any code that builds OperationLogSnapshot) to
populate these new fields from the fact at the time of the operation so the
append-only log contains a full historical picture and snapshot_at() can rebuild
exact historical facts.

In `@src/synthorg/memory/org/store.py`:
- Around line 680-702: The retract path in delete() incorrectly copies author
metadata from org_facts_snapshot so the RETRACT operation log entry is
attributed to the previous publisher; update the call to
_append_to_operation_log in delete() so it does not clone
row["author_agent_id"]/row["author_autonomy_level"] — instead pass the actual
actor context provided to delete() (e.g., the incoming
author_agent_id/author_autonomy_level parameters or self.current actor) or
explicitly pass None if no actor is available, and remove the code that reads
author_agent_id/author_autonomy_level from the snapshot row; keep
_append_to_operation_log, org_facts_snapshot, and delete() identifiers to locate
the change.
- Around line 286-302: _tags_from_json currently swallows malformed tag entries
by filtering out non-strings and blank strings; change it to validate the parsed
list strictly and raise OrgMemoryQueryError if any element is not a non-blank
string so corrupted rows surface as errors. Specifically, in _tags_from_json
after json.loads and the list check, iterate the list and if any item is not a
str or if str(item).strip() is empty, raise OrgMemoryQueryError with a
descriptive message; only if all elements pass, return tuple(NotBlankStr(t) for
t in parsed). Ensure you reference _tags_from_json, OrgMemoryQueryError, and
NotBlankStr when locating and updating the function.
- Around line 624-645: The current INSERT OR REPLACE resets created_at on
republish; change the upsert to an INSERT ... ON CONFLICT(fact_id) DO UPDATE
that updates all mutable columns but leaves created_at untouched (i.e., don't
overwrite existing created_at). In the upsert for org_facts_snapshot (where the
diff shows the INSERT OR REPLACE and VALUES using fact.created_at), switch to
using INSERT ... VALUES (...) ON CONFLICT(fact_id) DO UPDATE SET
content=excluded.content, category=excluded.category, tags=excluded.tags,
author_agent_id=excluded.author_agent_id,
author_seniority=excluded.author_seniority,
author_is_human=excluded.author_is_human,
author_autonomy_level=excluded.author_autonomy_level, retracted_at=NULL,
version=excluded.version and do not set created_at in the DO UPDATE clause (or
use created_at=COALESCE(org_facts_snapshot.created_at, excluded.created_at) if
you must include it); update the code around the save/ upsert logic in store.py
where this INSERT OR REPLACE is implemented (the block inserting into
org_facts_snapshot).

In `@tests/unit/memory/org/test_mvcc_store.py`:
- Around line 396-423: The test test_both_operations_in_log currently uses a
single SQLiteOrgFactStore with ":memory:" and awaits two save() calls
sequentially so they never run concurrently; change it to create two
SQLiteOrgFactStore instances (e.g., store_a and store_b) pointed at the same
on-disk temporary DB path (not ":memory:"), connect both (call connect() on
each), then publish the two facts concurrently using
asyncio.gather(store_a.save(...), store_b.save(...)), then retrieve the
operation log via get_operation_log("shared-fact") and the snapshot via
get("shared-fact") to assert ordering and last-writer-wins; ensure both stores
are closed/cleaned up after the test.
- Around line 312-323: The test test_snapshot_between_publish_and_retract is
non-deterministic because it relies on asyncio.sleep and datetime.now; modify
the test to drive timestamps explicitly by either injecting a mock clock into
SQLiteOrgFactStore or by calling save/delete/snapshot_at with explicit
timestamps instead of relying on real time; specifically, update the test to
create a deterministic monotonic/time source (or patch time.monotonic and
asyncio.sleep) and pass those controlled times to SQLiteOrgFactStore (or its
methods save/delete/snapshot_at) so the mid_ts is deterministic and the
snapshot_at assertion no longer depends on scheduler timing.

In `@tests/unit/memory/org/test_store.py`:
- Around line 376-378: The test currently converts retrieved.tags to a set which
masks ordering/duplication issues; instead assert the ordered tuple directly by
replacing the set assertion with a direct tuple equality check (e.g., assert
retrieved.tags == ("core-policy", "security")) for the OrgFact.tags round-trip,
and make the same change in the new backend tags test so both verify exact tuple
equality rather than a set.

---

Outside diff comments:
In `@src/synthorg/memory/org/store.py`:
- Around line 612-655: The exception handler in the transaction block around
_append_to_operation_log and the INSERT into org_facts_snapshot must explicitly
roll back the DB transaction before re-raising; update the except sqlite3.Error
as exc block (and the analogous handler in the other block at lines ~677-717) to
call await db.execute("ROLLBACK") (or await db.rollback() if the DB wrapper
exposes it) prior to logging and raising OrgMemoryWriteError so the connection
is not left inside an open transaction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2300da23-ab74-4caa-bb8e-faf52f8cbf47

📥 Commits

Reviewing files that changed from the base of the PR and between bd0929e and 2a45fcf.

📒 Files selected for processing (11)
  • CLAUDE.md
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations in Python code -- Python 3.14 has PEP 649 native lazy annotations

Use except A, B: syntax (no parentheses) for PEP 758 exception handling in Python 3.14

All public functions and classes must have type hints; mypy strict mode is enforced

Use Google-style docstrings on all public classes and functions (enforced by ruff D rules)

Create new objects for immutability; never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.

Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). In ConfigDict, use allow_inf_nan=False to reject NaN/Inf in numeric fields. Use @computed_field for derived values. Use NotBlankStr for all identifier/name fields instead of manual whitespace validators.

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code over bare create_task. Prefer structured concurrency.

Line length must be 88 characters (enforced by ruff)

Functions must be less than 50 lines; files less than 800 lines

Handle errors explicitly; never silently swallow exceptions in Python code

Validate at system boundaries (user input, external APIs, config files) in Python code

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic must import logging via from synthorg.observability import get_logger then logger = get_logger(name). Never use import logging / logging.getLogger() / print() in application code.

Always use variable name 'logger' (not '_logger', not 'log') for logging instances in Python

Use event name constants from synthorg.observability.events domain-specific modules (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly: from synthorg.observability.events. import EVENT_CONSTANT

Always use structured kwargs in logging: logger.info(EVENT, key=value) -- never logger.info('msg %s', val)

All error paths must log at WARNING or ERROR with context before raising in Python code

All state transitions must log at INFO level in Python code

Use DEBUG level for object creation, internal flow, entry/exit of key functions in Python code

All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.

RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig in Python code

Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry.

RetryExhaustedError signals that all retries failed -- the engine layer catches this to trigger fallback chains

Rate limiter respects RateLimitError.retry_after from providers -- automatically pauses future requests

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py
{src/**/*.py,tests/**/*.py,**/*.md,**/*.yaml,**/*.yml,**/*.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small. Vendor names only appear in: (1) Operations design page, (2) .claude/ files, (3) third-party imports, (4) provider presets in src/synthorg/providers/presets.py. Tests use test-provider, test-small-001.

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • CLAUDE.md
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/observability/events/org_memory.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow for test categorization

Maintain 80% minimum test coverage (enforced in CI) for Python tests

Use asyncio_mode = 'auto' in pytest configuration -- no manual @pytest.mark.asyncio needed for async tests

Tests have 30 seconds timeout per test (global in pyproject.toml). Non-default overrides like timeout(60) are allowed but timeout(30) markers are not needed.

Prefer @pytest.mark.parametrize for testing similar cases in Python tests

Use Hypothesis for property-based testing in Python with @given + @settings. Profiles: ci (10 examples, derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Controlled via HYPOTHESIS_PROFILE env var.

For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
🧠 Learnings (38)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 12 design pages.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: If implementation deviates from the design spec, alert the user and explain why. Do not silently diverge from the spec -- every deviation needs explicit user approval.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design. Surface improvements as suggestions, not silent changes -- user decides.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Always include -n 8 when running pytest locally, never run tests sequentially. CI uses -n auto.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: When Hypothesis finds a failure, fix the underlying bug and add an explicit example(...) decorator to the test so the case is permanently covered in CI. Never skip, dismiss, or ignore flaky tests.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Commit messages follow <type>: <description> format -- types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Signed commits are required on main via branch protection -- all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Use branch naming convention <type>/<slug> from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting), golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings). Pre-push hooks: mypy type-check + pytest unit tests (affected modules only) + golangci-lint + go vet + go test (CLI) + eslint-web (web dashboard).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Autoupdate is disabled in pre-commit.ci (autoupdate_schedule: never) -- Dependabot owns hook version bumps via pre-commit ecosystem
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Use gh issue list via Bash (not MCP tools) for GitHub issue queries -- MCP list_issues has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Use squash merge strategy for PRs -- PR body becomes the squash commit message on main. Trailers (e.g. Release-As, Closes `#N`) must be in the PR body to land in the final commit.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Preserve existing Closes `#NNN` references in PR bodies -- never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: After finishing an issue implementation, create a feature branch (<type>/<slug>), commit, and push -- do NOT create a PR automatically. Do NOT leave work uncommitted on main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Never use gh pr create directly -- it is blocked by hookify. Always use /pre-pr-review to create PRs. For trivial/docs-only changes: /pre-pr-review quick skips agents but still runs automated checks.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: After PR exists, use /aurelio-review-pr to handle external reviewer feedback. The /commit-push-pr command is effectively blocked.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: Fix everything valid from review agents -- never skip. When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to PR changes), fix them all. No deferring, no 'out of scope' skipping.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T05:29:08.143Z
Learning: PostToolUse hook (scripts/check_web_design_system.py) enforces web design system rules on every Edit/Write to web/src/
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-07T22:12:30.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.142Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)`—never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-04-07T22:12:30.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.142Z
Learning: Applies to src/synthorg/**/*.py : Pure data models, enums, and re-exports do NOT need logging

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-07T22:12:30.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.142Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`, etc.); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-07T22:12:30.141Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.141Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state that evolves; never mix static config fields with mutable runtime fields in one model

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-07T22:12:30.141Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.141Z
Learning: Applies to src/synthorg/**/*.py : For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/store.py
🔇 Additional comments (1)
src/synthorg/memory/org/hybrid_backend.py (1)

224-230: Tag metadata now survives the backend write path.

Passing request.tags into the persisted OrgFact closes the metadata-loss gap between OrgFactWriteRequest and storage.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/synthorg/memory/org/store.py (1)

617-637: ⚠️ Potential issue | 🟠 Major

snapshot_at() still reconstructs a projection, not the persisted fact state.

save() preserves fact.created_at in org_facts_snapshot, but the operation log never records it and the CTE rebuilds created_at from MIN(timestamp) instead. Imported or backfilled facts can therefore return one created_at from live reads and another from snapshot_at(). The same query also drops category and author fields, so the historical snapshot still cannot reproduce an OrgFact-equivalent state. Persist the original created_at in the log and carry forward the latest PUBLISH metadata in OperationLogSnapshot.

Also applies to: 669-700, 958-989

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/store.py` around lines 617 - 637, snapshot_at()
currently rebuilds fact.created_at from MIN(timestamp) because save() stores
created_at only in org_facts_snapshot while org_facts_operation_log insert calls
(used by OperationLogSnapshot) do not persist created_at or PUBLISH metadata;
update the INSERT into org_facts_operation_log to include created_at plus
author/category/publish-related fields (e.g., author_agent_id, author_seniority,
author_is_human, author_autonomy_level, category, and any PUBLISH-specific
metadata) and then update OperationLogSnapshot’s CTE/reconstruction logic to
carry forward the original created_at and the latest PUBLISH metadata (use the
operation_type == PUBLISH marker to pick the most recent publish fields) so
snapshot_at() reproduces an OrgFact-equivalent state rather than a projection.
tests/unit/memory/org/test_mvcc_store.py (2)

312-323: ⚠️ Potential issue | 🟡 Minor

Make the publish/retract boundary test deterministic.

asyncio.sleep(0.01) plus datetime.now(UTC) still leaves the midpoint dependent on scheduler timing. Patch the clock used by SQLiteOrgFactStore, or inject explicit timestamps, instead of sleeping.

As per coding guidelines, For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism instead of widening timing margins.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/memory/org/test_mvcc_store.py` around lines 312 - 323,
test_snapshot_between_publish_and_retract is flaky due to using asyncio.sleep +
datetime.now for the midpoint; make it deterministic by removing sleeps and
either (a) constructing facts with explicit timestamps and passing those to
SQLiteOrgFactStore.save/delete and then calling snapshot_at(mid_ts) with a known
mid_ts, or (b) patching/injecting the store's clock (e.g., monkeypatch a clock
attribute or time provider used by SQLiteOrgFactStore) so you can advance time
deterministically and call save/delete/snapshot_at without real sleeps; update
the test to use the chosen approach and reference the test name plus the store
methods save, delete and snapshot_at.

396-423: ⚠️ Potential issue | 🟠 Major

This still never exercises concurrent writers.

Both publishes are awaited sequentially on one :memory: connection, so the BEGIN IMMEDIATE / version-allocation path never contends. Also, :memory: is per-connection, so even a second store here would not share state. Use two stores against the same temporary on-disk DB and run the saves with asyncio.gather(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/memory/org/test_mvcc_store.py` around lines 396 - 423, The
test_both_operations_in_log currently uses a single
SQLiteOrgFactStore(":memory:") and awaits saves sequentially, so it never
triggers concurrent-writer contention; change it to create two
SQLiteOrgFactStore instances pointed at the same temporary on-disk filename (not
":memory:"), call connect() on both, then run the two save(...) calls
concurrently using asyncio.gather(...) (e.g., save on store_a and store_b with
facts created by _make_fact and authors agent_a/agent_b), then fetch the
operation log (get_operation_log) and snapshot (get) from one store and assert
results; ensure both stores are closed/cleaned up after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Line 93: Move the ORG_MEMORY MVCC constants so they sit with the rest of the
ORG_MEMORY constants instead of being appended at the end; locate the block
containing the ORG_MEMORY_* names (e.g., ORG_MEMORY_QUERY_START,
ORG_MEMORY_WRITE_COMPLETE, ORG_MEMORY_NOT_CONNECTED, etc.) and reposition the
MVCC constants (ORG_MEMORY_MVCC_PUBLISH_APPENDED,
ORG_MEMORY_MVCC_RETRACT_APPENDED, ORG_MEMORY_MVCC_SNAPSHOT_AT_QUERIED,
ORG_MEMORY_MVCC_LOG_QUERIED) immediately with that group (optionally alphabetize
or keep logical grouping) to keep all ORG_MEMORY constants together for
maintainability.

In `@src/synthorg/memory/org/store.py`:
- Around line 930-952: snapshot_at currently serializes the input timestamp with
timestamp.isoformat() which can produce non-UTC or naive ISO strings that don't
match the UTC ISO strings in the log; normalize the input to UTC first: in
snapshot_at, detect if timestamp.tzinfo is None and treat it explicitly (e.g.
attach UTC), otherwise convert with timestamp.astimezone(datetime.timezone.utc),
then serialize that UTC-aware datetime to produce query_ts used in the CTE;
update any imports if needed and keep the normalized query_ts variable name.

---

Duplicate comments:
In `@src/synthorg/memory/org/store.py`:
- Around line 617-637: snapshot_at() currently rebuilds fact.created_at from
MIN(timestamp) because save() stores created_at only in org_facts_snapshot while
org_facts_operation_log insert calls (used by OperationLogSnapshot) do not
persist created_at or PUBLISH metadata; update the INSERT into
org_facts_operation_log to include created_at plus
author/category/publish-related fields (e.g., author_agent_id, author_seniority,
author_is_human, author_autonomy_level, category, and any PUBLISH-specific
metadata) and then update OperationLogSnapshot’s CTE/reconstruction logic to
carry forward the original created_at and the latest PUBLISH metadata (use the
operation_type == PUBLISH marker to pick the most recent publish fields) so
snapshot_at() reproduces an OrgFact-equivalent state rather than a projection.

In `@tests/unit/memory/org/test_mvcc_store.py`:
- Around line 312-323: test_snapshot_between_publish_and_retract is flaky due to
using asyncio.sleep + datetime.now for the midpoint; make it deterministic by
removing sleeps and either (a) constructing facts with explicit timestamps and
passing those to SQLiteOrgFactStore.save/delete and then calling
snapshot_at(mid_ts) with a known mid_ts, or (b) patching/injecting the store's
clock (e.g., monkeypatch a clock attribute or time provider used by
SQLiteOrgFactStore) so you can advance time deterministically and call
save/delete/snapshot_at without real sleeps; update the test to use the chosen
approach and reference the test name plus the store methods save, delete and
snapshot_at.
- Around line 396-423: The test_both_operations_in_log currently uses a single
SQLiteOrgFactStore(":memory:") and awaits saves sequentially, so it never
triggers concurrent-writer contention; change it to create two
SQLiteOrgFactStore instances pointed at the same temporary on-disk filename (not
":memory:"), call connect() on both, then run the two save(...) calls
concurrently using asyncio.gather(...) (e.g., save on store_a and store_b with
facts created by _make_fact and authors agent_a/agent_b), then fetch the
operation log (get_operation_log) and snapshot (get) from one store and assert
results; ensure both stores are closed/cleaned up after the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c078f726-1258-4331-a548-5ec96a12136c

📥 Commits

Reviewing files that changed from the base of the PR and between 2a45fcf and 5fe3c69.

📒 Files selected for processing (7)
  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/store.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (7)
**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Always read the relevant docs/design/ page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to design pages. The design spec is the starting point for architecture, data models, and behavior.

Files:

  • CLAUDE.md
**/*.{py,pyi,ts,tsx,go,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Commits use format <type>: <description>. Types: feat, fix, refactor, docs, test, chore, perf, ci. Enforced by commitizen (commit-msg hook).

All commits on main must be GPG/SSH signed. Enforced by branch protection.

Use branch naming: <type>/<slug> from main.

Files:

  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Python 3.14+ with PEP 649 native lazy annotations. Do not use from __future__ import annotations.

Use PEP 758 except syntax: except A, B: (no parentheses) on Python 3.14. Ruff enforces this.

Include type hints on all public functions. Enable mypy strict mode for type checking.

Use Google-style docstrings on all public classes and functions. Required and enforced by ruff D rules.

Create new objects instead of mutating existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction and wrap with MappingProxyType for read-only enforcement. For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and use copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization).

Use frozen Pydantic models for config/identity. Use separate mutable-via-copy models (via model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Use Pydantic v2 with BaseModel, model_validator, computed_field, and ConfigDict. Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.

Use @computed_field for derived values instead of storing and validating redundant fields (e.g. TokenUsage.total_tokens).

Use NotBlankStr from core.types for all identifier/name fields, including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants, instead of manual whitespace validators.

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task. Existing code is being migrated incrementally.

Keep functions under 50 lines and files under 800 lines.

Handle errors explicitly, never silently swallow them.

Validate at system boundaries (u...

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
src/**/*.{py,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__). Never use import logging, logging.getLogger(), or print() in application code (exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, and observability/http_handler.py may use stdlib logging and print(..., file=sys.stderr)).

Always use logger as the variable name (not _logger, not log).

Use constants from domain-specific modules under synthorg.observability.events for event names. Import directly from the domain module (e.g. from synthorg.observability.events.api import API_REQUEST_STARTED). Each domain has its own module with constants like API_REQUEST_STARTED, TOOL_INVOKE_START, GIT_COMMAND_START, CONTEXT_BUDGET_FILL_UPDATED, BACKUP_STARTED, SETUP_COMPLETED, ROUTING_CANDIDATE_SELECTED, SHIPPING_HTTP_BATCH_SENT, EVAL_REPORT_COMPUTED, PROMPT_PROFILE_SELECTED, PROCEDURAL_MEMORY_START, PERF_LLM_JUDGE_STARTED, TASK_ENGINE_OBSERVER_FAILED, WORKFLOW_EXEC_COMPLETED, BLUEPRINT_INSTANTIATE_START, WORKFLOW_DEF_ROLLED_BACK, WORKFLOW_VERSION_SAVED, and memory/reporting/risk/consolidation/notification/quality/health/trajectory/coordination/web/database/terminal/sub_constraint/versioning/analytics/call_classification/quota/org_memory domain events.

Use structured logging with kwargs: always logger.info(EVENT, key=value) -- never logger.info("msg %s", val).

Log all error paths at WARNING or ERROR with context before raising.

Log all state transitions at INFO level.

Use DEBUG log level for object creation, internal flow, and entry/exit of key functions.

All provider calls go through BaseCompletionProvider which applies retry and rate limiting automatically. Never implement retry logic in driver subclasses or calling code.

Set RetryConfig and RateLimiterConfig per-provider in ProviderConfig.

Retryable errors (is_retryable=True) includ...

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
tests/**/*.{py,pyi}

📄 CodeRabbit inference engine (CLAUDE.md)

Use markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, and @pytest.mark.slow.

Maintain 80% test coverage minimum, enforced in CI.

Use asyncio_mode = "auto" in pytest config. No manual @pytest.mark.asyncio needed.

Default test timeout is 30 seconds per test (global in pyproject.toml). Do not add per-file pytest.mark.timeout(30) markers. Non-default overrides like timeout(60) are allowed.

Always include -n 8 when running pytest locally for parallel execution with pytest-xdist. Never run tests sequentially. CI uses -n auto.

Prefer @pytest.mark.parametrize for testing similar cases.

Use Hypothesis for property-based testing with @given and @settings decorators. Hypothesis profiles: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Control via HYPOTHESIS_PROFILE env var. .hypothesis/ is gitignored. Failing examples persist to ~/.synthorg/hypothesis-examples/ via _WriteOnlyDatabase.

When Hypothesis finds a failure, it is a real bug. Read the shrunk example, fix the underlying bug, and add an explicit @example(...) decorator to permanently cover the case in CI. Do NOT just rerun and move on.

Never skip, dismiss, or ignore flaky tests. Always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() for determinism instead of widening timing margins. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).

Files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
tests/**/*.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
🧠 Learnings (32)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts. At every phase of planning and implementation, be critical and actively look for ways to improve the design. Surface improvements as suggestions, not silent changes -- user decides.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Prioritize issues by dependency order, not priority labels. Unblocked dependencies come first.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Pure data models, enums, and re-exports do NOT need logging.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: `example-provider`, `example-large-001`, `example-medium-001`, `example-small-001`, `large`/`medium`/`small`. Vendor names may only appear in: (1) Operations design page provider list (`docs/design/operations.md`), (2) `.claude/` skill/agent files, (3) third-party import paths/module names, (4) provider presets (`src/synthorg/providers/presets.py`). Tests must use `test-provider`, `test-small-001`, etc.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile), golangci-lint + go vet (CLI, conditional), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, conditional).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Hookify rules committed in `.claude/hookify.*.md`: `block-pr-create` (blocks `gh pr create`, must use `/pre-pr-review`), `enforce-parallel-tests` (enforces `-n 8` with pytest), `no-cd-prefix` (blocks `cd` prefix in Bash commands), `no-local-coverage` (blocks `--cov` flags locally, CI handles coverage).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Pre-push hooks run: mypy type-check (affected modules only) + pytest unit tests (affected modules only) + golangci-lint + go vet + go test (CLI, conditional) + eslint-web (web dashboard, conditional). Fast gate before push, skipped in pre-commit.ci. Foundational module changes (core, config, observability) or conftest changes trigger full runs.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Pre-commit.ci has autoupdate disabled (`autoupdate_schedule: never`). Dependabot owns hook version bumps via the `pre-commit` ecosystem.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Use `gh issue list` via Bash when querying GitHub issues (not MCP tools) -- MCP `list_issues` has unreliable field data.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Use squash merge for PRs. PR body becomes the squash commit message on main. Trailers (e.g. `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Preserve existing `Closes `#NNN`` issue references in PR bodies. Never remove unless explicitly asked.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push. Do NOT create a PR automatically. Do NOT leave work uncommitted on main.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: Never create a PR directly with `gh pr create` (blocked by hookify). Always use `/pre-pr-review` to create PRs. It runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes, use `/pre-pr-review quick` to skip agents but still run checks. After PR exists, use `/aurelio-review-pr` for external reviewer feedback. The `/commit-push-pr` command is effectively blocked (calls `gh pr create` internally).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and adjacent findings), fix them all. No deferring, no 'out of scope' skipping.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T06:39:12.742Z
Learning: If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why. User decides whether to proceed or update the spec. Do NOT silently diverge. Every deviation needs explicit user approval. When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-07T22:12:30.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.142Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)`—never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-07T22:12:30.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.142Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins; for tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-07T22:12:30.141Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.141Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state that evolves; never mix static config fields with mutable runtime fields in one model

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-07T22:12:30.141Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T22:12:30.141Z
Learning: Applies to src/synthorg/**/*.py : For `dict`/`list` fields in frozen Pydantic models, rely on `frozen=True` for field reassignment prevention and `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)

Applied to files:

  • src/synthorg/memory/org/models.py
🔇 Additional comments (3)
CLAUDE.md (1)

93-93: The MVCC event constants documented on line 93 are correctly defined in src/synthorg/observability/events/org_memory.py and are already in use throughout the codebase (e.g., in src/synthorg/memory/org/store.py). The documentation is accurate.

src/synthorg/memory/org/hybrid_backend.py (1)

216-223: Good: tags now flow into persisted facts.

This closes the write-path gap between OrgFactWriteRequest and OrgFactStore.save().

tests/unit/memory/org/test_store.py (1)

362-378: Nice exact round-trip assertion for tags.

Direct tuple equality keeps ordering and duplication regressions visible instead of masking them behind set semantics.

Comment on lines +930 to +952
async def snapshot_at(
self,
timestamp: datetime,
) -> tuple[OperationLogSnapshot, ...]:
"""Point-in-time snapshot of all facts at a given timestamp.

Reconstructs fact state from the operation log. Active facts
have ``retracted_at=None``; retracted facts carry the retract
timestamp.

Args:
timestamp: UTC timestamp for the snapshot.

Returns:
Snapshot entries as they existed at the given time.

Raises:
OrgMemoryConnectionError: If not connected.
OrgMemoryQueryError: If the query fails.
"""
db = self._require_connected()
query_ts = timestamp.isoformat()
# Time-travel CTE: reconstruct fact state at a timestamp.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize snapshot_at() inputs to UTC before comparing ISO strings.

The log stores UTC ISO strings, but this query uses whatever timestamp.isoformat() returns. An aware non-UTC input represents the same instant with different text, so the lexical <= filter can exclude rows that should match. Convert to UTC before serializing, and handle naive datetimes explicitly.

🛠️ Suggested normalization
-        query_ts = timestamp.isoformat()
+        normalized_timestamp = (
+            timestamp.astimezone(UTC)
+            if timestamp.tzinfo is not None
+            else timestamp.replace(tzinfo=UTC)
+        )
+        query_ts = normalized_timestamp.isoformat()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/store.py` around lines 930 - 952, snapshot_at
currently serializes the input timestamp with timestamp.isoformat() which can
produce non-UTC or naive ISO strings that don't match the UTC ISO strings in the
log; normalize the input to UTC first: in snapshot_at, detect if
timestamp.tzinfo is None and treat it explicitly (e.g. attach UTC), otherwise
convert with timestamp.astimezone(datetime.timezone.utc), then serialize that
UTC-aware datetime to produce query_ts used in the CTE; update any imports if
needed and keep the normalized query_ts variable name.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/synthorg/memory/org/hybrid_backend.py (1)

167-178: ⚠️ Potential issue | 🟠 Major

Don’t leak raw OrgFactStore exceptions from the backend API.

query() and write() now only handle already-normalized domain errors. Any other store-side failure escapes as a raw backend-specific exception and skips the backend’s failure path, which makes caller behavior depend on the concrete OrgFactStore again instead of the OrgMemoryQueryError / OrgMemoryWriteError contract exposed here.

As per coding guidelines, "All error paths must log at WARNING or ERROR with context before raising," and the retrieved learnings note that memory/ uses pluggable backends.

Also applies to: 225-238

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/hybrid_backend.py` around lines 167 - 178, The
backend currently only catches OrgMemoryQueryError from self._store.query and
lets other backend-specific exceptions leak; wrap any unexpected exceptions from
self._store.query and self._store.write (the calls in hybrid_backend.py) by
catching Exception, logging an ERROR/WARNING with context (include the original
exception info) and re-raising a normalized OrgMemoryQueryError or
OrgMemoryWriteError respectively so callers see the defined contract; ensure the
logger.exception / logger.error call includes descriptive context (e.g., which
query/write and categories/limit) and chain the original exception when
constructing the OrgMemory*Error before raising.
♻️ Duplicate comments (2)
src/synthorg/memory/org/models.py (1)

186-245: ⚠️ Potential issue | 🟠 Major

The time-travel models still don't capture a full fact version.

OperationLogEntry still can't represent the stored OrgFact.created_at, and OperationLogSnapshot still can't represent category or author metadata. That means snapshot_at() has to synthesize part of the state from operation timestamps and can never return the same fact shape that get() exposes for the same historical version.

Also applies to: 272-303

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/models.py` around lines 186 - 245, OperationLogEntry
and OperationLogSnapshot are missing full fact-version fields so snapshot_at()
cannot reconstruct the original OrgFact shape; add OrgFact.created_at to
OperationLogEntry (and the corresponding field to OperationLogSnapshot), and
include category plus all author metadata fields (author_agent_id,
author_seniority, author_is_human, author_autonomy_level) on both
OperationLogEntry and OperationLogSnapshot so snapshot_at() can return the same
structure as get() for a historical version; update snapshot_at() to read these
new fields instead of synthesizing them from timestamps.
docs/design/memory-consistency.md (1)

109-110: ⚠️ Potential issue | 🟠 Major

Finish renaming the public write APIs in this spec.

These lines move MVCC to OrgFactStore, but the write-path section above still documents publish() / retract(). As written, the page points readers at both the old and new interfaces. Rename those to save() / delete(..., author=...) or add an explicit mapping here.

Based on learnings: "If implementation deviates from the spec, alert the user and explain why" and "Update the relevant docs/design/ page when approved deviations occur to reflect the new reality".

Also applies to: 137-140

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/design/memory-consistency.md` around lines 109 - 110, The docs currently
reference both the old write API (publish(), retract()) and the new MVCC-enabled
OrgFactStore APIs; update the spec text to finish the rename by replacing
references to publish() and retract() with the new public methods on
OrgFactStore (save() and delete(..., author=...)) or add an explicit mapping
sentence that shows publish() → save() and retract() → delete(..., author=...)
and mention SQLiteOrgFactStore as the MVCC implementation; ensure the write-path
section and the other occurrences (around lines 137–140) are made consistent so
readers aren’t pointed to both old and new interfaces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 584-607: The RETRACT path in delete() is currently writing tags=()
to _append_to_operation_log, discarding the active snapshot's tags and breaking
the audit trail; when selecting from org_facts_snapshot (the row variable) read
its tags column (e.g., row["tags"]) and pass those tags through to
_append_to_operation_log instead of an empty tuple so the operation log entry
preserves the snapshot metadata used at retraction time (ensure any stored
format is converted to the expected tags type before calling
_append_to_operation_log and keep other parameters like fact_id,
operation_type="RETRACT", category, and author_* unchanged).
- Around line 506-538: Normalize fact.created_at to UTC before converting to
text: replace the created_at argument passed to the INSERT/UPSERT with a
UTC-normalized ISO string by ensuring fact.created_at is timezone-aware and
converted to UTC (e.g., use
fact.created_at.astimezone(datetime.timezone.utc).isoformat(), or if naive
attach UTC then isoformat). Update the created_at expression used in the
org_facts_snapshot INSERT (the tuple element where fact.created_at.isoformat()
is used) so all persisted timestamps are in UTC for correct lexical ordering.

In `@tests/unit/memory/org/test_models.py`:
- Around line 108-119: Tests only cover valid tag tuples; add parametrized tests
that assert ValidationError for tags=("",) and tags=("   ",) for both the
OrgFact construction and OrgFactWriteRequest to prevent regressions to plain
tuple[str, ...]. Update or add parameterized test functions around
test_fact_with_tags to include these invalid cases and ensure they raise
pydantic.ValidationError; reference the OrgFact and OrgFactWriteRequest models
when creating instances in the tests. Also ensure the models use NotBlankStr
from core.types for identifier/name/tag element types (including tuple variants)
instead of manual whitespace validators so the validation is enforced at the
model level.

In `@tests/unit/memory/org/test_mvcc_store.py`:
- Around line 369-375: The test currently only checks both writes were logged
and a snapshot row exists; update it to assert the snapshot equals the winning
(highest-version) log entry by: after fetching log via
store_a.get_operation_log("shared-fact") and fact via
store_a.get("shared-fact"), locate the log entry with the maximum version (or
highest sequence/timestamp field used as the version) from the `log` sequence
and assert that `fact` matches that entry (compare the same fields used for
equality in your model, e.g., value/content and version), ensuring the snapshot
reflects the last-writer-wins entry recorded in the log.

In `@tests/unit/memory/org/test_store.py`:
- Around line 146-163: The test test_save_with_agent_author is missing an
assertion for the author's autonomy level; update the test to assert that the
round-tripped fact preserves AGENT_AUTHOR.autonomy_level by checking
retrieved.author.autonomy_level == AutonomyLevel.SEMI after calling
connected_store.save(fact) and connected_store.get("f1"), ensuring the
serialization/deserialization in OrgFact (and related store methods) retains
author.autonomy_level.

---

Outside diff comments:
In `@src/synthorg/memory/org/hybrid_backend.py`:
- Around line 167-178: The backend currently only catches OrgMemoryQueryError
from self._store.query and lets other backend-specific exceptions leak; wrap any
unexpected exceptions from self._store.query and self._store.write (the calls in
hybrid_backend.py) by catching Exception, logging an ERROR/WARNING with context
(include the original exception info) and re-raising a normalized
OrgMemoryQueryError or OrgMemoryWriteError respectively so callers see the
defined contract; ensure the logger.exception / logger.error call includes
descriptive context (e.g., which query/write and categories/limit) and chain the
original exception when constructing the OrgMemory*Error before raising.

---

Duplicate comments:
In `@docs/design/memory-consistency.md`:
- Around line 109-110: The docs currently reference both the old write API
(publish(), retract()) and the new MVCC-enabled OrgFactStore APIs; update the
spec text to finish the rename by replacing references to publish() and
retract() with the new public methods on OrgFactStore (save() and delete(...,
author=...)) or add an explicit mapping sentence that shows publish() → save()
and retract() → delete(..., author=...) and mention SQLiteOrgFactStore as the
MVCC implementation; ensure the write-path section and the other occurrences
(around lines 137–140) are made consistent so readers aren’t pointed to both old
and new interfaces.

In `@src/synthorg/memory/org/models.py`:
- Around line 186-245: OperationLogEntry and OperationLogSnapshot are missing
full fact-version fields so snapshot_at() cannot reconstruct the original
OrgFact shape; add OrgFact.created_at to OperationLogEntry (and the
corresponding field to OperationLogSnapshot), and include category plus all
author metadata fields (author_agent_id, author_seniority, author_is_human,
author_autonomy_level) on both OperationLogEntry and OperationLogSnapshot so
snapshot_at() can return the same structure as get() for a historical version;
update snapshot_at() to read these new fields instead of synthesizing them from
timestamps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1dcb95dd-b09c-4464-bb97-75e621e24005

📥 Commits

Reviewing files that changed from the base of the PR and between 5fe3c69 and dfda9d5.

📒 Files selected for processing (14)
  • CLAUDE.md
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations -- Python 3.14 has PEP 649

Use except A, B: (no parentheses) syntax for exception handling in Python 3.14 per PEP 758

All public functions must have type hints; use mypy strict mode for type checking

Use Google style docstrings, required on public classes and functions (enforced by ruff D rules)

Create new objects instead of mutating existing ones; for non-Pydantic internal collections use copy.deepcopy() at construction and MappingProxyType for read-only enforcement

Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves

Never mix static config fields with mutable runtime fields in one Pydantic model

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); include allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf

Use @computed_field for derived values in Pydantic models instead of storing and validating redundant fields (e.g., TokenUsage.total_tokens)

Use NotBlankStr from core.types for all identifier/name fields including optional and tuple variants instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls); prefer structured concurrency over bare create_task

Keep functions under 50 lines and files under 800 lines

Handle errors explicitly, never silently swallow them

Validate at system boundaries (user input, external APIs, config files)

Use line length of 88 characters (enforced by ruff)

Files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow markers for test classification

Use asyncio_mode = "auto" for async tests; no manual @pytest.mark.asyncio needed

Prefer @pytest.mark.parametrize for testing similar cases

Tests must use test-provider, test-small-001 instead of real vendor names

Use Hypothesis for property-based testing with @given and @settings; configure profiles in tests/conftest.py: ci (deterministic, max_examples=10, derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline)

When Hypothesis finds a failure, fix the underlying bug and add an explicit @example(...) decorator to the test for permanent CI coverage

Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally; for timing-sensitive tests mock time.monotonic() and asyncio.sleep() for determinism

For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) for cancellation-safety and no timing assumptions

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic must import from synthorg.observability import get_logger and set logger = get_logger(__name__)

Always use the variable name logger (not _logger, not log) for logger instances

Use vendor-agnostic names everywhere in project-owned code: example-provider, example-large-001, example-medium-001, example-small-001, or large/medium/small aliases; never use vendor names (Anthropic, OpenAI, Claude, GPT, etc.) except in: operations design page, .claude/ files, third-party imports, or provider presets

Files:

  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code (exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py)

Use constants from domain-specific modules under synthorg.observability.events for event names (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool)

Always use structured logging with logger.info(EVENT, key=value) format, never logger.info('msg %s', val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO level

Use DEBUG level for object creation, internal flow, and entry/exit of key functions

Files:

  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
🧠 Learnings (55)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Read the relevant `docs/design/` page before implementing any feature or planning any issue
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Alert the user and explain why if implementation deviates from the design spec; do not silently diverge -- every deviation needs explicit user approval
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Present every implementation plan to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design in the spirit of robustness, correctness, simplicity, and future-proofing
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Surface improvements as suggestions, not silent changes -- user decides whether to proceed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Pure data models, enums, and re-exports do not need logging
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Maintain 80% minimum code coverage (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Global test timeout is 30 seconds per test (configured in `pyproject.toml`); do not add per-file `pytest.mark.timeout(30)` markers; non-default overrides like `timeout(60)` are allowed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Always include `-n 8` when running pytest locally for parallelism with pytest-xdist; never run tests sequentially
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Control Hypothesis profile via `HYPOTHESIS_PROFILE` env var; failing examples are persisted to `~/.synthorg/hypothesis-examples/`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: CI runs 10 deterministic property test examples (`derandomize=True`); for random fuzzing locally use `HYPOTHESIS_PROFILE=dev` (1000 examples) or `HYPOTHESIS_PROFILE=fuzz` (10,000 examples, no deadline, all `given` tests)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Use commit format `<type>: <description>` with types: feat, fix, refactor, docs, test, chore, perf, ci (enforced by commitizen via commit-msg hook)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Signed commits are required on `main` via branch protection -- all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Use branch naming format `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint, go vet, no-em-dashes, no-redundant-timeout, eslint-web
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Pre-push hooks enforce mypy type-check and pytest unit tests on affected modules, plus linting for CLI and web; foundational module or conftest changes trigger full runs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Use `gh issue list` via Bash for GitHub issue queries; MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Use squash merge strategy -- PR body becomes the squash commit message on main; preserve `Closes `#NNN`` references in PR body
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically; do not leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Never create a PR directly with `gh pr create` (blocked by hookify) -- always use `/pre-pr-review` to create PRs; for trivial/docs-only changes use `/pre-pr-review quick`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: When review agents find valid issues, fix them all -- no deferring or skipping as out of scope
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: A PostToolUse hook (`scripts/check_web_design_system.py`) enforces web design system rules on every Edit/Write to `web/src/`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Never use `cd` in Bash commands -- working directory is already at project root; use absolute paths or run commands directly; exception: `bash -c "cd <dir> && <cmd>"` is safe for tools without `-C` flag
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: Never use Bash to write or modify files -- use Write or Edit tools; do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c`, or `tee` to create or modify files
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: See `web/CLAUDE.md` for web dashboard commands, design system, and component inventory
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: See `cli/CLAUDE.md` for CLI commands, flags, and reference; key rule: use `go -C cli` (never `cd cli`)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T11:38:12.476Z
Learning: See `docs/reference/claude-reference.md` for documentation layout, Docker commands, package structure, releasing, and CI pipelines
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/observability/events/org_memory.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_store.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/hybrid_backend.py
  • docs/design/memory-consistency.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_store.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/__init__.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-04-01T12:36:58.294Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T12:36:58.294Z
Learning: Applies to src/synthorg/**/*.py : Pure data models, enums, and re-exports do NOT need logging

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/org_memory.py
  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/org_memory.py
  • CLAUDE.md
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
  • CLAUDE.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/org_memory.py
  • CLAUDE.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T11:07:20.359Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.359Z
Learning: If implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge. When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T11:07:20.359Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.359Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to 12 design pages

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code (exceptions: observability setup modules)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use import logging / logging.getLogger() / print() in application code.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` -- never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves (via `model_copy(update=...)`). Never mix static config fields with mutable runtime fields in one model

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
🔇 Additional comments (1)
CLAUDE.md (1)

93-93: Good update to logging event-name guidance.

Line 93 now aligns with the actual events.org_memory inventory (including MVCC constants) and keeps the rule consistent with direct domain-constant imports.

Based on learnings: Use event name constants from domain-specific modules under synthorg.observability.events and import them directly.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
tests/unit/memory/org/test_mvcc_store.py (1)

369-378: ⚠️ Potential issue | 🟡 Minor

Assert the winning author metadata too.

This only proves content matches the highest-version log entry. A regression that leaves author_agent_id or seniority stale in org_facts_snapshot would still pass.

🧪 Tighten the assertion
             fact = await store_a.get("shared-fact")
             assert fact is not None
             winner = max(log, key=lambda e: e.version)
             assert fact.content == winner.content
+            assert fact.author.agent_id == winner.author_agent_id
+            assert fact.author.seniority == winner.author_seniority
+            assert fact.author.is_human == winner.author_is_human
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/memory/org/test_mvcc_store.py` around lines 369 - 378, The test
currently only asserts that fact.content equals the highest-version log entry;
update the assertion to also verify that the snapshot carries the winning
entry's metadata by comparing fact.author_agent_id and fact.seniority to the
winner's author_agent_id and seniority (use the existing log lookup via
get_operation_log and the snapshot read via get in test_mvcc_store.py). Locate
the winner as done with winner = max(log, key=lambda e: e.version) and add
assertions that fact.author_agent_id == winner.author_agent_id and
fact.seniority == winner.seniority to ensure org_facts_snapshot preserves
author/seniority metadata.
src/synthorg/memory/org/sqlite_store.py (1)

134-136: ⚠️ Potential issue | 🟠 Major

Preserve caller tag order when serializing.

Sorting here mutates the stored value, so get() and get_operation_log() cannot round-trip OrgFact.tags exactly. Since the public API exposes tags as ordered tuples, the serializer should preserve that order.

💡 Minimal fix
 def _tags_to_json(tags: tuple[NotBlankStr, ...]) -> str:
-    """Serialize tags tuple to sorted JSON array."""
-    return json.dumps(sorted(tags))
+    """Serialize tags tuple preserving caller order."""
+    return json.dumps(list(tags))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 134 - 136, The
serializer _tags_to_json currently sorts the tags which mutates caller order;
change it to preserve the original order by removing the sort and serializing
the tags as-is (e.g., convert the tuple to a list and call json.dumps on that)
so OrgFact.tags round-trips exactly; update _tags_to_json to return
json.dumps(list(tags)) and ensure any callers relying on sorted output are
adjusted accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 307-560: The file exceeds the 800-line limit and large functions
(save and snapshot_at) exceed 50 lines; refactor by extracting schema/DDL,
row-mapping, and time-travel reconstruction into separate modules/classes: move
SQL DDL constants (_CREATE_OPERATION_LOG_SQL, _CREATE_SNAPSHOT_SQL, _CREATE_...
etc.) and _ensure_schema into a new sqlite_schema (or SQLiteOrgSchema) unit,
move row mapping utilities used by _append_to_operation_log/save and snapshot_at
into a row_mapper (or OrgFactMapper) with functions to serialize/deserialize
tags, authors, timestamps, and fact rows, and move the time-travel replay logic
from snapshot_at into a dedicated reconstructor (e.g., OrgSnapshotReconstructor)
that can be called from snapshot_at; update SQLiteOrgFactStore to delegate to
these new units (keeping _append_to_operation_log, save, snapshot_at thin) so
functions are each under 50 lines and the file size is reduced.
- Around line 704-716: The current code uses the escaped variable for both the
LIKE filter and the INSTR() ordering parameter, causing searches with %, _, or \
to rank incorrectly; change the ORDER BY params to use the original raw text
(text) for the INSTR() relevance ordering while keeping escaped for the LIKE
clause: leave clauses.append("content LIKE ? ESCAPE '\\'") and
params.append(f"%{escaped}%") as-is, but when building order and appending the
second INSTR parameter, append the unescaped text (or its lowercased form)
instead of escaped so INSTR(LOWER(content), LOWER(?)) receives the raw search
term.
- Line 207: Update the tuple-style multi-except handlers to PEP 758
comma-separated syntax: replace constructs like "except (KeyError, ValueError,
ValidationError, OrgMemoryQueryError) as exc:" with "except KeyError,
ValueError, ValidationError, OrgMemoryQueryError as exc:" (and do the same for
the other occurrences referencing their respective exception lists). Locate
these handlers in sqlite_store.py (look for the except blocks that mention
KeyError, ValueError, ValidationError, OrgMemoryQueryError and the other
multi-except groups used inside methods of the SqliteOrgMemoryStore class) and
change each tuple-wrapped except to the comma-separated form so the code uses
the Python 3.14 PEP 758 syntax.

---

Duplicate comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 134-136: The serializer _tags_to_json currently sorts the tags
which mutates caller order; change it to preserve the original order by removing
the sort and serializing the tags as-is (e.g., convert the tuple to a list and
call json.dumps on that) so OrgFact.tags round-trips exactly; update
_tags_to_json to return json.dumps(list(tags)) and ensure any callers relying on
sorted output are adjusted accordingly.

In `@tests/unit/memory/org/test_mvcc_store.py`:
- Around line 369-378: The test currently only asserts that fact.content equals
the highest-version log entry; update the assertion to also verify that the
snapshot carries the winning entry's metadata by comparing fact.author_agent_id
and fact.seniority to the winner's author_agent_id and seniority (use the
existing log lookup via get_operation_log and the snapshot read via get in
test_mvcc_store.py). Locate the winner as done with winner = max(log, key=lambda
e: e.version) and add assertions that fact.author_agent_id ==
winner.author_agent_id and fact.seniority == winner.seniority to ensure
org_facts_snapshot preserves author/seniority metadata.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b4fe122c-9174-48cd-ad01-af9c8437df83

📥 Commits

Reviewing files that changed from the base of the PR and between dfda9d5 and e606485.

📒 Files selected for processing (5)
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Mark unit tests with @pytest.mark.unit

Mark integration tests with @pytest.mark.integration

Mark e2e tests with @pytest.mark.e2e

Mark slow tests with @pytest.mark.slow

Maintain 80% minimum code coverage (enforced in CI)

Use asyncio_mode = 'auto' in pytest config; do not add manual @pytest.mark.asyncio decorators

Set global 30-second timeout per test in pyproject.toml; do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed

Prefer @pytest.mark.parametrize for testing similar cases in Python tests

Use test-provider, test-small-001 etc. in test files instead of real vendor names

Use Hypothesis for property-based testing in Python with @given + @settings

For timing-sensitive Python tests, mock time.monotonic() and asyncio.sleep() to make them deterministic instead of widening timing margins

For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_mvcc_store.py
docs/design/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Update the relevant docs/design/ page to reflect approved deviations from the spec

Files:

  • docs/design/memory-consistency.md
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations in Python code (Python 3.14 has PEP 649)

Use except A, B: syntax (no parentheses) per PEP 758 in Python 3.14 code

All public functions must have type hints and comply with mypy strict mode

All public classes and functions must have Google-style docstrings (enforced by ruff D rules)

For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement

Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state

Use Pydantic v2 with BaseModel, model_validator, computed_field, ConfigDict

Use allow_inf_nan=False in all Pydantic ConfigDict declarations to reject NaN/Inf in numeric fields

Use @computed_field for derived values instead of storing + validating redundant fields in Pydantic models

Use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code instead of bare create_task

Keep functions under 50 lines and files under 800 lines

Use 88 character line length (enforced by ruff)

Every module with business logic must import from synthorg.observability import get_logger and assign logger = get_logger(__name__)

Never use import logging, logging.getLogger(), or print() in application code (exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py)

Always use logger as the variable name (not _logger or log)

Always use event name constants from domain-specific modules under synthorg.observability.events instead of string literals

Use structured kwargs in logger calls: logger.info(EVENT, key=value) instead of logger.info('msg %s', val)

All error paths must log at WARNING or ERROR with context before raising

All state transition...

Files:

  • src/synthorg/memory/org/sqlite_store.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/sqlite_store.py
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Read the relevant `docs/design/` page before implementing any feature or planning any issue
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Alert the user and explain why if implementation deviates from the design spec, do not silently diverge
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Present every implementation plan to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Be critical at every phase of planning and implementation, actively look for ways to improve the design
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Prioritize issues by dependency order, not priority labels
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Create new objects instead of mutating existing ones (immutability principle)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Handle errors explicitly, never silently swallow exceptions
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Validate at system boundaries (user input, external APIs, config files)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Pure data models, enums, and re-exports do NOT need logging
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Non-retryable errors raise immediately without retry
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: `RetryExhaustedError` signals all retries failed; the engine layer catches this to trigger fallback chains
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Always include `-n 8` when running pytest locally to enable parallelism with pytest-xdist
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Never run tests sequentially locally
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Control Hypothesis profile via `HYPOTHESIS_PROFILE` environment variable
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: When Hypothesis finds a failure, fix the underlying bug and add an explicit `example(...)` decorator to the test for permanent coverage in CI
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Never skip, dismiss, or ignore flaky tests; always fix them fully and fundamentally
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Commit messages must follow format `<type>: <description>` with types: feat, fix, refactor, docs, test, chore, perf, ci
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: All commits must be GPG/SSH signed on `main` (enforced by branch protection)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Use branch naming convention `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint, golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard, zero warnings)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Never use `cd` in Bash commands; use absolute paths or run commands directly; exception: `bash -c "cd <dir> && <cmd>"` is safe
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Never use Bash to write or modify files; use Write or Edit tools instead of `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, etc.
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Read the full component inventory, design token rules, and post-training references in `web/CLAUDE.md` before modifying web dashboard code
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: A PostToolUse hook (`scripts/check_web_design_system.py`) enforces design system rules on every Edit/Write to `web/src/`
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: See `cli/CLAUDE.md` for Go CLI commands, flags, and reference; use `go -C cli` (never `cd cli`)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: See `web/CLAUDE.md` for React web dashboard commands, design system, and component inventory
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: See `docs/reference/claude-reference.md` for documentation layout, Docker commands, package structure, releasing, and CI pipelines
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: After finishing an issue implementation, create a feature branch (`<type>/<slug>`), commit, and push; do NOT create a PR automatically
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Do NOT leave work uncommitted on main; branch, commit, push immediately after finishing
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Never create a PR directly with `gh pr create` (blocked by hookify); always use `/pre-pr-review` to create PRs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Use `/pre-pr-review quick` for trivial/docs-only changes to skip agents but still run automated checks
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Fix all valid issues found by review agents, including pre-existing issues in surrounding code, suggestions, and adjacent findings; never skip or defer fixes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: Use `/aurelio-review-pr` to handle external reviewer feedback on PRs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:00:23.388Z
Learning: The `/commit-push-pr` command is effectively blocked (it calls `gh pr create` internally)
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • tests/unit/memory/org/test_store.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_store.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-08T11:07:20.358Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.358Z
Learning: Applies to tests/**/*.py : For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks blocking indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T11:07:20.359Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.359Z
Learning: If implementation deviates from the spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge. When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T11:07:20.359Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T11:07:20.359Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to 12 design pages

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
🔇 Additional comments (1)
src/synthorg/memory/org/sqlite_store.py (1)

491-545: Guard concurrent access to the shared aiosqlite connection or document the single-caller constraint.

Each SQLiteOrgFactStore instance maintains a single connection in self._db. If the same store instance handles concurrent save() or delete() calls from multiple coroutines, the second call's BEGIN IMMEDIATE will fail with "cannot start a transaction within a transaction." The application currently avoids concurrent access through single-writer patterns, but there is no explicit guard (e.g., asyncio.Lock) or documentation of this design constraint. To prevent future misuse, either add synchronization or clearly document that store instances are single-caller only.

Also applies to: 562-635

author=author,
created_at=created_at,
)
except (KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and check its structure
wc -l src/synthorg/memory/org/sqlite_store.py

Repository: Aureliolo/synthorg

Length of output: 105


🏁 Script executed:

# Check the specific lines mentioned in the review comment
sed -n '205,210p; 252,256p; 292,296p; 341,349p; 380,384p' src/synthorg/memory/org/sqlite_store.py

Repository: Aureliolo/synthorg

Length of output: 1238


Update exception handlers to PEP 758 syntax without parentheses.

The codebase requires comma-separated except A, B as exc: syntax per PEP 758 for Python 3.14. Convert all tuple-style multi-except handlers at lines 207, 254, 294, 343, 347, and 382:

Example conversions
-        except (KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc:
+        except KeyError, ValueError, ValidationError, OrgMemoryQueryError as exc:
-            except (sqlite3.Error, OSError) as exc:
+            except sqlite3.Error, OSError as exc:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` at line 207, Update the tuple-style
multi-except handlers to PEP 758 comma-separated syntax: replace constructs like
"except (KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc:"
with "except KeyError, ValueError, ValidationError, OrgMemoryQueryError as exc:"
(and do the same for the other occurrences referencing their respective
exception lists). Locate these handlers in sqlite_store.py (look for the except
blocks that mention KeyError, ValueError, ValidationError, OrgMemoryQueryError
and the other multi-except groups used inside methods of the
SqliteOrgMemoryStore class) and change each tuple-wrapped except to the
comma-separated form so the code uses the Python 3.14 PEP 758 syntax.

Comment on lines +307 to +560
class SQLiteOrgFactStore:
"""SQLite-backed organizational fact store with MVCC.

All writes are appended to an operation log; a materialized
snapshot table maintains the current committed state. Reads
query the snapshot. Time-travel queries replay the log.

Uses a separate database from the operational persistence layer
to keep institutional knowledge decoupled.

Args:
db_path: Path to the SQLite database file (or ``:memory:``).

Raises:
OrgMemoryConnectionError: If the path contains traversal.
"""

def __init__(self, db_path: str) -> None:
_reject_traversal(db_path)
self._db_path = db_path
self._db: aiosqlite.Connection | None = None

async def connect(self) -> None:
"""Open the SQLite database with WAL mode and ensure schema.

Raises:
OrgMemoryConnectionError: If the connection fails.
"""
if self._db is not None:
return
try:
self._db = await aiosqlite.connect(self._db_path)
self._db.row_factory = aiosqlite.Row
if self._db_path != ":memory:":
await self._db.execute("PRAGMA journal_mode=WAL")
await self._ensure_schema()
except (sqlite3.Error, OSError) as exc:
if self._db is not None:
try:
await self._db.close()
except (sqlite3.Error, OSError) as close_exc:
logger.warning(
ORG_MEMORY_DISCONNECT_FAILED,
db_path=self._db_path,
reason="cleanup close during failed connect",
error=str(close_exc),
error_type=type(close_exc).__name__,
)
self._db = None
msg = f"Failed to connect to org fact store: {exc}"
logger.exception(
ORG_MEMORY_CONNECT_FAILED,
db_path=self._db_path,
error=str(exc),
)
raise OrgMemoryConnectionError(msg) from exc

async def _ensure_schema(self) -> None:
"""Create tables and indexes if they don't exist."""
db = self._require_connected()
await db.execute(_CREATE_OPERATION_LOG_SQL)
await db.execute(_CREATE_OPLOG_FACT_INDEX_SQL)
await db.execute(_CREATE_OPLOG_TIMESTAMP_INDEX_SQL)
await db.execute(_CREATE_OPLOG_COMPOSITE_INDEX_SQL)
await db.execute(_CREATE_SNAPSHOT_SQL)
await db.execute(_CREATE_SNAPSHOT_CATEGORY_INDEX_SQL)
await db.execute(_CREATE_SNAPSHOT_ACTIVE_INDEX_SQL)
await db.commit()

async def disconnect(self) -> None:
"""Close the database connection."""
if self._db is None:
return
try:
await self._db.close()
except (sqlite3.Error, OSError) as exc:
logger.warning(
ORG_MEMORY_DISCONNECT_FAILED,
db_path=self._db_path,
error=str(exc),
error_type=type(exc).__name__,
)
finally:
self._db = None

def _require_connected(self) -> aiosqlite.Connection:
"""Return the connection or raise if not connected.

Raises:
OrgMemoryConnectionError: If not connected.
"""
if self._db is None:
msg = "Not connected -- call connect() first"
logger.warning(
ORG_MEMORY_NOT_CONNECTED,
db_path=self._db_path,
)
raise OrgMemoryConnectionError(msg)
return self._db

# ── Write operations ────────────────────────────────────────

async def _append_to_operation_log( # noqa: PLR0913
self,
db: aiosqlite.Connection,
*,
fact_id: str,
operation_type: Literal["PUBLISH", "RETRACT"],
content: str | None,
category: OrgFactCategory | None,
tags: tuple[NotBlankStr, ...],
author_agent_id: str | None,
author_seniority: SeniorityLevel | None,
author_is_human: bool,
author_autonomy_level: AutonomyLevel | None,
) -> tuple[int, datetime]:
"""Append an operation to the log within the caller's transaction.

Must be called inside a ``BEGIN IMMEDIATE`` transaction
managed by the caller.

Args:
db: Active database connection (caller-managed txn).
fact_id: Logical fact identifier.
operation_type: ``PUBLISH`` or ``RETRACT``.
content: Fact body (``None`` for RETRACT).
category: Fact category at time of operation.
tags: Metadata tags.
author_agent_id: Agent ID (``None`` for human).
author_seniority: Agent seniority level.
author_is_human: Whether the author is human.
author_autonomy_level: Autonomy level at write time.

Returns:
Tuple of ``(version, timestamp)``.
"""
operation_id = str(uuid.uuid4())
now = datetime.now(UTC)
cursor = await db.execute(
"SELECT COALESCE(MAX(version), 0) "
"FROM org_facts_operation_log WHERE fact_id = ?",
(fact_id,),
)
row = await cursor.fetchone()
# COALESCE(MAX(version), 0) always returns exactly one row.
current: int = row[0] if row is not None else 0
next_version = current + 1
await db.execute(
"INSERT INTO org_facts_operation_log "
"(operation_id, fact_id, operation_type, content, "
"tags, author_agent_id, author_seniority, "
"author_is_human, author_autonomy_level, category, "
"timestamp, version) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
operation_id,
fact_id,
operation_type,
content,
_tags_to_json(tags),
author_agent_id,
(author_seniority.value if author_seniority else None),
int(author_is_human),
(author_autonomy_level.value if author_autonomy_level else None),
(category.value if category else None),
now.isoformat(),
next_version,
),
)
return next_version, now

async def save(self, fact: OrgFact) -> None:
"""Publish a fact: append PUBLISH to log, upsert snapshot.

Re-publishing a fact with the same ``fact_id`` creates a
new version in the operation log and updates the snapshot.

Args:
fact: The fact to persist.

Raises:
OrgMemoryConnectionError: If not connected.
OrgMemoryWriteError: If the save fails.
"""
db = self._require_connected()
try:
await db.execute("BEGIN IMMEDIATE")
version, _ = await self._append_to_operation_log(
db,
fact_id=fact.id,
operation_type="PUBLISH",
content=fact.content,
category=fact.category,
tags=fact.tags,
author_agent_id=fact.author.agent_id,
author_seniority=fact.author.seniority,
author_is_human=fact.author.is_human,
author_autonomy_level=fact.author.autonomy_level,
)
await db.execute(
"INSERT INTO org_facts_snapshot "
"(fact_id, content, category, tags, "
"author_agent_id, author_seniority, author_is_human, "
"author_autonomy_level, created_at, retracted_at, "
"version) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?) "
"ON CONFLICT(fact_id) DO UPDATE SET "
"content=excluded.content, "
"category=excluded.category, "
"tags=excluded.tags, "
"author_agent_id=excluded.author_agent_id, "
"author_seniority=excluded.author_seniority, "
"author_is_human=excluded.author_is_human, "
"author_autonomy_level=excluded.author_autonomy_level, "
"retracted_at=NULL, "
"version=excluded.version",
(
fact.id,
fact.content,
fact.category.value,
_tags_to_json(fact.tags),
fact.author.agent_id,
(fact.author.seniority.value if fact.author.seniority else None),
int(fact.author.is_human),
(
fact.author.autonomy_level.value
if fact.author.autonomy_level
else None
),
(
fact.created_at.astimezone(UTC).isoformat()
if fact.created_at.tzinfo is not None
else fact.created_at.replace(tzinfo=UTC).isoformat()
),
version,
),
)
await db.commit()
except sqlite3.Error as exc:
with contextlib.suppress(sqlite3.Error):
await db.execute("ROLLBACK")
logger.exception(
ORG_MEMORY_WRITE_FAILED,
fact_id=fact.id,
error=str(exc),
)
msg = f"Failed to save org fact: {exc}"
raise OrgMemoryWriteError(msg) from exc
else:
logger.info(
ORG_MEMORY_MVCC_PUBLISH_APPENDED,
fact_id=fact.id,
version=version,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Split this MVCC store before it grows further.

This new module is already past the 800-line cap, and save() plus snapshot_at() are both well over the 50-line function limit. Pulling DDL/row-mapping/time-travel reconstruction into dedicated units will make this logic easier to review and evolve safely. As per coding guidelines, "src/**/*.py: Keep functions under 50 lines and files under 800 lines."

Also applies to: 771-860

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 307 - 560, The file
exceeds the 800-line limit and large functions (save and snapshot_at) exceed 50
lines; refactor by extracting schema/DDL, row-mapping, and time-travel
reconstruction into separate modules/classes: move SQL DDL constants
(_CREATE_OPERATION_LOG_SQL, _CREATE_SNAPSHOT_SQL, _CREATE_... etc.) and
_ensure_schema into a new sqlite_schema (or SQLiteOrgSchema) unit, move row
mapping utilities used by _append_to_operation_log/save and snapshot_at into a
row_mapper (or OrgFactMapper) with functions to serialize/deserialize tags,
authors, timestamps, and fact rows, and move the time-travel replay logic from
snapshot_at into a dedicated reconstructor (e.g., OrgSnapshotReconstructor) that
can be called from snapshot_at; update SQLiteOrgFactStore to delegate to these
new units (keeping _append_to_operation_log, save, snapshot_at thin) so
functions are each under 50 lines and the file size is reduced.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/synthorg/memory/org/sqlite_store.py (2)

307-560: 🧹 Nitpick | 🔵 Trivial

File exceeds size limits; consider splitting.

The file is 911 lines (exceeds 800-line limit), and both save() (~70 lines) and snapshot_at() (~70 lines) exceed the 50-line function limit. As previously noted, extracting DDL/schema, row-mapping helpers, and time-travel CTE logic into separate modules would improve maintainability.

As per coding guidelines: "Functions must be < 50 lines; files < 800 lines"

📦 Suggested module split
  1. sqlite_schema.py: DDL constants (_CREATE_*_SQL) and _ensure_schema()
  2. sqlite_mappers.py: Row-to-model functions (_snapshot_row_to_org_fact, _row_to_operation_log_entry, _row_to_snapshot)
  3. sqlite_store.py: Core SQLiteOrgFactStore class with delegated imports

This keeps each module focused and under size limits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 307 - 560, The file
exceeds the 800-line limit and large functions (notably save() and
snapshot_at()) exceed the 50-line guideline; split schema DDL, row-mapping
helpers, and time-travel CTE logic into separate modules and import them into
SQLiteOrgFactStore. Extract the _CREATE_*_SQL constants and the logic from
_ensure_schema() into a new sqlite_schema.py, move row-to-model helpers (create
_snapshot_row_to_org_fact, _row_to_operation_log_entry, _row_to_snapshot) into
sqlite_mappers.py, and move any complex time-travel/CTE SQL used by
snapshot_at() into a dedicated helper module (e.g., sqlite_time_travel.py);
update SQLiteOrgFactStore to import these symbols and delegate to them so
save(), snapshot_at(), and _ensure_schema() shrink below 50 lines and the
original file drops under 800 lines.

207-214: ⚠️ Potential issue | 🟡 Minor

Update exception handlers to PEP 758 comma-separated syntax.

The except clause uses parenthesized tuple syntax, but PEP 758 requires comma-separated syntax without parentheses for Python 3.14. This applies to multiple locations in the file.

As per coding guidelines: "Use PEP 758 except syntax: except A, B: (no parentheses)—ruff enforces this on Python 3.14"

🛠️ Suggested fix for Line 207
-    except (KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc:
+    except KeyError, ValueError, ValidationError, OrgMemoryQueryError as exc:

Same pattern applies at Lines 254, 294, 343, 347, and 382.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 207 - 214, The except
clauses that catch multiple exceptions using parenthesized tuples (e.g., except
(KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc:) must be
changed to PEP 758 comma-separated syntax (e.g., except KeyError, ValueError,
ValidationError, OrgMemoryQueryError as exc:). Update every such multi-exception
handler in src/synthorg/memory/org/sqlite_store.py — specifically the handlers
that log ORG_MEMORY_ROW_PARSE_FAILED and raise OrgMemoryQueryError (and any
other occurrences catching multiple exceptions) to use the new comma-separated
form while preserving the existing logger.warning(...) and raise
OrgMemoryQueryError(...) behavior.
src/synthorg/memory/org/models.py (1)

272-316: ⚠️ Potential issue | 🟠 Major

OperationLogSnapshot is missing the category field.

The org_facts_operation_log table stores category for every operation, and the OperationLogEntry model includes it. However, the snapshot_at() CTE query omits category from its SELECT clause, and OperationLogSnapshot lacks the field entirely. This means time-travel snapshots cannot preserve or restore category state across history, limiting queries that depend on category filtering.

Add category to the snapshot query, model, and _row_to_snapshot constructor.

🛠️ Suggested fix
 class OperationLogSnapshot(BaseModel):
     """Materialized snapshot row for current committed state.

     Represents the state of a single fact at a point in time.
     Active facts have ``retracted_at=None``.

     Attributes:
         fact_id: Logical fact identifier (primary key).
         content: Current fact body.
+        category: Fact category.
         tags: Current metadata tags.
         created_at: Timestamp of first PUBLISH.
         retracted_at: Timestamp of retraction (``None`` = active).
         version: Version matching most recent operation log entry.
     """

     model_config = ConfigDict(frozen=True, allow_inf_nan=False)

     fact_id: NotBlankStr = Field(description="Logical fact identifier")
     content: NotBlankStr = Field(description="Current fact body")
+    category: OrgFactCategory = Field(description="Fact category")
     tags: tuple[NotBlankStr, ...] = Field(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/models.py` around lines 272 - 316,
OperationLogSnapshot currently lacks the category field so snapshots lose fact
category state; add a category: NotBlankStr field (no default) to the
OperationLogSnapshot model, update the snapshot_at() CTE SELECT to include
category in the materialized row, and update the _row_to_snapshot constructor
(the function that builds OperationLogSnapshot from DB rows) to map the DB's
category column into the new OperationLogSnapshot.category attribute so
historical snapshots preserve and restore category correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/memory/org/test_mvcc_store.py`:
- Around line 101-109: The test test_save_with_tags_records_in_log relies on the
implementation detail that tags are stored sorted; update the test to clearly
express that expectation by either adding a one-line comment referencing the
sorting behavior in _tags_to_json or changing the assertion to compare against
tuple(sorted(("security", "core-policy"))) (i.e., assert log[0].tags ==
tuple(sorted(("security", "core-policy")))), and keep references to _make_fact
and _tags_to_json so future readers know this is an intentional sorted-roundtrip
check.

---

Duplicate comments:
In `@src/synthorg/memory/org/models.py`:
- Around line 272-316: OperationLogSnapshot currently lacks the category field
so snapshots lose fact category state; add a category: NotBlankStr field (no
default) to the OperationLogSnapshot model, update the snapshot_at() CTE SELECT
to include category in the materialized row, and update the _row_to_snapshot
constructor (the function that builds OperationLogSnapshot from DB rows) to map
the DB's category column into the new OperationLogSnapshot.category attribute so
historical snapshots preserve and restore category correctly.

In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 307-560: The file exceeds the 800-line limit and large functions
(notably save() and snapshot_at()) exceed the 50-line guideline; split schema
DDL, row-mapping helpers, and time-travel CTE logic into separate modules and
import them into SQLiteOrgFactStore. Extract the _CREATE_*_SQL constants and the
logic from _ensure_schema() into a new sqlite_schema.py, move row-to-model
helpers (create _snapshot_row_to_org_fact, _row_to_operation_log_entry,
_row_to_snapshot) into sqlite_mappers.py, and move any complex time-travel/CTE
SQL used by snapshot_at() into a dedicated helper module (e.g.,
sqlite_time_travel.py); update SQLiteOrgFactStore to import these symbols and
delegate to them so save(), snapshot_at(), and _ensure_schema() shrink below 50
lines and the original file drops under 800 lines.
- Around line 207-214: The except clauses that catch multiple exceptions using
parenthesized tuples (e.g., except (KeyError, ValueError, ValidationError,
OrgMemoryQueryError) as exc:) must be changed to PEP 758 comma-separated syntax
(e.g., except KeyError, ValueError, ValidationError, OrgMemoryQueryError as
exc:). Update every such multi-exception handler in
src/synthorg/memory/org/sqlite_store.py — specifically the handlers that log
ORG_MEMORY_ROW_PARSE_FAILED and raise OrgMemoryQueryError (and any other
occurrences catching multiple exceptions) to use the new comma-separated form
while preserving the existing logger.warning(...) and raise
OrgMemoryQueryError(...) behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc71928a-de74-41e4-9e67-5628808a8f48

📥 Commits

Reviewing files that changed from the base of the PR and between e606485 and 558def6.

📒 Files selected for processing (14)
  • CLAUDE.md
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations—Python 3.14 has PEP 649 native lazy annotations

Use PEP 758 except syntax: except A, B: (no parentheses)—ruff enforces this on Python 3.14

Files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • tests/unit/memory/org/test_mvcc_store.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use markers @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow for test classification

Prefer @pytest.mark.parametrize for testing similar cases

Property-based testing: use Hypothesis (@given + @settings) in Python tests with profiles configured in tests/conftest.py (ci, dev, fuzz, extreme); controlled via HYPOTHESIS_PROFILE env var

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
{src,tests}/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples—use generic names like example-provider, test-provider, large/medium/small

Files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • tests/unit/memory/org/test_mvcc_store.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

All public functions require type hints; mypy strict mode enforced

Docstrings required on public classes/functions using Google style, enforced by ruff D rules

Create new objects, never mutate existing ones; use copy.deepcopy() at construction and MappingProxyType for read-only enforcement

Use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state that evolves

Use Pydantic v2 with allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing and validating redundant fields

Use NotBlankStr (from core.types) for all identifier/name fields—including optional and tuple variants—instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; prefer structured concurrency over bare create_task

Line length: 88 characters (enforced by ruff)

Functions must be < 50 lines; files < 800 lines

Handle errors explicitly, never silently swallow; validate at system boundaries (user input, external APIs, config files)

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic must have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Logger variable name must always be logger (not _logger, not log)

Event names must always use constants from domain-specific modules under synthorg.observability.events—import directly and use structured kwargs like logger.info(EVENT, key=value)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO level

DEBUG logging for object creation, internal flow, entry/exit of key functions

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
🧠 Learnings (73)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: If implementation deviates from the design spec, alert the user and explain why—user decides whether to proceed or update the spec
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: At every phase of planning and implementation, be critical and actively look for ways to improve the design; surface improvements as suggestions, not silent changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Prioritize issues by dependency order, not priority labels—unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Pure data models, enums, and re-exports do NOT need logging
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Coverage minimum: 80% (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Async tests: use `asyncio_mode = "auto"` in pytest configuration—no manual `pytest.mark.asyncio` needed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Test timeout: 30 seconds per test (global in `pyproject.toml`); do not add per-file `pytest.mark.timeout(30)` markers unless overriding to non-default
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Parallelism: always include `-n 8` when running pytest locally, never run tests sequentially
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Vendor names may only appear in: (1) Operations design page provider list, (2) `.claude/` skill/agent files, (3) third-party import paths/module names, (4) provider presets in `src/synthorg/providers/presets.py` which are user-facing runtime data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: When Hypothesis finds a failure, it is a real bug—the shrunk example is saved to `~/.synthorg/hypothesis-examples/` for analysis but is not replayed automatically; fix the underlying bug and add an explicit `example(...)` decorator
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Never skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism, or use `asyncio.Event().wait()` for indefinite blocking
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Commits: use format `<type>: <description>` with types (feat, fix, refactor, docs, test, chore, perf, ci)—enforced by commitizen
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Signed commits required on `main` via branch protection—all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Branch naming: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile), golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (zero warnings, web dashboard)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Hookify rules: `block-pr-create` (blocks direct `gh pr create`), `enforce-parallel-tests` (enforces `-n 8` with pytest), `no-cd-prefix` (blocks `cd` prefix in Bash), `no-local-coverage` (blocks `--cov` flags locally)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Pre-push hooks: mypy type-check (affected modules) + pytest unit tests (affected modules) + golangci-lint + go vet + go test (CLI) + eslint-web (dashboard); foundational module changes (core, config, observability) or conftest changes trigger full runs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Pre-commit.ci: autoupdate disabled (`autoupdate_schedule: never`)—Dependabot owns hook version bumps via `pre-commit` ecosystem
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools)—MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Merge strategy: squash merge—PR body becomes the squash commit message on main; trailers (e.g. `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Preserve existing `Closes `#NNN`` references in PR issue references—never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: After finishing an issue implementation: always create a feature branch (`<type>/<slug>`), commit, and push—do NOT create a PR automatically
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Do NOT leave work uncommitted on main—branch, commit, push immediately after finishing
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Never create a PR directly—`gh pr create` is blocked by hookify; always use `/pre-pr-review` to create PRs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: For trivial/docs-only changes: use `/pre-pr-review quick` to skip agents but still run automated checks
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Fix all valid issues found by review agents—never skip, no deferring, no 'out of scope' skipping; include pre-existing issues in surrounding code and adjacent findings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Never use `cd` in Bash commands—working directory is already set to project root; use absolute paths or run commands directly; exception: `bash -c "cd <dir> && <cmd>"` is safe (child process)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: Never use Bash to write or modify files—use Write or Edit tools; do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, or `tee` to create/modify files (read-only uses are fine)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T12:36:56.849Z
Learning: See `web/CLAUDE.md` for the full component inventory, design token rules, and post-training references; a PostToolUse hook (`scripts/check_web_design_system.py`) enforces these rules on every Edit/Write to `web/src/`
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_store.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/store.py
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-06T16:35:12.934Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-06T16:35:12.934Z
Learning: All project conventions, commands, and standards are defined in CLAUDE.md - refer to it for project structure, package layout, code conventions, quick commands, git workflow, testing standards, design specifications, logging, resilience, and security patterns

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to src/synthorg/**/*.py : Always use structured kwargs in logger calls (e.g., `logger.info(EVENT, key=value)`) never format strings (e.g., `logger.info('msg %s', val)`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to src/synthorg/**/*.py : Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-04-01T12:36:58.294Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T12:36:58.294Z
Learning: Applies to src/synthorg/**/*.py : Pure data models, enums, and re-exports do NOT need logging

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`) instead of string literals.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.{py,ts,tsx,js,go} : If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge -- every deviation needs explicit user approval.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier and name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config and identity data. Use separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/core/**/*.py : Core module must contain shared domain models, base classes, resilience config (RetryConfig, RateLimiterConfig)

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) for PEP 758 exception syntax -- ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic. For tasks that must block indefinitely, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/synthorg/memory/org/models.py (1)

186-246: ⚠️ Potential issue | 🟠 Major

snapshot_at() still cannot reconstruct the original fact timestamp.

OperationLogEntry still has no way to carry OrgFact.created_at. In src/synthorg/memory/org/sqlite_store.py, save() persists fact.created_at only in org_facts_snapshot, while snapshot_at() rebuilds created_at from operation-log timestamps. Any backfilled/imported fact whose creation time differs from publish time will therefore come back with the wrong historical created_at. Persist the immutable source created_at in the log and have time-travel read that instead of inferring it from write timestamps.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/models.py` around lines 186 - 246, OperationLogEntry
currently lacks the immutable OrgFact.created_at so snapshot_at() reconstructs
created_at from operation timestamps; add a created_at: AwareDatetime | None
Field to OperationLogEntry (frozen model_config remains) and update
sqlite_store.save() to persist fact.created_at into the operation log row (in
addition to org_facts_snapshot), then change snapshot_at() to read created_at
from the OperationLogEntry (falling back to timestamp only if created_at is
null) so historical facts preserve their original creation time.
src/synthorg/memory/org/sqlite_store.py (1)

207-207: 🛠️ Refactor suggestion | 🟠 Major

Rewrite the tuple-style multi-except clauses to PEP 758 syntax.

This file still uses except (A, B) as exc, which does not match the repo’s required Python 3.14 style and will keep Ruff flagging these handlers.

#!/bin/bash
rg -n 'except \(' src/synthorg/memory/org/sqlite_store.py
Example conversion
-    except (sqlite3.Error, OSError) as exc:
+    except sqlite3.Error, OSError as exc:

As per coding guidelines, "**/*.py: Use except A, B: (no parentheses) for PEP 758 except syntax -- ruff enforces this on Python 3.14`."

Also applies to: 254-255, 295-296, 344-348, 383-383

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` at line 207, Replace tuple-style
except clauses like "except (KeyError, ValueError, ValidationError,
OrgMemoryQueryError) as exc" with PEP 758 syntax by removing the parentheses and
comma-separating exception names: "except KeyError, ValueError, ValidationError,
OrgMemoryQueryError as exc". Apply the same change for the other occurrences
noted (the handlers involving the same pattern around the other blocks) so each
multi-exception handler uses the new "except A, B, C as exc" form.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 587-629: The code in the delete flow can raise non-sqlite
exceptions when parsing the selected row (OrgFactCategory(row["category"]) or
_tags_from_json(row["tags"])) before _append_to_operation_log is called, leaving
the DB transaction open; update the error handling so any exception during row
parsing or subsequent steps triggers a rollback and is converted to an
OrgMemoryWriteError. Concretely, wrap the SELECT/row
parsing/_append_to_operation_log/UPDATE sequence in a try/except Exception block
(or add an additional broad except around the parsing and call sites) that calls
await db.execute("ROLLBACK") inside a contextlib.suppress(sqlite3.Error), logs
via logger.exception(ORG_MEMORY_WRITE_FAILED, fact_id=fact_id, error=str(exc)),
and re-raises as OrgMemoryWriteError (like the existing sqlite3.Error handler)
so malformed snapshot rows no longer leave the connection in a transaction;
reference the _append_to_operation_log, OrgFactCategory, _tags_from_json, and
logger.exception usages to locate where to add this handling.
- Around line 325-328: Init currently sets up _db_path and _db but lacks a write
lock; add an asyncio.Lock instance (e.g., self._write_lock = asyncio.Lock()) in
__init__ of the SQLite store class and then guard all write and
read-modify-write methods (at minimum save() and delete(), and any other methods
that perform BEGIN/COMMIT sequences) by wrapping their transaction bodies with
"async with self._write_lock:" to prevent interleaving of BEGIN/COMMIT across
coroutines, following the same pattern used in SQLitePersistenceBackend and
SQLiteDecisionRepository.

---

Duplicate comments:
In `@src/synthorg/memory/org/models.py`:
- Around line 186-246: OperationLogEntry currently lacks the immutable
OrgFact.created_at so snapshot_at() reconstructs created_at from operation
timestamps; add a created_at: AwareDatetime | None Field to OperationLogEntry
(frozen model_config remains) and update sqlite_store.save() to persist
fact.created_at into the operation log row (in addition to org_facts_snapshot),
then change snapshot_at() to read created_at from the OperationLogEntry (falling
back to timestamp only if created_at is null) so historical facts preserve their
original creation time.

In `@src/synthorg/memory/org/sqlite_store.py`:
- Line 207: Replace tuple-style except clauses like "except (KeyError,
ValueError, ValidationError, OrgMemoryQueryError) as exc" with PEP 758 syntax by
removing the parentheses and comma-separating exception names: "except KeyError,
ValueError, ValidationError, OrgMemoryQueryError as exc". Apply the same change
for the other occurrences noted (the handlers involving the same pattern around
the other blocks) so each multi-exception handler uses the new "except A, B, C
as exc" form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0be8708a-8b0d-407d-9dcf-cad79b2e8eb0

📥 Commits

Reviewing files that changed from the base of the PR and between 558def6 and 867b819.

📒 Files selected for processing (3)
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • tests/unit/memory/org/test_models.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Do not use from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations

Use except A, B: (no parentheses) for PEP 758 except syntax -- ruff enforces this on Python 3.14

Type hints: all public functions must have type hints, enforce mypy strict mode

Docstrings: Google style, required on public classes and functions (enforced by ruff D rules)

Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction plus MappingProxyType wrapping for read-only enforcement

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)

Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). In all ConfigDict declarations, use allow_inf_nan=False to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing and validating redundant fields (e.g., TokenUsage.total_tokens)

Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators

For async concurrency, prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task

Line length: 88 characters (ruff enforced)

Functions must be less than 50 lines, files less than 800 lines

Handle errors explicitly, never silently swallow

Vali...

Files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/models.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Use pytest markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow

Coverage: 80% minimum (enforced in CI)

Async tests: asyncio_mode = "auto" in pytest config -- no manual @pytest.mark.asyncio needed

Test timeout: 30 seconds per test (global in pyproject.toml). Do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed

Always include -n 8 when running pytest locally for parallelism via pytest-xdist. Never run tests sequentially. CI uses -n auto

Prefer @pytest.mark.parametrize for testing similar cases

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in tests. Use generic names: test-provider, test-small-001, test-medium-001, test-large-001

Use Hypothesis for property-based testing with @given and @settings decorators. Use pytest profiles: ci (deterministic, max_examples=10, derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Control via HYPOTHESIS_PROFILE env var

Hypothesis workflow: CI runs 10 deterministic examples per property test. When Hypothesis finds a failure, fix the underlying bug and add an explicit @example(...) decorator to permanently cover the case in CI. Do NOT just rerun and move on

Never skip, dismiss, or ignore flaky tests. Always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks blocking indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Files:

  • tests/unit/memory/org/test_models.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_models.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Never use import logging / logging.getLogger() / print() in application code. Exceptions: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, and observability/http_handler.py may use stdlib logging and print(..., file=sys.stderr) for handler construction and bootstrap

Logging variable name: always logger (not _logger, not log)

Event names: always use constants from domain-specific modules under synthorg.observability.events (import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT). Comprehensive list includes: API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool, GIT_COMMAND_START from events.git, and many others across all domain modules

Structured logging kwargs: always use logger.info(EVENT, key=value) -- never logger.info('msg %s', val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO

DEBUG logging is appropriate for object creation, internal flow, and entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging

All provider calls go through BaseCompletionProvider which applies retry and rate limiting automatically. Never implement retry logic in driver subclasses or calling code

RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig

Retryable errors (is_retryable=True) include: RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately

RetryExhaustedError signals that all retries failed -- the engine layer catches this to trigger fallback chains

The rate limiter respects RateLimitError.retry_after from providers -- automatically pauses future requests

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc...

Files:

  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/models.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/models.py
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Alert the user and explain why if implementation deviates from the design spec (better approach found, scope evolved, etc.) -- user decides whether to proceed or update the spec. Never silently diverge
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: At every phase of planning and implementation, be critical -- actively look for ways to improve the design in the spirit of robustness, correctness, simplicity, and future-proofing. Surface improvements as suggestions, not silent changes
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Never use `cd` in Bash commands. The working directory is already set to the project root. Use absolute paths or run commands directly. Exception: `bash -c "cd <dir> && <cmd>"` is safe (runs in a child process, no cwd side effects)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Never use Bash to write or modify files -- use the Write or Edit tools. Do not use `cat >`, `cat << EOF`, `echo >`, `echo >>`, `sed -i`, `python -c "open(...).write(...)"`, or `tee` to create or modify files
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Branch naming: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Pre-commit hooks enforce: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint (Dockerfile linting), golangci-lint + go vet (CLI), no-em-dashes, no-redundant-timeout, eslint-web (web dashboard)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Hookify rules enforce: `block-pr-create` (blocks direct `gh pr create`, must use `/pre-pr-review`), `enforce-parallel-tests` (enforces `-n 8` with pytest), `no-cd-prefix` (blocks `cd` prefix in Bash commands), `no-local-coverage` (blocks `--cov` flags locally)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Pre-push hooks: mypy type-check (affected modules only) + pytest unit tests (affected modules only) + golangci-lint + go vet + go test (CLI) + eslint-web (web dashboard). Foundational module changes (core, config, observability) or conftest changes trigger full runs
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Pre-commit.ci: autoupdate disabled (`autoupdate_schedule: never`) -- Dependabot owns hook version bumps via `pre-commit` ecosystem
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: GitHub issue queries: use `gh issue list` via Bash (not MCP tools) -- MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Merge strategy: squash merge -- PR body becomes the squash commit message on main. Trailers (e.g., `Release-As`, `Closes `#N``) must be in the PR body to land in the final commit
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: PR issue references: preserve existing `Closes `#NNN`` references in PR bodies -- never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: After finishing an issue implementation: always create a feature branch (`<type>/<slug>`), commit, and push -- do NOT create a PR automatically. Do NOT leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Never create a PR directly with `gh pr create` (blocked by hookify). Always use `/pre-pr-review` to create PRs -- it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback. The `/commit-push-pr` command is effectively blocked (it calls `gh pr create` internally)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T13:11:06.754Z
Learning: Fix everything valid in PR review -- never skip valid issues found by review agents, including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier and name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) for PEP 758 exception syntax -- ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config and identity data. Use separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 14:12 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 596-651: The delete() block opens a transaction with "BEGIN
IMMEDIATE" but doesn't handle asyncio.CancelledError, which can leave the
transaction open; add an explicit "except asyncio.CancelledError" handler before
the existing except tuple that calls the same rollback logic (use
contextlib.suppress to await db.execute("ROLLBACK")), then re-raise the
CancelledError so cancellation propagates; keep the existing exception handler
for sqlite3.Error/ValueError/OrgMemoryQueryError intact and reference the same
symbols (_append_to_operation_log, ORG_MEMORY_WRITE_FAILED, logger) when placing
the new handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: abac9ab8-5cbc-4cdd-ab75-96d6bdcb8532

📥 Commits

Reviewing files that changed from the base of the PR and between 867b819 and a249cc1.

📒 Files selected for processing (1)
  • src/synthorg/memory/org/sqlite_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Backend
  • GitHub Check: Build Web
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always read the relevant docs/design/ page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior

If implementation deviates from the design spec (better approach found, scope evolved, etc.), alert the user and explain why -- user decides whether to proceed or update the spec. Do NOT silently diverge -- every deviation needs explicit user approval

When a spec topic is referenced (e.g. 'the Agents page' or 'the Engine page's Crash Recovery section'), read the relevant docs/design/ page before coding

When approved deviations occur, update the relevant docs/design/ page to reflect the new reality

Commits must follow format: <type>: <description> where types are feat, fix, refactor, docs, test, chore, perf, ci -- enforced by commitizen (commit-msg hook)

All commits must be GPG/SSH signed (required on main via branch protection)

Files:

  • src/synthorg/memory/org/sqlite_store.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations -- Python 3.14 has PEP 649 native lazy annotations

Use PEP 758 except syntax: use except A, B: (no parentheses) -- ruff enforces this on Python 3.14

All public functions must have type hints and docstrings in Google style

All public classes must have Google-style docstrings

Create new objects instead of mutating existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement

For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence)

Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves (e.g. agent execution state, task progress). Never mix static config fields with mutable runtime fields in one model

Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time

Use @computed_field for derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens)

Use NotBlankStr (from core.types) for all identifier/name fields -- including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants -- instead of manual whitespace validators

Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task

Line length must be 88 characters (enforced by ruff)

Functions must be less than 50 lines; files must be less than 800 lines

Handle errors explicitly; never silently swallow exceptions

Validate at system boundaries (user input,...

Files:

  • src/synthorg/memory/org/sqlite_store.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases. Vendor names may only appear in: (1) Operations design page provider list (docs/design/operations.md), (2) .claude/ skill/agent files, (3) third-party import paths/module names, (4) provider presets (src/synthorg/providers/presets.py)

Files:

  • src/synthorg/memory/org/sqlite_store.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/sqlite_store.py
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Never use Bash to write or modify files -- use the Write or Edit tools. Do not use cat >, cat << EOF, echo >, echo >>, sed -i, or tee to create/modify files. Read-only/inspection uses like piping to stdout are fine

Files:

  • src/synthorg/memory/org/sqlite_store.py
🧠 Learnings (14)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: At every phase of planning and implementation, be critical -- actively look for ways to improve the design in the spirit of what we're building (robustness, correctness, simplicity, future-proofing where it's free)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Surface improvements as suggestions, not silent changes -- user decides
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Prioritize issues by dependency order, not priority labels -- unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts. At every phase be critical and surface improvements as suggestions. Prioritize issues by dependency order, not priority labels
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Use feature branches `<type>/<slug>` from main. After finishing an issue implementation, create a feature branch, commit, and push -- do NOT create a PR automatically. Never leave work uncommitted on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: Never create a PR directly using `gh pr create` (blocked by hookify). Always use `/pre-pr-review` to create PRs. For trivial/docs-only changes use `/pre-pr-review quick`. After the PR exists, use `/aurelio-review-pr` for external reviewer feedback
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T14:12:38.432Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no 'out of scope' skipping
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-04-08T12:31:46.839Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T12:31:46.839Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) for PEP 758 exception syntax -- ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
🔇 Additional comments (9)
src/synthorg/memory/org/sqlite_store.py (9)

208-208: PEP 758 exception syntax still uses parentheses.

This was previously flagged. The codebase requires comma-separated except A, B, C as exc: syntax per PEP 758 for Python 3.14.


787-883: LGTM!

The time-travel CTE correctly reconstructs point-in-time state:

  • Uses ROW_NUMBER() OVER (PARTITION BY fact_id ORDER BY version DESC) to find the latest operation per fact at the query timestamp.
  • Falls back to the most recent PUBLISH for content/category/tags when the latest operation is RETRACT.
  • Derives created_at from the earliest PUBLISH timestamp.

The function length exceeds guidelines but was already flagged in a previous review.


1-46: LGTM!

The imports are well-organized. Observability setup correctly uses get_logger(__name__) with the proper logger variable name, and event constants are imported from the domain-specific module.


48-108: LGTM!

The schema design is solid:

  • Append-only operation log with UNIQUE(fact_id, version) enforces version monotonicity.
  • Partial index idx_snapshot_active on retracted_at IS NULL optimizes active-fact reads.
  • Composite index idx_oplog_ts_fact supports efficient time-travel queries.

218-304: LGTM!

Row deserialization helpers properly handle nullable fields, parse timestamps with UTC fallback, and wrap all conversion errors into OrgMemoryQueryError with appropriate logging.


326-331: LGTM!

The asyncio.Lock for write serialization is properly initialized. Path traversal validation in __init__ provides defense against malicious paths.


332-394: LGTM!

Connection lifecycle management is robust:

  • WAL mode is correctly skipped for :memory: databases.
  • Schema creation is idempotent with IF NOT EXISTS.
  • Failed connections are properly cleaned up with the connection closed and set to None.
  • Disconnect logs errors but still clears state in finally.

655-783: LGTM!

Read operations correctly:

  • Filter on retracted_at IS NULL to return only active facts per the protocol contract.
  • Use parameterized queries throughout to prevent SQL injection.
  • Use raw text (not escaped) for INSTR() relevance ordering while using escaped text for LIKE filtering.

926-934: LGTM!

Properties are simple and correct.

Comment on lines +596 to +651
async with self._write_lock:
try:
await db.execute("BEGIN IMMEDIATE")
cursor = await db.execute(
"SELECT fact_id, category, tags "
"FROM org_facts_snapshot "
"WHERE fact_id = ? "
"AND retracted_at IS NULL",
(fact_id,),
)
row = await cursor.fetchone()
if row is None:
await db.execute("ROLLBACK")
return False
version, now = await self._append_to_operation_log(
db,
fact_id=fact_id,
operation_type="RETRACT",
content=None,
category=(
OrgFactCategory(row["category"]) if row["category"] else None
),
tags=_tags_from_json(row["tags"]),
author_agent_id=author.agent_id,
author_seniority=author.seniority,
author_is_human=author.is_human,
author_autonomy_level=author.autonomy_level,
)
await db.execute(
"UPDATE org_facts_snapshot "
"SET retracted_at = ?, version = ? "
"WHERE fact_id = ?",
(now.isoformat(), version, fact_id),
)
await db.commit()
except (
sqlite3.Error,
ValueError,
OrgMemoryQueryError,
) as exc:
with contextlib.suppress(sqlite3.Error):
await db.execute("ROLLBACK")
logger.exception(
ORG_MEMORY_WRITE_FAILED,
fact_id=fact_id,
error=str(exc),
)
msg = f"Failed to delete org fact: {exc}"
raise OrgMemoryWriteError(msg) from exc
else:
logger.info(
ORG_MEMORY_MVCC_RETRACT_APPENDED,
fact_id=fact_id,
version=version,
)
return True
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same cancellation gap applies to delete().

The except block doesn't catch asyncio.CancelledError, so a cancellation after BEGIN IMMEDIATE leaves the transaction open. Apply the same fix as suggested for save().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 596 - 651, The delete()
block opens a transaction with "BEGIN IMMEDIATE" but doesn't handle
asyncio.CancelledError, which can leave the transaction open; add an explicit
"except asyncio.CancelledError" handler before the existing except tuple that
calls the same rollback logic (use contextlib.suppress to await
db.execute("ROLLBACK")), then re-raise the CancelledError so cancellation
propagates; keep the existing exception handler for
sqlite3.Error/ValueError/OrgMemoryQueryError intact and reference the same
symbols (_append_to_operation_log, ORG_MEMORY_WRITE_FAILED, logger) when placing
the new handler.

…odel (Phase 1.5)

Replace SQLiteOrgFactStore internals with append-only operation log +
materialized snapshot tables (D26). All writes append to
org_facts_operation_log; a denormalized org_facts_snapshot table
maintains the current committed state for fast reads.

Key changes:
- Add operation_log and snapshot tables (replace old org_facts table)
- save() appends PUBLISH to log + upserts snapshot
- delete() appends RETRACT to log + marks snapshot retracted_at
- get()/query()/list_by_category() filter retracted_at IS NULL
- Add snapshot_at(timestamp) for point-in-time queries
- Add get_operation_log(fact_id) for audit trail
- Add autonomy_level to OrgFactAuthor
- Add tags to OrgFact and OrgFactWriteRequest
- Wire tags/autonomy_level through HybridPromptRetrievalBackend
- Add MVCC observability event constants
- Add OperationLogEntry and OperationLogSnapshot models
- 138 org memory tests (all passing)

Spec deviations (user-approved):
- No feature flag (pre-alpha, MVCC is the only implementation)
- Time-travel methods on OrgFactStore, not SharedKnowledgeStore
  (Mem0 backend is a separate protocol)

Closes #1130
- Add content-operation alignment validator to OperationLogEntry
  (PUBLISH requires content, RETRACT requires None)
- Add created_at <= retracted_at validator to OperationLogSnapshot
- Replace silent fallback in _tags_from_json with OrgMemoryQueryError
- Add explicit BEGIN IMMEDIATE transactions in save() and delete()
- Update memory-consistency.md: remove feature flag, clarify OrgFactStore
- Add MVCC event constants to CLAUDE.md logging inventory

Pre-reviewed by 6 agents, 7 findings addressed
…and local agents

Critical fixes:
- Add explicit ROLLBACK on save/delete transaction errors (Gemini, Copilot, CodeRabbit, local agents)
- Preserve original created_at on republish via ON CONFLICT DO UPDATE (Gemini, CodeRabbit, local agents)

Major fixes:
- delete() now accepts author param for accurate RETRACT audit trail (Gemini, Copilot, CodeRabbit)
- Add author_seniority, author_is_human, category to operation log schema (Gemini, CodeRabbit)
- Add category field to OperationLogEntry model (CodeRabbit)
- Add logging before raise in model validators and _tags_from_json (local agents)
- Strict tag validation -- reject non-string/blank entries instead of filtering (CodeRabbit, local agents)
- Add OrgMemoryQueryError to row-parse except tuples (Copilot)
- Remove broad except Exception in hybrid_backend (local agents)
- Fix delete() docstring (local agents)
- Add SQL comments on time-travel CTE query (local agents)
- Add composite index on operation log (timestamp, fact_id) (local agents)
- Update CLAUDE.md with all 19 org_memory event constants (local agents)
- Fix bare assert in test_hybrid_backend (local agents)
- Assert tuple directly in tag tests (CodeRabbit)
- Split store.py (1066 lines) into store.py (protocol, 175 lines) and
  sqlite_store.py (implementation, 903 lines)
- Add conftest.py with shared connected_store fixture, removing 25+
  try/finally blocks from test_mvcc_store.py and test_store.py
- Reconcile memory-consistency.md with OrgFactStore-only MVCC rollout
- Fix concurrent test: two SQLiteOrgFactStore on temp file + gather
- Fix snapshot boundary test: mock datetime for deterministic timestamps
- Normalize snapshot_at input timestamp to UTC before serialization
Add return type to connected_store fixture (AsyncGenerator),
SQLiteOrgFactStore type to all connected_store parameters,
Path type to tmp_path, and return type to _fake_now helper.
Fixes mypy no-untyped-def errors in test files.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (4)
src/synthorg/memory/org/models.py (1)

272-305: ⚠️ Potential issue | 🟠 Major

snapshot_at() still drops author provenance.

org_facts_snapshot stores author_agent_id, author_seniority, author_is_human, and author_autonomy_level, but OperationLogSnapshot has nowhere to return them. That makes time-travel reads lossy compared with get(), even though the design page now documents snapshot author info. Add the author fields here, or return OrgFact plus MVCC metadata, so historical reads preserve full fact state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/models.py` around lines 272 - 305,
OperationLogSnapshot currently omits author provenance fields saved in
org_facts_snapshot, causing snapshot_at() to return lossy historical facts;
update the OperationLogSnapshot model to include author_agent_id,
author_seniority, author_is_human, and author_autonomy_level (with appropriate
types and Field descriptions) so snapshot_at() can return full historical author
metadata, or alternatively change snapshot_at() to return a tuple/structure of
OrgFact plus MVCC metadata (including those four author fields) so time-travel
reads preserve complete fact state (refer to the OperationLogSnapshot class and
snapshot_at()/org_facts_snapshot usage to locate where to add/return these
fields).
src/synthorg/memory/org/sqlite_store.py (3)

50-65: ⚠️ Potential issue | 🟠 Major

Time-travel still cannot reproduce the stored created_at.

save() persists fact.created_at into org_facts_snapshot.created_at, but the append-only log never records that value. snapshot_at() therefore reconstructs created_at from MIN(timestamp), so any backfilled/imported fact with an explicit creation time will disagree between get() and snapshot_at() for the same version. Persist the fact creation timestamp in the log and replay from that column instead of operation time.

Also applies to: 510-550, 853-857

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 50 - 65, The operation
log schema and replay use operation timestamp instead of the fact's creation
time, causing created_at mismatches between save()/get() and snapshot_at();
update the org_facts_operation_log schema to add a created_at TEXT column,
modify the code path that appends to the log (the save() logic that inserts into
org_facts_operation_log) to persist fact.created_at into this new column, and
change snapshot_at() to reconstruct created_at from MIN(created_at) (and any
replay logic that derives created_at) rather than MIN(timestamp) so
backfilled/imported facts retain their original creation time.

208-208: ⚠️ Potential issue | 🟠 Major

Switch these handlers to the repo’s mandated multi-except form.

This file still uses tuple-style except (...) as exc, which does not match the project’s Python 3.14 rule and is likely to keep ruff failing. Convert the multi-except handlers here to the required comma-separated syntax everywhere in this module.

As per coding guidelines, "**/*.py: Use PEP 758 except syntax: except A, B: (no parentheses)—ruff enforces this on Python 3.14."

Also applies to: 255-255, 296-296, 346-350, 385-385, 631-635

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` at line 208, Replace tuple-style
exception handlers in this module (e.g. "except (KeyError, ValueError,
ValidationError, OrgMemoryQueryError) as exc") with the repo-mandated PEP 758
comma-separated form ("except KeyError, ValueError, ValidationError,
OrgMemoryQueryError as exc"); update every similar occurrence listed in the
review (the groups at the shown ranges) so they use the new syntax and keep the
same exception variable name (exc) and handler body unchanged.

494-564: ⚠️ Potential issue | 🟠 Major

Rollback these write transactions on cancellation too.

Both methods open BEGIN IMMEDIATE, but CancelledError bypasses the current handlers. If a task is cancelled after BEGIN and before COMMIT/ROLLBACK, the shared connection is left inside an open transaction and later calls can fail against poisoned state. Add an explicit except asyncio.CancelledError: in both methods that rolls back and re-raises.

Also applies to: 595-644

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/sqlite_store.py` around lines 494 - 564, The
transaction-start block that calls await db.execute("BEGIN IMMEDIATE") inside
the async with self._write_lock (the block that calls _append_to_operation_log
and performs the INSERT into org_facts_snapshot) currently only catches
sqlite3.Error; add an explicit except asyncio.CancelledError handler that
executes a rollback on the same db (await db.execute("ROLLBACK") guarded with
contextlib.suppress(sqlite3.Error) if desired) and then re-raises the
CancelledError to ensure the connection is not left in an open transaction; make
the same change to the other method with its own BEGIN IMMEDIATE/COMMIT/ROLLBACK
sequence (the second block around lines 595-644) so both transactional paths
roll back on cancellation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 688-736: The query method accepts a limit that can be negative
(SQLite treats -1 as no limit), so validate and clamp the limit at the method
boundary in query to enforce the intended 1..100 contract before building the
SQL; inside query (function name: query, table: org_facts_snapshot, param:
limit) check that limit is an int within the allowed range (e.g., if limit < 1
set to 1, if limit > 100 set to 100, or raise ValueError) and then use the
validated value when appending to params and building the LIMIT clause so an
external caller cannot bypass the cap.

In `@src/synthorg/memory/org/store.py`:
- Around line 110-115: The store.delete method signature was changed to require
the author parameter as keyword-only (async def delete(self, fact_id:
NotBlankStr, *, author: OrgFactAuthor)), which breaks callers that pass author
positionally; update all callers (e.g., the calls in composite adapter,
consolidation.service, consolidation.retention, consolidation.simple_strategy,
consolidation.dual_mode_strategy, and consolidation.llm_strategy) to call
delete(fact_id, author=some_author) instead of delete(fact_id, some_author),
ensuring the correct OrgFactAuthor value is passed as the author keyword; verify
each updated call site compiles and run tests to confirm no other positional
uses remain.

---

Duplicate comments:
In `@src/synthorg/memory/org/models.py`:
- Around line 272-305: OperationLogSnapshot currently omits author provenance
fields saved in org_facts_snapshot, causing snapshot_at() to return lossy
historical facts; update the OperationLogSnapshot model to include
author_agent_id, author_seniority, author_is_human, and author_autonomy_level
(with appropriate types and Field descriptions) so snapshot_at() can return full
historical author metadata, or alternatively change snapshot_at() to return a
tuple/structure of OrgFact plus MVCC metadata (including those four author
fields) so time-travel reads preserve complete fact state (refer to the
OperationLogSnapshot class and snapshot_at()/org_facts_snapshot usage to locate
where to add/return these fields).

In `@src/synthorg/memory/org/sqlite_store.py`:
- Around line 50-65: The operation log schema and replay use operation timestamp
instead of the fact's creation time, causing created_at mismatches between
save()/get() and snapshot_at(); update the org_facts_operation_log schema to add
a created_at TEXT column, modify the code path that appends to the log (the
save() logic that inserts into org_facts_operation_log) to persist
fact.created_at into this new column, and change snapshot_at() to reconstruct
created_at from MIN(created_at) (and any replay logic that derives created_at)
rather than MIN(timestamp) so backfilled/imported facts retain their original
creation time.
- Line 208: Replace tuple-style exception handlers in this module (e.g. "except
(KeyError, ValueError, ValidationError, OrgMemoryQueryError) as exc") with the
repo-mandated PEP 758 comma-separated form ("except KeyError, ValueError,
ValidationError, OrgMemoryQueryError as exc"); update every similar occurrence
listed in the review (the groups at the shown ranges) so they use the new syntax
and keep the same exception variable name (exc) and handler body unchanged.
- Around line 494-564: The transaction-start block that calls await
db.execute("BEGIN IMMEDIATE") inside the async with self._write_lock (the block
that calls _append_to_operation_log and performs the INSERT into
org_facts_snapshot) currently only catches sqlite3.Error; add an explicit except
asyncio.CancelledError handler that executes a rollback on the same db (await
db.execute("ROLLBACK") guarded with contextlib.suppress(sqlite3.Error) if
desired) and then re-raises the CancelledError to ensure the connection is not
left in an open transaction; make the same change to the other method with its
own BEGIN IMMEDIATE/COMMIT/ROLLBACK sequence (the second block around lines
595-644) so both transactional paths roll back on cancellation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 894269fc-af48-4534-a377-a236baec4b5b

📥 Commits

Reviewing files that changed from the base of the PR and between a249cc1 and 0ebd038.

📒 Files selected for processing (14)
  • CLAUDE.md
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_mvcc_store.py
  • tests/unit/memory/org/test_store.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test (Python 3.14)
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

No from __future__ import annotations—Python 3.14 has PEP 649

Use PEP 758 except syntax: except A, B: (no parentheses)—ruff enforces this on Python 3.14

Type hints: all public functions, mypy strict mode

Docstrings: Google style, required on public classes/functions (enforced by ruff D rules)

Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections, use copy.deepcopy() at construction and MappingProxyType wrapping

Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models for runtime state

Models: Pydantic v2 using BaseModel, model_validator, computed_field, ConfigDict. Use allow_inf_nan=False in all ConfigDict declarations

Use NotBlankStr (from core.types) for all identifier/name fields instead of manual whitespace validators

Async concurrency: prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code

Line length: 88 characters (ruff)

Functions: < 50 lines, files < 800 lines

Handle errors explicitly, never silently swallow exceptions

Validate at system boundaries (user input, external APIs, config files)

Always use logger as the variable name (not _logger, not log)

Use event name constants from synthorg.observability.events domain modules instead of string literals

Always use structured kwargs for logging: logger.info(EVENT, key=value) instead of logger.info('msg %s', val)

All error paths must log at WARNING or ERROR with context before raising

All state transitions must log at INFO

DEBUG logging for object creation, internal flow, entry/exit of key functions

For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number)

Pre-commit hooks: ruff check+format for Python files

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
src/synthorg/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(__name__)

Vendor-agnostic everywhere: NEVER use real vendor names in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
src/synthorg/**/!(setup|sinks|syslog_handler|http_handler).py

📄 CodeRabbit inference engine (CLAUDE.md)

Never use import logging / logging.getLogger() / print() in application code (exception: observability handler modules)

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
src/**/*.py

⚙️ CodeRabbit configuration file

This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.

Files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
tests/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Markers: @pytest.mark.unit, @pytest.mark.integration, @pytest.mark.e2e, @pytest.mark.slow

Prefer @pytest.mark.parametrize for testing similar cases

Tests must use test-provider, test-small-001 instead of real vendor names

Property-based testing: use Hypothesis (@given + @settings) with profiles configured in tests/conftest.py

When Hypothesis finds a failure, fix the underlying bug and add an @example(...) decorator to permanently cover the case in CI

NEVER skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally

For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py

⚙️ CodeRabbit configuration file

Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare @settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which @given() honors automatically.

Files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • tests/unit/memory/org/test_models.py
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
🧠 Learnings (72)
📓 Common learnings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: When implementation deviates from the design spec, alert the user and explain why before proceeding
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Every implementation plan must be presented to the user for accept/deny before coding starts
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: At every phase of planning and implementation, actively look for ways to improve the design and surface improvements as suggestions
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Prioritize issues by dependency order, not priority labels—unblocked dependencies come first
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Coverage: 80% minimum (enforced in CI)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Async tests: `asyncio_mode = 'auto'` in pytest—no manual `pytest.mark.asyncio` needed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Timeout: 30 seconds per test (global in `pyproject.toml`)—non-default overrides like `timeout(60)` are allowed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: ALWAYS include `-n 8` when running pytest locally, never run tests sequentially
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Git commits: `<type>: <description>`—types: feat, fix, refactor, docs, test, chore, perf, ci (enforced by commitizen)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Signed commits: required on `main` via branch protection—all commits must be GPG/SSH signed
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Branches: `<type>/<slug>` from main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Pre-push hooks: mypy type-check (affected modules only) and pytest unit tests (affected modules only)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Use `gh issue list` via Bash for GitHub issue queries—MCP `list_issues` has unreliable field data
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Merge strategy: squash merge—PR body becomes the squash commit message on main
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: PR issue references: preserve existing `Closes `#NNN`` references—never remove unless explicitly asked
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: After finishing an issue implementation: create a feature branch (`<type>/<slug>`), commit, and push—do NOT create a PR automatically
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Do NOT leave work uncommitted on main—branch, commit, push immediately after finishing
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: NEVER create a PR directly—ALWAYS use `/pre-pr-review` (blocks `gh pr create` via hookify)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Fix everything valid from review agents—never skip valid issues, suggestions, or adjacent findings
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: NEVER use `cd` in Bash commands—use absolute paths or run commands directly
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: Exception to no-cd rule: `bash -c "cd <dir> && <cmd>"` is safe (runs in child process)
Learnt from: CR
URL: 
File: CLAUDE.md:undefined-undefined
Timestamp: 2026-04-08T15:04:25.180Z
Learning: NEVER use Bash to write or modify files—use the Write or Edit tools. Do not use `cat >`, `echo >`, `sed -i`, or `tee` to create/modify files
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.

Applied to files:

  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.

Applied to files:

  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-04-02T12:07:44.443Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T12:07:44.443Z
Learning: Applies to src/synthorg/**/*.py : Always use structured logging: `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings

Applied to files:

  • CLAUDE.md
  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic MUST have: `from synthorg.observability import get_logger` then `logger = get_logger(__name__)`. Never use `import logging` / `logging.getLogger()` / `print()` in application code. Variable name: always `logger` (not `_logger`, not `log`).

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Use structured logging: always `logger.info(EVENT, key=value)` — never `logger.info("msg %s", val)`

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit

Applied to files:

  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Structured kwargs in logging: always `logger.info(EVENT, key=value)` — never `logger.info('msg %s', val)`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions

Applied to files:

  • CLAUDE.md
  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/memory/**/*.py : Memory package (memory/): pluggable MemoryBackend protocol, backends/ (Mem0 adapter), retrieval pipeline (ranking, RRF fusion, injection, formatting, non-inferable filtering), shared org memory (org/), consolidation/archival (density-aware: DensityClassifier, AbstractiveSummarizer, ExtractivePreserver, DualModeConsolidationStrategy)

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/observability/events/org_memory.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_store.py
  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
  • src/synthorg/memory/org/models.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to src/synthorg/**/*.py : Log at WARNING or ERROR with context before raising exceptions in all error paths in Python.

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/memory/**/*.py : Use MemoryBackend protocol with pluggable backends (Mem0 adapter available at backends/mem0/) for persistent agent memory

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Log all error paths at WARNING or ERROR with context before raising; log all state transitions at INFO; log object creation/internal flow/entry-exit at DEBUG

Applied to files:

  • src/synthorg/memory/org/hybrid_backend.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/persistence/**/*.py : Persistence uses pluggable PersistenceBackend protocol. SQLite is the initial backend. Settings use SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
  • tests/unit/memory/org/test_store.py
  • src/synthorg/memory/org/sqlite_store.py
  • src/synthorg/memory/org/store.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Persistence backend: pluggable PersistenceBackend protocol in `src/synthorg/persistence/`, SQLite initial, SettingsRepository (namespaced settings CRUD).

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • docs/design/memory-consistency.md
  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...

Applied to files:

  • tests/unit/memory/org/test_factory.py
  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/__init__.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves

Applied to files:

  • tests/unit/memory/org/test_hybrid_backend.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import `from synthorg.observability import get_logger` and define `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Every module with business logic must import logger via `from synthorg.observability import get_logger` and initialize with `logger = get_logger(__name__)`

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : All state transitions must log at INFO.

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-04-01T12:36:58.294Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T12:36:58.294Z
Learning: Applies to src/synthorg/**/*.py : Pure data models, enums, and re-exports do NOT need logging

Applied to files:

  • src/synthorg/memory/org/__init__.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/observability/**/*.py : Observability package (observability/): structured logging, correlation tracking, log sinks; event constants organized by domain under observability/events/ (e.g., events.api, events.tool, events.git, events.context_budget, events.backup)

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to src/synthorg/**/*.py : Always use constants from domain-specific modules under `synthorg.observability.events` for event names in Python (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`).

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly from the domain module

Applied to files:

  • src/synthorg/observability/events/org_memory.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to **/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields in Python (including optional and tuple variants) instead of manual whitespace validators.

Applied to files:

  • tests/unit/memory/org/test_models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`

Applied to files:

  • tests/unit/memory/org/test_models.py
  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to docs/design/**/*.md : Design specification pages in `docs/design/` must be consulted before implementing features (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue — DESIGN_SPEC.md is a pointer file linking to 7 design pages (Agents, Organization, Communication, Engine, Memory, Operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-30T10:41:40.176Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T10:41:40.176Z
Learning: Update the relevant `docs/design/` page to reflect new reality when approved deviations from spec occur

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: When implementation deviates from the design spec, alert the user and explain why before proceeding. Update the relevant `docs/design/` page after approved deviations.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docs/design/*.md : Design spec pages: 7 pages in `docs/design/` — index, agents, organization, communication, engine, memory, operations

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue. DESIGN_SPEC.md is a pointer file linking to the 7 design pages (index, agents, organization, communication, engine, memory, operations).

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Applies to docs/design/*.md : Update the relevant `docs/design/` page when approved deviations occur to reflect the new reality

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-18T08:23:08.912Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T08:23:08.912Z
Learning: When approved deviations occur, update the relevant `docs/design/` page to reflect the new reality.

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to docs/** : Docs source in docs/ (Markdown, built with Zensical); design spec in docs/design/ (7 pages: index, agents, organization, communication, engine, memory, operations)

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-31T14:31:11.894Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T14:31:11.894Z
Learning: Always read the relevant `docs/design/` page before implementing any feature or planning any issue

Applied to files:

  • docs/design/memory-consistency.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/conftest.py
  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to tests/**/*.py : For timing-sensitive Python tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins. For tasks that must block indefinitely until cancelled, use `asyncio.Event().wait()` instead of `asyncio.sleep(large_number)`.

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally; mock `time.monotonic()` and `asyncio.sleep()` for determinism; use `asyncio.Event().wait()` for indefinite blocking instead of `asyncio.sleep(large_number)`

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests; fix them fully and fundamentally by mocking `time.monotonic()` and `asyncio.sleep()` for determinism or using `asyncio.Event().wait()` for indefinite blocking

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to tests/**/*.py : Flaky tests: NEVER skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins

Applied to files:

  • tests/unit/memory/org/test_mvcc_store.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity. Use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.

Applied to files:

  • src/synthorg/memory/org/models.py
📚 Learning: 2026-03-31T21:07:37.470Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.470Z
Learning: Applies to **/*.py : Use `except A, B:` (no parentheses) per PEP 758 exception syntax on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (without parentheses) per PEP 758 for exception handling in Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Use `except A, B:` syntax (no parentheses) for exception handling — PEP 758 exception syntax enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: `except A, B:` (no parentheses) — enforced by ruff on Python 3.14

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-04-08T15:03:47.761Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-08T15:03:47.761Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) in Python 3.14 code. Ruff enforces this.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Use PEP 758 except syntax with `except A, B:` (no parentheses) for multiple exceptions—ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to **/*.py : Use PEP 758 except syntax: use `except A, B:` (no parentheses) — ruff enforces this on Python 3.14.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions

Applied to files:

  • src/synthorg/memory/org/sqlite_store.py
🔇 Additional comments (15)
tests/unit/memory/org/conftest.py (1)

1-47: LGTM!

Well-structured test fixtures module. The connected_store fixture properly handles the async lifecycle with connect()/disconnect(), and the shared authors and factory function reduce duplication across tests.

tests/unit/memory/org/test_factory.py (1)

9-9: LGTM!

Import path correctly updated to reflect the module reorganization where SQLiteOrgFactStore moved to sqlite_store.py while the OrgFactStore protocol remains in store.py.

CLAUDE.md (1)

93-93: LGTM!

Event names documentation correctly extended with the four new MVCC-related constants, properly grouped with other ORG_MEMORY_* constants.

src/synthorg/memory/org/hybrid_backend.py (1)

220-220: LGTM!

Tags are correctly forwarded from the write request to the constructed OrgFact, maintaining type compatibility (tuple[NotBlankStr, ...] on both sides).

src/synthorg/memory/org/__init__.py (1)

24-34: LGTM!

Module re-exports correctly updated: OperationLogEntry and OperationLogSnapshot imported from models, SQLiteOrgFactStore now from sqlite_store, and OrgFactStore protocol from store. The __all__ list is properly maintained.

tests/unit/memory/org/test_hybrid_backend.py (4)

21-21: LGTM!

Import path correctly updated to match the module reorganization.


167-169: LGTM!

Strengthened assertions verify both type and non-emptiness of the returned fact ID.


208-223: LGTM!

Good test coverage for tag persistence through the hybrid backend's write path. The test verifies tags are round-tripped correctly from OrgFactWriteRequest through storage and back via query.


237-259: LGTM!

Test correctly updated to reflect the new error handling behavior where non-OrgMemoryWriteError exceptions propagate unwrapped rather than being wrapped.

src/synthorg/observability/events/org_memory.py (1)

40-46: LGTM!

Well-structured MVCC event constants following the org_memory.mvcc.<action> naming convention. The section header with Phase 1.5/D26 reference provides good traceability to the design decision.

tests/unit/memory/org/test_models.py (5)

8-20: LGTM!

Imports correctly updated for new enums and MVCC models.


46-67: LGTM!

Good coverage for the new autonomy_level field: verifies it's accepted for agent authors and correctly rejected for human authors with a clear error message.


108-154: LGTM!

Comprehensive tag validation tests covering the happy path and blank/whitespace rejection for both OrgFact and OrgFactWriteRequest. The parametrized approach keeps the tests concise.


228-310: LGTM!

Thorough OperationLogEntry validation tests covering PUBLISH/RETRACT operation types, version constraints, content/operation alignment rules, and immutability.


312-365: LGTM!

Good OperationLogSnapshot validation coverage including active/retracted states, version constraints, and the created_at <= retracted_at temporal invariant.

Comment on lines +110 to +115
async def delete(
self,
fact_id: NotBlankStr,
*,
author: OrgFactAuthor,
) -> bool:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check the delete() method signature at lines 110-115
head -115 src/synthorg/memory/org/store.py | tail -10

Repository: Aureliolo/synthorg

Length of output: 262


🏁 Script executed:

# Also get broader context around those lines
sed -n '100,125p' src/synthorg/memory/org/store.py

Repository: Aureliolo/synthorg

Length of output: 727


🏁 Script executed:

# Check the actual delete() calls in the mentioned files
echo "=== adapter.py:315-324 ===" && sed -n '315,324p' src/synthorg/memory/backends/composite/adapter.py
echo -e "\n=== service.py:170-177 ===" && sed -n '170,177p' src/synthorg/memory/consolidation/service.py
echo -e "\n=== retention.py:227-232 ===" && sed -n '227,232p' src/synthorg/memory/consolidation/retention.py
echo -e "\n=== simple_strategy.py:110-120 ===" && sed -n '110,120p' src/synthorg/memory/consolidation/simple_strategy.py

Repository: Aureliolo/synthorg

Length of output: 1529


🏁 Script executed:

# Search for all delete() method calls in the codebase to find patterns
rg "\.delete\(" -A 2 -B 1 --type py src/synthorg/memory/

Repository: Aureliolo/synthorg

Length of output: 2263


This API break has multiple unported callers that will fail at runtime.

delete() now requires keyword-only author parameter, but six callers pass only 2 positional arguments: src/synthorg/memory/backends/composite/adapter.py:315-324, src/synthorg/memory/consolidation/service.py:170-177, src/synthorg/memory/consolidation/retention.py:227-232, src/synthorg/memory/consolidation/simple_strategy.py:110-120, src/synthorg/memory/consolidation/dual_mode_strategy.py, and src/synthorg/memory/consolidation/llm_strategy.py. All will raise TypeError on execution unless updated to pass author as a keyword argument.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/synthorg/memory/org/store.py` around lines 110 - 115, The store.delete
method signature was changed to require the author parameter as keyword-only
(async def delete(self, fact_id: NotBlankStr, *, author: OrgFactAuthor)), which
breaks callers that pass author positionally; update all callers (e.g., the
calls in composite adapter, consolidation.service, consolidation.retention,
consolidation.simple_strategy, consolidation.dual_mode_strategy, and
consolidation.llm_strategy) to call delete(fact_id, author=some_author) instead
of delete(fact_id, some_author), ensuring the correct OrgFactAuthor value is
passed as the author keyword; verify each updated call site compiles and run
tests to confirm no other positional uses remain.

@Aureliolo Aureliolo force-pushed the feat/mvcc-org-memory branch from 0ebd038 to f1c7733 Compare April 8, 2026 15:23
@Aureliolo Aureliolo merged commit 965d3a1 into main Apr 8, 2026
6 of 7 checks passed
@Aureliolo Aureliolo deleted the feat/mvcc-org-memory branch April 8, 2026 15:23
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview April 8, 2026 15:25 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Preview Deployment

URL: https://pr-1134.synthorg-pr-preview.pages.dev

Built from commit f1c7733

Aureliolo added a commit that referenced this pull request Apr 9, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.6.5](v0.6.4...v0.6.5)
(2026-04-09)


### Features

* add control-plane API endpoints batch
([#1118](#1118),
[#1119](#1119),
[#1120](#1120),
[#1121](#1121))
([#1138](#1138))
([af11f0a](af11f0a))
* engine intelligence v2 -- trace enrichment, compaction, versioning
eval ([#1139](#1139))
([ed57dfa](ed57dfa)),
closes [#1123](#1123)
[#1125](#1125)
[#1113](#1113)
* generalize versioning to VersionSnapshot[T] for all entity types
([#1155](#1155))
([5f563ce](5f563ce)),
closes [#1131](#1131)
[#1132](#1132)
[#1133](#1133)
* implement auxiliary tool categories -- design, communication,
analytics ([#1152](#1152))
([b506ba4](b506ba4))
* implement multi-project support -- engine orchestration
([#242](#242))
([#1153](#1153))
([74f1362](74f1362))
* implement SharedKnowledgeStore append-only + MVCC consistency model
(Phase 1.5) ([#1134](#1134))
([965d3a1](965d3a1)),
closes [#1130](#1130)
* implement shutdown strategies and SUSPENDED task status
([#1151](#1151))
([6a0db11](6a0db11))
* persistent cost aggregation for project-lifetime budgets
([#1173](#1173))
([5c212c5](5c212c5)),
closes [#1156](#1156)
* Prometheus /metrics endpoint and OTLP exporter
([#1122](#1122))
([#1135](#1135))
([aaeaae9](aaeaae9)),
closes [#1124](#1124)
* Prometheus metrics -- daily budget %, per-agent cost, per-agent budget
% ([#1154](#1154))
([581c494](581c494)),
closes [#1148](#1148)


### Bug Fixes

* communication hardening -- meeting cooldown, circuit breaker backoff,
debate fallback
([#1140](#1140))
([fe82894](fe82894)),
closes [#1115](#1115)
[#1116](#1116)
[#1117](#1117)


### CI/CD

* bump wrangler from 4.80.0 to 4.81.0 in /.github in the all group
([#1144](#1144))
([b7c0945](b7c0945))


### Maintenance

* bump python from `6869258` to `5e59aae` in /docker/backend in the all
group ([#1141](#1141))
([01e99c2](01e99c2))
* bump python from `6869258` to `5e59aae` in /docker/sandbox in the all
group ([#1143](#1143))
([ea755bd](ea755bd))
* bump python from `6869258` to `5e59aae` in /docker/web in the all
group ([#1142](#1142))
([5416dd9](5416dd9))
* bump the all group across 1 directory with 2 updates
([#1181](#1181))
([d3d5adf](d3d5adf))
* bump the all group across 1 directory with 3 updates
([#1146](#1146))
([c609e6c](c609e6c))
* bump the all group in /cli with 2 updates
([#1177](#1177))
([afd9cde](afd9cde))
* bump the all group in /site with 3 updates
([#1178](#1178))
([7cff82a](7cff82a))
* bump the all group with 2 updates
([#1180](#1180))
([199a1a8](199a1a8))
* bump vitest from 4.1.2 to 4.1.3 in /site in the all group
([#1145](#1145))
([a8c1194](a8c1194))
* consolidated web deps (11 packages + hono security + test fixes)
([#1150](#1150))
([63a9390](63a9390)),
closes [#1147](#1147)
[#1136](#1136)
[#1137](#1137)
* pin Docker Python base image to 3.14.x
([#1182](#1182))
([8ffdd86](8ffdd86))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: implement SharedKnowledgeStore append-only + MVCC consistency model (Phase 1.5)

2 participants