feat(scheduler-gate): throttle background AI on battery / busy CPU#1062
Conversation
Background memory-tree jobs (extract, seal, summarise, embed, daily digest) used to run flat-out and made the host visibly lag, especially on laptops on battery. This adds a per-host scheduler gate that defers LLM-bound jobs based on power state, CPU pressure, and deployment mode. - New `openhuman::scheduler_gate` module: signals (battery via `starship-battery`, CPU via `sysinfo` two-shot, deployment-mode env detection), 4-tier `Policy` (Aggressive/Normal/Throttled/Paused), cooperative `wait_for_capacity()` consumed by background workers. Background sampler refreshes every 30s on a tokio task. - New `[scheduler_gate]` config section: `mode` (auto/always_on/off), `battery_floor` (0.80), `cpu_busy_threshold_pct` (70.0), `throttled_backoff_ms` (30s), `paused_poll_ms` (60s). - Hooked into the memory-tree worker pool just before the existing `llm_slots` semaphore acquire; only LLM-bound job kinds are gated, so AppendBuffer/FlushStale (DB I/O) keep running freely. - Composio periodic tick relaxed from 60s to 300s. Per-provider `sync_interval_secs` still bounds the actual minimum; this just loosens the upper bound so the scheduler wakes up less often when nothing is due. The composio sync path itself (`ingest_email` -> regex score -> `jobs::enqueue_tx`) does not call the LLM inline — only the worker pool does — so the gate sits exactly at the LLM boundary. Embedding work (Ollama) runs inside `ExtractChunk`/`Seal`/`DigestDaily`/ `TopicRoute` jobs, all of which `is_llm_bound()` already covers. On-demand embeddings (vision, RAG retrieval) intentionally bypass the gate so user-facing reads aren't blocked.
📝 WalkthroughWalkthroughAdds a scheduler gate that samples host signals (battery, CPU, deployment), decides an operational policy (Aggressive/Normal/Throttled/Paused), exposes an async capacity gate, integrates it into startup and job workers, and introduces config types and a battery probe dependency. Changes
Sequence Diagram(s)sequenceDiagram
participant Startup as Startup
participant Gate as SchedulerGate
participant Sampler as SignalSampler
participant Policy as PolicyEngine
participant BgLoop as BgLoop(30s)
participant JobWorker as JobWorker
Startup->>Gate: init_global(&config)
Gate->>Gate: init OnceLock / seed Signals
Gate->>Sampler: Signals::sample()
Sampler-->>Gate: Signals
Gate->>Policy: decide(signals, config)
Policy-->>Gate: Policy
Gate->>Gate: store State
Gate->>BgLoop: spawn periodic sampler
loop every 30s
BgLoop->>Sampler: sample()
Sampler-->>BgLoop: Signals
BgLoop->>Policy: decide(signals, config)
Policy-->>BgLoop: Policy
BgLoop->>Gate: update State
end
JobWorker->>Gate: wait_for_capacity().await
alt Policy == Aggressive or Normal
Gate-->>JobWorker: return immediately
else Policy == Throttled
Gate->>JobWorker: sleep(throttled_backoff_ms)
JobWorker-->>JobWorker: proceed
else Policy == Paused
loop until policy changes
Gate->>JobWorker: sleep(paused_poll_ms)
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 2/5 reviews remaining, refill in 24 minutes and 41 seconds. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/memory/tree/jobs/worker.rs (1)
76-89:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftDon't block after claiming the job.
claim_next()has already locked the row, sowait_for_capacity()can hold that lock indefinitely inPausedmode. OnceDEFAULT_LOCK_DURATION_MSelapses,recover_stale_locks()can requeue the same job and another worker may process it again, duplicating side effects.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/memory/tree/jobs/worker.rs` around lines 76 - 89, You are currently awaiting scheduler_gate::wait_for_capacity() and llm_slots.acquire() after claim_next(), which holds the DB lock and can cause the job row to be re-claimed by recover_stale_locks() when DEFAULT_LOCK_DURATION_MS expires; change the flow so you never block while holding the claim: either move capacity checks before calling claim_next() or, if you must claim first, use non-blocking checks (e.g., a scheduler_gate::try_check / llm_slots.try_acquire) and if they would block, immediately release the claim (call whatever function releases the claim or requeue the job) and return Ok(false) instead of awaiting; ensure references to claim_next, wait_for_capacity, llm_slots.acquire, DEFAULT_LOCK_DURATION_MS and recover_stale_locks are updated 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/openhuman/scheduler_gate/policy.rs`:
- Around line 43-50: Clamp the configuration thresholds before using them in
policy decisions: ensure cfg.battery_floor is clamped into a valid range (e.g.,
0..=100) and cfg.cpu_busy_threshold_pct is clamped into 0..=100 (or the
appropriate domain for those types) and then use the clamped values in the
battery_ok and cpu_ok checks; update the logic around the battery_ok computation
(which references cfg.battery_floor) and the cpu_ok computation (which uses
cfg.cpu_busy_threshold_pct) to refer to the clamped values to prevent
out-of-range config values from disabling or forcing throttling.
In `@src/openhuman/scheduler_gate/signals.rs`:
- Around line 43-49: The current env_on_ac parsing coerces any unrecognized
OPENHUMAN_ON_AC_POWER value to false; change the parsing for env_on_ac to only
accept explicit true tokens ("1","true","yes") as Some(true) and explicit false
tokens ("0","false","no") as Some(false), returning None for any other/invalid
input so invalid env values are ignored instead of treated as false (update the
env_on_ac expression that reads OPENHUMAN_ON_AC_POWER accordingly); keep
env_charge parsing as-is (it may remain .and_then(|v|
v.parse::<f32>().ok()).map(|v| v.clamp(0.0,1.0))).
---
Outside diff comments:
In `@src/openhuman/memory/tree/jobs/worker.rs`:
- Around line 76-89: You are currently awaiting
scheduler_gate::wait_for_capacity() and llm_slots.acquire() after claim_next(),
which holds the DB lock and can cause the job row to be re-claimed by
recover_stale_locks() when DEFAULT_LOCK_DURATION_MS expires; change the flow so
you never block while holding the claim: either move capacity checks before
calling claim_next() or, if you must claim first, use non-blocking checks (e.g.,
a scheduler_gate::try_check / llm_slots.try_acquire) and if they would block,
immediately release the claim (call whatever function releases the claim or
requeue the job) and return Ok(false) instead of awaiting; ensure references to
claim_next, wait_for_capacity, llm_slots.acquire, DEFAULT_LOCK_DURATION_MS and
recover_stale_locks are updated accordingly.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b2c4ebd0-1877-4b03-b08b-0a31a39049a2
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
Cargo.tomlsrc/core/jsonrpc.rssrc/openhuman/composio/periodic.rssrc/openhuman/config/mod.rssrc/openhuman/config/schema/mod.rssrc/openhuman/config/schema/scheduler_gate.rssrc/openhuman/config/schema/types.rssrc/openhuman/memory/tree/jobs/worker.rssrc/openhuman/mod.rssrc/openhuman/scheduler_gate/gate.rssrc/openhuman/scheduler_gate/mod.rssrc/openhuman/scheduler_gate/policy.rssrc/openhuman/scheduler_gate/signals.rs
- worker.rs: move `wait_for_capacity()` before `claim_next()`. Holding the DB claim across an awaited gate (esp. in `Paused` mode) could exceed `DEFAULT_LOCK_DURATION_MS`, after which `recover_stale_locks` would requeue the row and a second worker could re-process it, duplicating side effects. Throttling pre-claim trades a small delay on non-LLM jobs (which are rare and quick) for correctness. - policy.rs: clamp `battery_floor` to 0..=1 and `cpu_busy_threshold_pct` to 0..=100 before use, so a malformed config.toml can't silently flip the gate on/off. Added two unit tests covering clamp behaviour on both ends of each range. - signals.rs: tighten `OPENHUMAN_ON_AC_POWER` parsing — only explicit truthy/falsy tokens are honoured; garbage values now yield `None` (and the real probe gets to answer) instead of being coerced to `false` and pushing the host into Throttled.
There was a problem hiding this comment.
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/openhuman/scheduler_gate/signals.rs`:
- Around line 15-20: The fields and related variables in signals.rs use
snake_case but must follow camelCase for this module; rename struct fields
on_ac_power, battery_charge, cpu_usage_pct, server_mode to onAcPower,
batteryCharge, cpuUsagePct, serverMode (and the local variables/env flags
env_on_ac, env_charge, no_battery to envOnAc, envCharge, noBattery), then update
every usage, pattern match, constructor, method, impl, test, and
serialization/deserialization attribute that references those symbols (e.g., the
Signal struct, env_* locals, and any functions that read/write them) so names
are consistent; if external JSON/serde keys must remain snake_case, add
appropriate serde(rename = "...") attributes to the renamed fields.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b376add9-9ffb-4d4b-8aa9-b25435634402
📒 Files selected for processing (3)
src/openhuman/memory/tree/jobs/worker.rssrc/openhuman/scheduler_gate/policy.rssrc/openhuman/scheduler_gate/signals.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/openhuman/scheduler_gate/policy.rs
| pub on_ac_power: bool, | ||
| /// 0.0..=1.0, or `None` when no battery sensor is present (most servers). | ||
| pub battery_charge: Option<f32>, | ||
| /// Recent global CPU usage, 0..100. | ||
| pub cpu_usage_pct: f32, | ||
| pub server_mode: bool, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Align identifiers with the repo’s camelCase naming rule for this path.
This module introduces several snake_case variable/field names (on_ac_power, battery_charge, cpu_usage_pct, server_mode, env_on_ac, env_charge, no_battery) that conflict with the naming convention for src/openhuman/**/*.rs.
As per coding guidelines src/openhuman/**/*.rs: Use camelCase for variable names.
Also applies to: 26-33, 46-56, 130-151
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/openhuman/scheduler_gate/signals.rs` around lines 15 - 20, The fields and
related variables in signals.rs use snake_case but must follow camelCase for
this module; rename struct fields on_ac_power, battery_charge, cpu_usage_pct,
server_mode to onAcPower, batteryCharge, cpuUsagePct, serverMode (and the local
variables/env flags env_on_ac, env_charge, no_battery to envOnAc, envCharge,
noBattery), then update every usage, pattern match, constructor, method, impl,
test, and serialization/deserialization attribute that references those symbols
(e.g., the Signal struct, env_* locals, and any functions that read/write them)
so names are consistent; if external JSON/serde keys must remain snake_case, add
appropriate serde(rename = "...") attributes to the renamed fields.
* feat(remotion): Ghosty character library with transparent MOV variants (tinyhumansai#1059) Co-authored-by: WOZCODE <contact@withwoz.com> * feat(composio/gmail): sync into memory tree (Slack-parity) (tinyhumansai#1056) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(scheduler-gate): throttle background AI on battery / busy CPU (tinyhumansai#1062) * fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q (tinyhumansai#1061) * ci: add dedicated staging release workflow (tinyhumansai#1066) * fix(sentry): Rust source context + per-release deploy marker (tinyhumansai#405) (tinyhumansai#1067) * fix(welcome): re-enable OAuth buttons with focus/timeout recovery (tinyhumansai#1049) (tinyhumansai#1069) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(dependencies): update pnpm-lock.yaml and Cargo.lock for package… (tinyhumansai#1082) * fix(onboarding): personalize welcome agent greeting with user identity (tinyhumansai#1078) * fix(chat): make agent message bubbles fit content width (tinyhumansai#1083) * Feat/dmg checks (tinyhumansai#1084) * fix(linux): Add X11 platform flags to .deb package launcher (tinyhumansai#1087) Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> * fix(sentry): auto-send React events; collapse core→tauri for desktop (tinyhumansai#1086) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(cef): run blank reload guard on the CEF UI thread (tinyhumansai#1092) * fix(app): reload webview instead of restart_app in dev mode (tinyhumansai#1068) (tinyhumansai#1071) * fix(linux): deliver X11 ozone flags via custom .desktop template (tinyhumansai#1091) * fix(webview-accounts): retry data-dir purge so CEF handle race doesn't leak cookies (tinyhumansai#1076) (tinyhumansai#1081) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * fix(webview/slack): media perms + deep-link isolation (tinyhumansai#1074) (tinyhumansai#1080) Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * ci(release): split staging vs production workflows; promote staging tags (tinyhumansai#1094) * Update release-staging.yml (tinyhumansai#1097) * chore(staging): v0.53.5 * chore(staging): v0.53.6 * ci(staging): cut staging from main; add act local-debug helper (tinyhumansai#1099) * chore(staging): v0.53.7 * fix(ci): correct sentry-cli download URL and trap scope (tinyhumansai#1100) * chore(staging): v0.53.8 * feat(chat): forward thread_id to backend for KV cache locality (tinyhumansai#1095) * fix(ci): bump pinned sentry-cli to 3.4.1 (2.34.2 was never published) (tinyhumansai#1102) * chore(staging): v0.53.9 * fix(ci): drop bash trap in upload_sentry_symbols.sh; inline cleanup (tinyhumansai#1103) * chore(staging): v0.53.10 * refactor(session): flatten session_raw/, switch md to YYYY_MM_DD (tinyhumansai#1098) * Add full Composio managed-auth toolkit catalog (tinyhumansai#1093) * ci: add diff-aware 80% coverage gate (Vitest + cargo-llvm-cov) (tinyhumansai#1104) * feat(scripts): pnpm work + pnpm debug for agent-driven workflows (tinyhumansai#1105) * ci: pull pnpm into CI image, drop redundant setup steps (tinyhumansai#1107) * docs: add Cursor Cloud specific instructions to AGENTS.md (tinyhumansai#1106) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * chore(staging): v0.53.11 * docs: surface 80% coverage gate and scripts/debug runners (tinyhumansai#1108) * feat(app): show Composio integrations as sorted icon grid on Skills (tinyhumansai#1109) Co-authored-by: Cursor Agent <cursoragent@cursor.com> * feat(composio): client-side trigger enable/disable toggles (tinyhumansai#1110) * feat(skills): channels grid + integrations card polish; tolerant Composio trigger decode (tinyhumansai#1112) * chore(staging): v0.53.12 * feat(home): early-bird banner + assistant→agent terminology (tinyhumansai#1113) * feat(updater): in-app auto-update with auto-download + restart prompt (tinyhumansai#677) (tinyhumansai#1114) * chore(claude): add ship-and-babysit slash command (tinyhumansai#1115) * feat(home): EarlyBirdyBanner + agent terminology + LinkedIn enrichment model pin (tinyhumansai#1118) * fix(chat): single onboarding thread in sidebar after wizard (tinyhumansai#1116) Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> * fix: filter out global namespace from citation chips (tinyhumansai#1124) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> * feat(nav): enable Memory tab in BottomTabBar (tinyhumansai#1125) * feat(memory): singleton ingestion + status RPC + UI pill (tinyhumansai#1126) * feat(human): mascot tab with viseme-driven lipsync (staging only) (tinyhumansai#1127) * Fix CEF zombie processes on full app close and restart (tinyhumansai#1128) Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> * Update issue templates for GitHub issue types (tinyhumansai#1146) * feat(human): expand mascot expressions and tighten reply-speech state machine (tinyhumansai#1147) * feat(memory): ingestion pipeline + tree-architecture docs + ops/schemas split (tinyhumansai#1142) * feat(threads): surface live subagent work in parent thread (tinyhumansai#1122) (tinyhumansai#1159) * fix(human): keep mascot mouth animating when TTS ships no viseme data (tinyhumansai#1160) * feat(composio): consume backend markdownFormatted for LLM output (tinyhumansai#1165) * fix(subagent): lazy-register toolkit actions filtered out of fuzzy top-K (tinyhumansai#1162) * feat(memory): user-facing long-term memory window preset (tinyhumansai#1137) (tinyhumansai#1161) * fix(tauri-shell): proactively kill stale openhuman RPC on startup (tinyhumansai#1166) * chore(staging): v0.53.13 * fix(composio): per-action tool consumes backend markdownFormatted (tinyhumansai#1167) * fix(threads): persist selectedThreadId across reloads (tinyhumansai#1168) * feat(memory_tree): switch embed model to bge-m3 (1024-dim, 8K context) (tinyhumansai#1174) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(agent): drop redundant [Memory context] recall injection (tinyhumansai#1173) * chore(memory_tree): drop body-read timeouts on Ollama HTTP calls (tinyhumansai#1171) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(transcript): emit thread_id + fix orchestrator missing cost (tinyhumansai#1169) * fix(composio/gmail): phase out html2md, prefer text/plain MIME part (tinyhumansai#1170) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): markdown output for internal tool results (tinyhumansai#1172) * feat(security): enforce prompt-injection guard before model and tool execution (tinyhumansai#1175) * fix(cef): popup paint dies after first frame — skip blank-page guard for popups (tinyhumansai#1079) (tinyhumansai#1182) Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> * chore(sentry): rename OPENHUMAN_SENTRY_DSN → OPENHUMAN_CORE_SENTRY_DSN (tinyhumansai#1186) * feat(remotion): add yellow mascot character with all animation variants (tinyhumansai#1193) Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(composio): hide raw connection ID, derive friendly label (tinyhumansai#1153) (tinyhumansai#1185) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(windows): align install.ps1 MSI with per-machine scope (tinyhumansai#913) (tinyhumansai#1187) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(tauri): deterministic CEF teardown on full app close (tinyhumansai#1120) (tinyhumansai#1189) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(composio): cap Gmail HTML body before strip (crash mitigation) (tinyhumansai#1191) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(auth): stop stale chat threads after signup (tinyhumansai#1192) Co-authored-by: Cursor <cursoragent@cursor.com> * feat(sentry): staging-only "Trigger Sentry Test" button (tinyhumansai#1072) (tinyhumansai#1183) * chore(staging): v0.53.14 * chore(staging): v0.53.15 * feat(composio): format trigger slugs into human-readable labels (tinyhumansai#1129) (tinyhumansai#1179) Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix(ui): hide unsupported permission UI on non-macOS for Screen Intelligence (tinyhumansai#1194) Co-authored-by: Cursor <cursoragent@cursor.com> * chore(tauri-shell): retire embedded Gmail webview-account flow (tinyhumansai#1181) * feat(onboarding): replace welcome-agent bot with react-joyride walkthrough (tinyhumansai#1180) * chore(release): v0.53.16 * fix(threads): preserve selectedThreadId on cold-boot identity hydration (tinyhumansai#1196) * feat(core): version/shutdown/update RPCs + mid-thread integration refresh (tinyhumansai#1195) * fix(mascot): swap to yellow mascot via @remotion/player (tinyhumansai#1200) * feat(memory_tree): cloud-default LLM, queue priority, entity filter, Memory tab UI (tinyhumansai#1198) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Persist turn state + restore conversation history on cold-boot (tinyhumansai#1202) * feat(mascot): floating desktop mascot via native NSPanel + WKWebView (macOS) (tinyhumansai#1203) * fix(memory/tree): emit summary children as Obsidian wikilinks (tinyhumansai#1210) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(tools): coding-harness baseline primitives (tinyhumansai#1205) (tinyhumansai#1208) * docs: add Codex PR checklist for remote agents --------- Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Co-authored-by: WOZCODE <contact@withwoz.com> Co-authored-by: sanil-23 <sanil@vezures.xyz> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com> Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com> Co-authored-by: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Co-authored-by: Gaurang Patel <ptelgm.yt@gmail.com> Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com> Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com> Co-authored-by: Steven Enamakel's Droid <enamakel.agent@tinyhumans.ai> Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com> Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Co-authored-by: Neil <neil@maha.xyz> Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local> Co-authored-by: obchain <167975049+obchain@users.noreply.github.com> Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
Summary
openhuman::scheduler_gatemodule that gates background LLM-bound work based on power state (battery viastarship-battery), CPU pressure (sysinfotwo-shot), and deployment mode (env +/.dockerenv+ Kubernetes detection).memory/tree/jobs/worker.rs) right before the existingllm_slotssemaphore — onlyis_llm_bound()jobs (ExtractChunk, Seal, DigestDaily, TopicRoute) are throttled. AppendBuffer / FlushStale (pure DB IO) keep running freely.sync_interval_secsstill bounds the minimum delay between actual syncs; this just stops the scheduler from waking the host every minute to find nothing due.[scheduler_gate]config section with mode (auto/always_on/off),battery_floor(0.80),cpu_busy_threshold_pct(70.0),throttled_backoff_ms,paused_poll_ms.wait_for_capacity()returns immediately for Aggressive/Normal, sleeps for Throttled, polls for Paused.Why: background memory-tree digests + embeddings ran flat-out and made the laptop visibly lag, especially on battery. The gate sits exactly at the LLM boundary — composio's ingest path (
ingest_email→ regex score →jobs::enqueue_tx) does not touch the LLM inline, only the worker pool does, so throttling the worker is sufficient.Test plan
cargo check --libcleancargo test --lib scheduler_gate— 8/8 policy tests pass (off/always_on/server-mode/plugged-in/below-floor/above-floor/busy-cpu/no-battery)cargo test --lib composio::periodic— 6/6 pass (incl. relaxedtick_seconds_is_sane_default)cargo fmtOPENHUMAN_ON_AC_POWER=false OPENHUMAN_BATTERY_CHARGE=0.5forces Throttled; watch[scheduler_gate]debug logs for the policy transitionOPENHUMAN_DEPLOYMENT=serverforces Aggressive regardless of host signalsSummary by CodeRabbit
New Features
Configuration
Performance