Skip to content

feat(memory-sync): wake Composio periodic loop on resume + gate on per-source toggle (#2831) - #3417

Merged
senamakel merged 4 commits into
tinyhumansai:mainfrom
sanil-23:feat/2831-composio-autosync-wake
Jun 7, 2026
Merged

senamakel merged 4 commits into
tinyhumansai:mainfrom
sanil-23:feat/2831-composio-autosync-wake

Conversation

@sanil-23

@sanil-23 sanil-23 commented Jun 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a process-wide resume Notify to scheduler_gate::gate, fired when the gate transitions out of Policy::Paused — Memory Tree toggled back on (update_config) or sign-in (set_signed_out true→false).
  • composio::periodic::run_loop now select!s over the 20-min ticker and the resume notify, so background sync restarts within seconds instead of at the next ≤20-min boundary. On wake it runs a tick immediately and reset()s the ticker so rapid toggles don't bunch-fire.
  • Gates the periodic loop on the per-source enabled toggle: a source switched off now stops syncing in the background (matching the manual sync paths). No registry row yet → sync with conservative per-toolkit defaults (never uncapped, never skipped).

Original issue request (#2831)

feat(memory-sync): wake Composio periodic loop early via Notify when Memory Tree toggled back on — click to expand the original ask

Summary — Follow-up to #2825. The gate-check pause leaves up to 20 min worst-case resume latency when the user toggles Memory Tree back on: the loop's tokio::time::interval keeps the original cadence, and each paused tick is a fast Ok(()) no-op. Matters for onboarding, dev iteration, and sign-out recovery.

Problem — periodic.rs uses tokio::time::interval(1200s) gated via scheduler_gate::current_policy(). When un-paused, the next scheduled tick is up to 20 min away.

Proposed shape

  1. Expose a per-process tokio::sync::Notify from scheduler_gate::gate that fires on transitions out of Policy::Paused.
  2. Replace the loop's ticker.tick().await with tokio::select! { _ = ticker.tick() => …, _ = resumed_notify.notified() => … }.
  3. After a Notify wake, run one tick immediately and reset the interval so rapid toggles don't bunch-fire.

Acceptance criteria

  • Off → on fires a periodic tick within seconds (test under tokio's pause() clock).
  • Rapid toggles (off-on-off-on within 1 s) don't bunch-fire — only the final "on" triggers a tick.
  • The existing gate-check path in run_one_tick still short-circuits cleanly when paused.
  • Sign-in transition (Paused { SignedOut } → Normal) also wakes the loop.

How this PR maps to the acceptance criteria:

Acceptance criterion Where addressed
Off→on tick within seconds scheduler_gate::gate::resume_notify() fired from update_config on un-pause; run_loop select!s on it and runs a tick immediately
Rapid toggles don't bunch-fire ticker.reset() on the wake arm + notify_one()'s single stored permit collapse repeated wakes to one tick
Paused short-circuit preserved periodic_pause_reason() Step-0 check unchanged; woken ticks re-check it and no-op if still paused
Sign-in also wakes the loop set_signed_out(true→false) fires resume_notify()

Problem

#2825 paused the periodic Composio loop via a scheduler-gate check, but resume latency was up to 20 min: the tokio::time::interval kept its cadence and each paused tick was a fast no-op, so re-enabling Memory Tree (or signing back in) didn't sync until the next boundary. Painful for onboarding, dev iteration, and re-login recovery (the explicit follow-up #2825 asked for).

Separately, while tracing the loop: the periodic scheduler gated on the Composio connection status (is_active()) and consulted the memory_sources registry only for caps (.unwrap_or((None, None))). A source the user toggled off therefore kept syncing in the background — and uncapped (None = uncapped). The manual paths (memory_sources::sync_source, memory_sources_sync_all) already early-return on !enabled; the periodic loop was the only sync entry point that ignored the toggle.

Solution

  • Resume signal (scheduler_gate/gate.rs): new RESUME_NOTIFY singleton + resume_notify(). update_config fires notify_one() on a paused→running transition; set_signed_out fires on true→false. Over-notifying is safe by design — the woken tick re-checks current_policy() and no-ops if still effectively paused, so we avoid computing the precise combined (config × signed-out) edge.
  • Early-wake loop (composio/periodic.rs): tokio::select! over ticker.tick() and resume.notified(); ticker.reset() on the wake arm. notify_one's single stored permit + the reset bound rapid toggles to one extra tick.
  • Per-source gate: fetch all Composio sources once per tick (list_sources, indexed by connection id) and resolve via a pure decide_periodic_source helper — disabled → skip, enabled → its caps, no-row → memory_sync_defaults_for_toolkit (bounded). The no-match path defaults to sync-bounded, never skip, so a missing/mismatched row degrades safely instead of going dark.

Submission Checklist

  • Tests added or updated (happy path + edge case) — decide_periodic_source (disabled-skip / enabled-caps / default-caps), resume_notify singleton + parked-waiter wake.
  • Diff coverage ≥ 80% — measured locally via cargo-llvm-cov + diff-cover against upstream/main: 85% total (gate.rs 100%, periodic.rs 73.9%). Passes the gate. periodic.rs misses are the run_loop select! + the connection-loop body (only reachable with a live Composio client / the infinite loop); the decision logic they wrap is unit-tested.
  • Coverage matrix updated — TODO: confirm whether this behaviour-only change needs a row in docs/TEST-COVERAGE-MATRIX.md (likely N/A: behaviour-only change).
  • No new external network dependencies introduced.
  • Manual smoke checklist — N/A: behaviour-only core background-sync change, not a release-cut surface.
  • Linked issue closed via Closes #NNN.

Impact

  • Desktop/core only. No RPC/UI/schema changes.
  • Behaviour change: a source explicitly toggled off stops background-syncing (matches the toggle's meaning and the manual paths). Silent — no migration/notice.
  • Background sync resumes ~seconds after re-enabling autosync / sign-in instead of up to 20 min.

Validation status

Automated: cargo-llvm-cov + diff-cover → 85% diff coverage on changed lines (≥80% gate passes; gate.rs 100%, periodic.rs 73.9%). The run_loop select! arm and the per-connection loop body are integration-level (live Composio client / infinite loop) and not reachable from lib unit tests; the decision logic they wrap (decide_periodic_source, effective_interval_secs, connection_is_due) is unit-tested, including a multi-account regression test.

Manual smoke (not run — deferred): behaviours to verify on a live signed-in instance — Memory Tree off→on resumes a Composio tick within seconds (not at the 20-min boundary); rapid off-on-off-on doesn't bunch-fire; sign-out→in wakes the loop; a single source toggled off stops background sync while others continue (manual sync still works).

Related


AI Authored PR Metadata

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/2831-composio-autosync-wake
  • Commit SHA: 7972889

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no app/ changes)
  • pnpm typecheck — N/A (no app/ changes)
  • Focused tests: cargo test --lib periodic + scheduler_gate modules (22 + 7 pass)
  • Rust fmt/check (if changed): cargo fmt --check clean; cargo check --lib + --tests clean
  • Tauri fmt/check (if changed): N/A (no shell changes)

Behavior Changes

  • Intended behavior change: background Composio sync resumes immediately on autosync-on/sign-in; disabled sources no longer sync in the background.
  • User-visible effect: faster sync after re-enabling; a source toggled off truly stops syncing.

Parity Contract

  • Legacy behavior preserved: enabled-source caps + cadence (effective_interval_secs/connection_is_due) unchanged; pause short-circuit (periodic_pause_reason) unchanged.
  • Guard/fallback/dispatch parity checks: no-source-row path defaults to sync-with-caps (safe direction), never skip.

Summary by CodeRabbit

  • New Features

    • Per-source enable/disable control for periodic data sync with sensible defaults when a source lacks config.
    • Scheduler now resumes immediately on unpause and rebases cadence to avoid rapid on/off bursts.
  • Bug Fixes

    • Disabled sources are properly skipped and no longer sync.
  • Tests

    • Expanded unit tests covering source gating, defaulting behavior, and resume timing.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 388b6232-a815-4ceb-816c-6ff3f64bb638

📥 Commits

Reviewing files that changed from the base of the PR and between a2bf8db and 886b350.

📒 Files selected for processing (2)
  • src/openhuman/memory_sync/composio/periodic.rs
  • src/openhuman/scheduler_gate/gate.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/scheduler_gate/gate.rs
  • src/openhuman/memory_sync/composio/periodic.rs

📝 Walkthrough

Walkthrough

Add a process-wide resume Notify and fire it when the scheduler gate unpauses (including sign-in). Make the Composio periodic loop wait on either its interval or the resume Notify, reset cadence on resume, and use a per-tick snapshot plus decide_periodic_source to gate each Composio connection by registry-enabled state or toolkit defaults. Tests added for notify behavior and periodic decision logic.

Changes

Resume Notification and Periodic Gating

Layer / File(s) Summary
Resume notification singleton and gate transitions
src/openhuman/scheduler_gate/gate.rs
Process-wide RESUME_NOTIFY singleton exposed via resume_notify(). update_config detects Paused -> non-Paused transitions and set_signed_out detects true -> false transitions; both call notify_one(). Unit tests verify the notifier is stable, wakes waiters, and is triggered by both transition paths.
Decision helper and imports
src/openhuman/memory_sync/composio/periodic.rs
Introduce PeriodicSourceDecision and pure decide_periodic_source mapping optional MemorySourceEntry to Skip or Sync(with caps) using row values or per-toolkit defaults. Adjust imports for defaults and gate wiring.
Resume-aware ticker and per-tick snapshot
src/openhuman/memory_sync/composio/periodic.rs
Replace plain ticker.tick().await with tokio::select! on ticker.tick() and resume_notify().notified(), resetting the interval on resume. Build a per-tick composio_sources: HashMap<connection_id, MemorySourceEntry> from config.memory_sources.
Apply per-source decisions to provider startup
src/openhuman/memory_sync/composio/periodic.rs
Replace prior caps-only registry lookup with decide_periodic_source on the snapshot: skip disabled sources, use enabled-row caps, or fall back to memory_sync_defaults_for_toolkit when no row exists. Construct ProviderContext with chosen max_items/sync_depth_days.
Unit tests and builders
src/openhuman/memory_sync/composio/periodic.rs, src/openhuman/scheduler_gate/gate.rs
Add composio_source test builder and tests for decide_periodic_source cases (skip, use caps, default, independent gating). Add scheduler-gate tests for resume_notify stability, wake behavior, and transition-triggered notifications.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nudged the ticker, soft and light,
Now sleep turns into timely sight.
Resume bells wake the sync to run,
Each source checked—none block the sun.
Hooray, the trees begin their hum!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: adding resume notification to wake the Composio periodic loop and implementing per-source gating via toggle, directly addressing issue #2831.
Linked Issues check ✅ Passed All coding requirements from #2831 are met: exposed tokio::sync::Notify singleton from scheduler_gate, fires on transitions out of Policy::Paused, replaced ticker.tick() with tokio::select! for immediate wake, runs tick on resume with interval reset, re-checks policy for safety, and preserves pause behavior with safe defaults.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #2831 objectives: the Notify singleton, scheduler_gate updates for pause/sign-out transitions, and Composio periodic loop enhancements with per-source gating are all in scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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.

@sanil-23
sanil-23 marked this pull request as ready for review June 7, 2026 05:39
@sanil-23
sanil-23 requested a review from a team June 7, 2026 05:39

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/openhuman/scheduler_gate/gate.rs (1)

285-287: ⚡ Quick win

Log when the gate actually emits the resume signal.

These are the new wakeup branches, but nothing with the [scheduler_gate] prefix currently says that notify_one() fired or which transition caused it. A debug! breadcrumb here would make resume-latency issues diagnosable.

As per coding guidelines, "Include debug logging with verbose diagnostics on all new/changed flows: log entry/exit, branches, external calls, retries/timeouts, state transitions, and errors with stable grep-friendly prefixes ([domain], [rpc])."

Also applies to: 355-357

🤖 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 `@src/openhuman/scheduler_gate/gate.rs` around lines 285 - 287, Add a debug log
right before/after calling resume_notify().notify_one() to record that the gate
emitted a resume signal and which transition triggered it; specifically in the
branch that checks the resumed boolean (the resumed variable and the
resume_notify() call) emit a debug! with the `[scheduler_gate]` prefix and
include the transition name/identifier and resumed=true context. Apply the same
pattern to the other wakeup branch around the second
resume_notify().notify_one() (the code referenced at the other location) so both
wakeup paths log a concise, grep-friendly breadcrumb when notify_one() is
invoked.

Source: Coding guidelines

src/openhuman/memory_sync/composio/periodic.rs (1)

298-305: ⚡ Quick win

Log the resume-notify branch before rebasing the ticker.

This is the new behavior change, but there's no [composio:periodic] breadcrumb showing whether a tick came from the 20-minute timer or from resume_notify(). A debug! here would make wakeups and burst-prevention behavior observable.

As per coding guidelines, "Include debug logging with verbose diagnostics on all new/changed flows: log entry/exit, branches, external calls, retries/timeouts, state transitions, and errors with stable grep-friendly prefixes ([domain], [rpc])."

🤖 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 `@src/openhuman/memory_sync/composio/periodic.rs` around lines 298 - 305, Add a
debug log entry in the resume-notified branch so wakeups are observable: inside
the tokio::select! branch that matches resume.notified() (before calling
ticker.reset()), emit a debug! with a stable grep-friendly prefix like
"[composio:periodic]" and a message noting the branch (e.g. "resume-notify
wakeup - rebasing ticker") and any useful context (timestamp or task id if
available). Locate the resume.notified() match and insert the debug logging
immediately before calling ticker.reset() to ensure the log records that this
wakeup came from resume_notify() rather than the periodic tick.

Source: Coding guidelines

🤖 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 `@src/openhuman/memory_sync/composio/periodic.rs`:
- Around line 456-471: The current code builds composio_sources from
list_sources().await.unwrap_or_default(), which masks errors and effectively
treats failures as "all sources enabled"; instead, use the already-loaded config
snapshot for this tick (config.memory_sources) to build the index: iterate over
config.memory_sources, filter entries where kind == SourceKind::Composio, map
entries with a connection_id to (connection_id.clone(), MemorySourceEntry) and
collect into the HashMap used by composio_sources so that decide_periodic_source
gets explicit per-source state rather than defaulting on a failed registry read;
update the variable/comprehension that defines composio_sources and remove
reliance on list_sources() for this snapshot.

---

Nitpick comments:
In `@src/openhuman/memory_sync/composio/periodic.rs`:
- Around line 298-305: Add a debug log entry in the resume-notified branch so
wakeups are observable: inside the tokio::select! branch that matches
resume.notified() (before calling ticker.reset()), emit a debug! with a stable
grep-friendly prefix like "[composio:periodic]" and a message noting the branch
(e.g. "resume-notify wakeup - rebasing ticker") and any useful context
(timestamp or task id if available). Locate the resume.notified() match and
insert the debug logging immediately before calling ticker.reset() to ensure the
log records that this wakeup came from resume_notify() rather than the periodic
tick.

In `@src/openhuman/scheduler_gate/gate.rs`:
- Around line 285-287: Add a debug log right before/after calling
resume_notify().notify_one() to record that the gate emitted a resume signal and
which transition triggered it; specifically in the branch that checks the
resumed boolean (the resumed variable and the resume_notify() call) emit a
debug! with the `[scheduler_gate]` prefix and include the transition
name/identifier and resumed=true context. Apply the same pattern to the other
wakeup branch around the second resume_notify().notify_one() (the code
referenced at the other location) so both wakeup paths log a concise,
grep-friendly breadcrumb when notify_one() is invoked.
🪄 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: CHILL

Plan: Pro

Run ID: 346ecc51-7203-47f0-a03d-ecd11b4acb2a

📥 Commits

Reviewing files that changed from the base of the PR and between 7133dae and e1d11e2.

📒 Files selected for processing (2)
  • src/openhuman/memory_sync/composio/periodic.rs
  • src/openhuman/scheduler_gate/gate.rs

sanil-23 and others added 4 commits June 7, 2026 13:42
…r-source toggle (tinyhumansai#2831)

Pause via the scheduler gate (tinyhumansai#2825) left up to 20 min resume latency: when
Memory Tree is toggled back on (or the user signs back in), the periodic loop
kept its original interval cadence and the next real tick could be ~20 min away.

- Add a process-wide resume `Notify` to `scheduler_gate::gate`, fired on
  transitions out of `Policy::Paused` in `update_config` (Memory Tree toggled
  on) and on `set_signed_out` true->false (sign-in). Over-notifying is safe:
  the woken tick re-checks `current_policy()` and no-ops if still paused.
- `periodic::run_loop` now `select!`s over the ticker and the resume notify;
  on wake it runs a tick immediately and `reset()`s the ticker so rapid
  toggles don't bunch-fire.
- Gate the periodic loop on the per-source `enabled` toggle: a source switched
  off now stops syncing in the background (matching the manual sync paths,
  which already honour `enabled`). Previously the loop read the registry only
  for caps and synced disabled sources anyway, uncapped. No registry row yet
  -> sync with conservative per-toolkit defaults (never uncapped, never skip).

Tests: decide_periodic_source (disabled skip / enabled caps / default caps),
resume_notify singleton + parked-waiter wake.

Closes tinyhumansai#2831

Co-Authored-By: Claude <noreply@anthropic.com>
tinyhumansai#2831)

Seeds STATE directly (the test module can reach it) and drives a
deterministic Off -> AlwaysOn transition plus set_signed_out true->false,
asserting each fires the resume notify. Lifts changed-line diff coverage
on the touched files above the 80% gate (gate.rs to 100%).

Co-Authored-By: Claude <noreply@anthropic.com>
…oolkits (tinyhumansai#2831)

tinyhumansai#3443 (merged from upstream) added multiple account connections per
toolkit. The periodic loop already handles this correctly because it
keys the per-source gate on connection_id (not toolkit) and walks every
connection — so two accounts of the same toolkit are gated independently.
Add a regression test pinning that connection-id keying: enabled account
syncs with its caps, a disabled sibling account is skipped, an
unregistered sibling falls back to bounded defaults.

Co-Authored-By: Claude <noreply@anthropic.com>
…ig snapshot (tinyhumansai#2831)

CodeRabbit review: `list_sources().await.unwrap_or_default()` did a second
config read per tick and collapsed any transient load error into an empty
map — which made every disabled source fall through to the
`decide_periodic_source(None, ..)` default-caps path, silently re-enabling
background sync for sources the user switched off. Build the index from the
already-loaded `config.memory_sources` snapshot instead: fail-closed (a
disabled row stays disabled even if a read would have failed) and one fewer
config load.

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23
sanil-23 force-pushed the feat/2831-composio-autosync-wake branch from a2bf8db to 886b350 Compare June 7, 2026 08:21
@senamakel
senamakel merged commit 4c6fa39 into tinyhumansai:main Jun 7, 2026
22 checks passed
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
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(memory-sync): wake Composio periodic loop early via Notify when Memory Tree toggled back on

2 participants