Skip to content

feat(sync): retire hook-based cloud sync now that write-through is live (day 4 of #194)#201

Merged
Gradata merged 1 commit into
mainfrom
feat/sync-cleanup-day4-fresh
May 17, 2026
Merged

feat(sync): retire hook-based cloud sync now that write-through is live (day 4 of #194)#201
Gradata merged 1 commit into
mainfrom
feat/sync-cleanup-day4-fresh

Conversation

@Gradata
Copy link
Copy Markdown
Owner

@Gradata Gradata commented May 17, 2026

Day 4 of #194 — gate session_close legacy cloud sync behind GRADATA_DISABLE_WRITE_THROUGH=1.

What

session_close._run_cloud_sync now skips cloud_sync_tick by default. With #200 merged, Brain.correct() write-through covers every correction — running the hook tick on top of that was double-writing.

Lesson graduation, pipeline, tree consolidation, lesson_applications resolution still run as before. Only the cloud sync line is gated.

Why

Pre-#194: cloud sync only fired from session_close hook → fragile + missed long-running agent sessions.
Post-#200: Brain.correct() write-through → every correction syncs immediately, no hooks required.
Day 4: stop the hook from also syncing the same data.

Opt-out

GRADATA_DISABLE_WRITE_THROUGH=1 restores legacy behavior. The two paths are mutually exclusive.

Untouched

  • daemon /sync (dashboard 'Sync Now' button)
  • /api/v1/sync bulk endpoint (used by Brain.close() drain + legacy opt-out)
  • /home/olive/.gradata/sync_cron.py (safety-net cron, keep for 7-day soak)

Tests

tests/test_session_close_write_through_gate.py × 3: default skip, opt-out invoke, no-api-key skip.

Follow-up

After 7 days of clean write-through telemetry, file follow-up PR to remove cron + /api/v1/sync legacy code.

Depends on

#200 (still draft, write-through wire-up). Mark this Ready for Review after #200 merges.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Review Change Stack

📝 Walkthrough

Summary

  • Gate legacy hook-based cloud sync in session_close._run_cloud_sync() behind GRADATA_DISABLE_WRITE_THROUGH environment variable; skipped by default when write-through is active
  • Cloud sync now only runs when explicitly opted into via GRADATA_DISABLE_WRITE_THROUGH=1, preventing double-writes after PR #200's write-through implementation
  • Lesson graduation, pipeline, tree consolidation, and lesson_applications resolution remain unaffected in session_close hook
  • Return early if GRADATA_API_KEY is unset, regardless of disable flag
  • Add new test module test_session_close_write_through_gate.py covering three scenarios: default skip, opt-out invoke, and no-api-key skip
  • Daemon /sync endpoint, /api/v1/sync bulk endpoint, and cron job remain unchanged pending 7-day telemetry soak
  • Opt-out flag provides backward-compatibility path for users not ready for write-through behavior

Walkthrough

This PR adds a feature gate to control legacy cloud sync behavior in the session close hook. The _run_cloud_sync function now respects the GRADATA_DISABLE_WRITE_THROUGH environment variable: when unset or not "1", it logs and skips the cloud sync tick; when "1", it proceeds with the legacy behavior. Three tests validate all scenarios.

Changes

Write-through sync feature gate

Layer / File(s) Summary
Feature gate implementation and test coverage
src/gradata/hooks/session_close.py, tests/test_session_close_write_through_gate.py
_run_cloud_sync checks GRADATA_DISABLE_WRITE_THROUGH and conditionally skips the legacy cloud_sync_tick call. Tests verify the gating logic with various environment variable states and API key presence.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Suggested labels

feature

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: gating the hook-based cloud sync behind a flag to prevent double-writing now that write-through is live.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, providing clear context about what changed, why it changed, and how to opt out.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sync-cleanup-day4-fresh

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.20.0)

OpenGrep fatal error (exit code 2):
┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m
[00.20][ERROR]: Error: exception Glob.Lexer.Syntax_error("malformed glob pattern: missing ']'")
Raised at Glob__Lexer.syntax_error in file "libs/glob/Lexer.mll", line 8, characters 2-26
Called from Glob__Lexer.__ocaml_lex_token_rec in file "libs/glob/Lexer.mll", line 29, characters 26-53
Cal


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

…ve (day 4 of #194)

Brain.correct() write-through (PR #200) is now the default cloud sync path. The session_close hook's legacy cloud_sync_tick was double-writing every correction.

Default behavior: session_close skips cloud_sync_tick. Lesson graduation, pipeline, tree consolidation, lesson_applications resolution all still run as before — only the cloud sync portion is gated.

Opt-out: GRADATA_DISABLE_WRITE_THROUGH=1 restores the legacy hook-based cloud sync path AND disables write-through. The two paths are mutually exclusive: only one ever runs.

Untouched:
- daemon /sync (dashboard 'Sync Now' button)
- /api/v1/sync (bulk endpoint, used by Brain.close() drain)
- /home/olive/.gradata/sync_cron.py (safety-net cron, keep for 7-day soak)

Follow-up: after 7 days of clean write-through telemetry in cloud, the cron and legacy /api/v1/sync code paths can be removed in a separate PR.
@Gradata Gradata marked this pull request as ready for review May 17, 2026 00:28
@Gradata Gradata force-pushed the feat/sync-cleanup-day4-fresh branch from a48eb2d to 8ce46a7 Compare May 17, 2026 00:28
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai coderabbitai Bot added the feature label May 17, 2026
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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Gradata/tests/test_session_close_write_through_gate.py`:
- Around line 21-24: Remove the redundant tuple expression wrapping
mock_tick.assert_not_called() in the test; replace the tuple
(mock_tick.assert_not_called(), ("session_close must NOT call cloud_sync_tick
when write-through is on")) with a direct assertion call and, if a message is
desired, use the testing framework's assertion API (i.e., call
mock_tick.assert_not_called() directly or use an explicit assert with the
message) so the test invokes mock_tick.assert_not_called() rather than creating
a no-op tuple; locate the call near the session_close test referencing mock_tick
and cloud_sync_tick.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb489615-3956-47bb-b065-284a611a7b5a

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9a1e4 and 8ce46a7.

📒 Files selected for processing (2)
  • Gradata/src/gradata/hooks/session_close.py
  • Gradata/tests/test_session_close_write_through_gate.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). (8)
  • GitHub Check: pytest macos-latest / py3.11
  • GitHub Check: pytest ubuntu-latest / py3.11
  • GitHub Check: pytest windows-latest / py3.11
  • GitHub Check: pytest windows-latest / py3.12
  • GitHub Check: pytest macos-latest / py3.12
  • GitHub Check: pytest ubuntu-latest / py3.12
  • GitHub Check: pytest (py3.12)
  • GitHub Check: pytest (py3.11)
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/tests/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/tests/**/*.py: Set BRAIN_DIR environment variable via tmp_path in conftest.py for test isolation — ensure _paths.py module cache refreshes when calling Brain.init() directly inside tests
Add unit tests in tests/test_*.py for every CI push without LLM calls (deterministic); mark integration tests with @pytest.mark.integration and skip them by default (they hit real LLM APIs)

Files:

  • Gradata/tests/test_session_close_write_through_gate.py
Gradata/src/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/src/**/*.py: Prefer sentence-transformers for local embeddings, google-genai for Gemini embeddings, cryptography for AES-GCM encrypted system.db, bm25s for BM25 rule ranking, and mem0ai for external memory adapters — guard all optional dependency imports with try / except ImportError at the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bare except: pass — use typed exceptions or at minimum logger.warning(...) with exc_info=True to avoid silent failure in a memory product
Never import from out-of-scope sibling directories ../Sprites/ or ../Hausgem/ within gradata/* code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to ../Sprites/, ../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from inside gradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes

Files:

  • Gradata/src/gradata/hooks/session_close.py
🔇 Additional comments (2)
Gradata/src/gradata/hooks/session_close.py (1)

281-285: LGTM!

Also applies to: 289-295

Gradata/tests/test_session_close_write_through_gate.py (1)

1-20: LGTM!

Also applies to: 27-43

Comment on lines +21 to +24
(
mock_tick.assert_not_called(),
("session_close must NOT call cloud_sync_tick when write-through is on"),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import ast
from pathlib import Path

p = Path("Gradata/tests/test_session_close_write_through_gate.py")
tree = ast.parse(p.read_text(encoding="utf-8"))
for node in ast.walk(tree):
    if isinstance(node, ast.Expr) and isinstance(node.value, ast.Tuple):
        print(f"{p}:{node.lineno}:{node.col_offset} tuple-expression statement")
PY

Repository: Gradata/gradata

Length of output: 146


Remove the no-op tuple wrapper around assert_not_called().

This tuple expression serves no purpose and obfuscates the assertion.

Suggested diff
-    (
-        mock_tick.assert_not_called(),
-        ("session_close must NOT call cloud_sync_tick when write-through is on"),
-    )
+    mock_tick.assert_not_called()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/tests/test_session_close_write_through_gate.py` around lines 21 - 24,
Remove the redundant tuple expression wrapping mock_tick.assert_not_called() in
the test; replace the tuple (mock_tick.assert_not_called(), ("session_close must
NOT call cloud_sync_tick when write-through is on")) with a direct assertion
call and, if a message is desired, use the testing framework's assertion API
(i.e., call mock_tick.assert_not_called() directly or use an explicit assert
with the message) so the test invokes mock_tick.assert_not_called() rather than
creating a no-op tuple; locate the call near the session_close test referencing
mock_tick and cloud_sync_tick.

@Gradata Gradata merged commit 3457457 into main May 17, 2026
9 checks passed
@Gradata Gradata deleted the feat/sync-cleanup-day4-fresh branch May 17, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant