Skip to content

fix(skills): route sync RPC to onSync handler and persist state to memory#183

Merged
senamakel merged 10 commits into
mainfrom
fix/skills-sync
Apr 2, 2026
Merged

fix(skills): route sync RPC to onSync handler and persist state to memory#183
senamakel merged 10 commits into
mainfrom
fix/skills-sync

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 2, 2026

Summary

  • Fixed routing bug: handle_skills_sync() was sending "skill/tick" instead of "skill/sync", causing sync requests to call onTick() (which most skills don't implement) instead of onSync(), and completely skipping memory persistence
  • Added memory persistence to CronTrigger and Tick handlers so all sync paths (manual sync, cron-triggered sync, tick) now write published state to the local memory store
  • Extracted persist_state_to_memory() helper to eliminate code duplication across the three sync paths
  • Added integration tests verifying sync→memory persistence end-to-end, plus a regression test for the routing bug
  • Extended Notion live test with memory verification step and added a helper script for live debugging

Root Cause

schemas.rs:778 mapped openhuman.skills_sync RPC → "skill/tick"SkillMessage::TickonTick() (no memory write). The correct "skill/sync" handler existed in the event loop with full memory persistence but was dead code — nothing ever routed to it.

Test plan

  • cargo check passes
  • cargo test --test skills_sync_memory_test — 3 tests pass (sync, tick, routing regression)
  • cargo test --test skills_debug_e2e — full lifecycle with new sync step passes
  • RUN_LIVE_NOTION=1 cargo test --test skills_notion_live -- --nocapture — live Notion sync writes to memory
  • bash scripts/debug-notion-sync-memory.sh — end-to-end live verification

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • Tests

    • Added/updated integration and end-to-end tests with a dedicated SYNC phase, memory verification, and safer string truncation for UTF‑8 boundaries.
  • New Features

    • Support for a local skills source via a new SKILLS_LOCAL_DIR and ability to read local registries alongside remote URLs.
  • Bug Fixes

    • Ensure state persistence only after successful handlers and improved memory-write reliability and logging.
  • Chores

    • Added a debug script to run live Notion sync tests with memory verification.

senamakel and others added 2 commits April 1, 2026 17:45
…ce memory persistence

- Introduced `debug-notion-sync-memory.sh` to facilitate live testing of the Notion skill with memory verification.
- The script validates the full flow from skill start to memory persistence, ensuring required environment variables are set.
- Updated `handle_skills_sync` to change the RPC method from `skill/tick` to `skill/sync` for better clarity in the sync process.
- Implemented `persist_state_to_memory` function to ensure published ops state is saved to memory after sync, cron, and tick events.
- Enhanced end-to-end tests to validate memory persistence during skill sync and tick operations, ensuring robust functionality.

This update improves the debugging process for the Notion skill and enhances the overall reliability of memory operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

Adds bounded background memory-write worker and helper invoked after cron/tick/sync handlers, routes skills RPCs to skill/sync, adds local SKILLS_LOCAL_DIR support and local registry/file:// fetching, new integration tests verifying memory persistence, and a debug script for Notion live tests with memory verification.

Changes

Cohort / File(s) Summary
Debug Script
scripts/debug-notion-sync-memory.sh
New executable Bash script to run Notion live integration tests with env loading, required-variable checks, and memory-verification invocation.
Event Loop & Persistence
src/openhuman/skills/qjs_skill_instance/event_loop.rs
Added MemoryWriteJob, bounded channel & worker (spawn_memory_write_worker()), persist_state_to_memory() helper; persist only after successful cron/tick/sync handlers; moved snapshot work off hot path and reduced inline snapshot logging.
RPC Routing / Schema
src/openhuman/skills/schemas.rs
handle_skills_sync now targets engine RPC "skill/sync" and logs debug info before dispatch.
Engine Source Dir
src/openhuman/skills/qjs_engine.rs
get_skills_source_dir now honors SKILLS_LOCAL_DIR first (logs info/warning), falling back to prior resolution.
Registry & Install
src/openhuman/skills/registry_ops.rs
Support local skill installs from SKILLS_LOCAL_DIR; registry_fetch accepts local absolute paths and file:// URIs (reads JSON from disk) and preserves remote caching; added fetch_url_bytes and copy_dir_recursive.
Env Example
.env.example
Documented SKILLS_REGISTRY_URL supports local paths; added SKILLS_LOCAL_DIR= example and notes.
Notion Live Test
tests/skills_notion_live.rs
Added truncate_str helper for safe UTF-8 truncation; replaced final step with “Sync + Memory Verification”: calls skill/sync, waits, then uses MemoryClient to list skill documents/namespaces before final state check.
New Memory Sync Tests
tests/skills_sync_memory_test.rs
New integration test file with Tokio tests validating that skill/sync and skill/tick persist to memory when published state exists, plus a regression test asserting "periodic sync" titles.
E2E Debug Test
tests/skills_debug_e2e.rs, tests/skills_rpc_e2e.rs
SKILLS_LOCAL_DIR support added to skills-dir discovery; separated TICK and SYNC phases in debug e2e; try_find_skills_dir() checks SKILLS_LOCAL_DIR early.
RPC Test Relaxation
tests/json_rpc_e2e.rs
Relaxed assertion for openhuman.skills_sync: ensure no JSON-RPC error instead of requiring ok:true.

Sequence Diagram

sequenceDiagram
    actor Trigger as Trigger (Cron / Tick / RPC)
    participant EventLoop as Event Loop
    participant QJS as QJS Skill Instance
    participant Helper as persist_state_to_memory()
    participant Worker as Memory Write Worker
    participant Memory as MemoryClient

    Trigger->>EventLoop: deliver message (cron/tick/rpc)
    EventLoop->>QJS: invoke handler (onCron/onTick/onSync)
    QJS-->>EventLoop: handler returns (Ok / Err)
    alt handler Ok
        EventLoop->>Helper: call with ops_state & title_suffix
        Helper->>Worker: try_send MemoryWriteJob (bounded channel)
        Worker->>Memory: store_skill_sync(job) (background)
        Memory-->>Worker: success/failure
        Worker-->>EventLoop: log result
    else handler Err
        EventLoop-->>EventLoop: skip persistence (warn)
    end
    EventLoop-->>Trigger: complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble snapshots, soft and spry,
Hopping through tick, cron, and sync nearby.
Into memory's burrow each state I tuck,
Quietly saving—no panic, no rush—just luck. 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fixes in this changeset: routing the sync RPC to the correct onSync handler and adding memory persistence to the state.
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.

✨ 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 fix/skills-sync

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

@senamakel
Copy link
Copy Markdown
Member Author

#163

Copy link
Copy Markdown
Contributor

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/openhuman/skills/schemas.rs (1)

772-780: ⚠️ Potential issue | 🟡 Minor

Add a debug checkpoint log for the new skills.sync route.

The routing fix is correct, but this changed critical path currently has no trace/debug log to help diagnose sync routing issues at runtime.

💡 Proposed patch
 fn handle_skills_sync(params: Map<String, Value>) -> ControllerFuture {
     Box::pin(async move {
         let p: SkillIdParams =
             serde_json::from_value(Value::Object(params)).map_err(|e| e.to_string())?;
         let engine = require_engine()?;
+        log::debug!(
+            "[skills.sync] routing skill '{}' to RPC method 'skill/sync'",
+            p.skill_id
+        );
         engine
             .rpc(&p.skill_id, "skill/sync", serde_json::json!({}))
             .await
     })
 }

As per coding guidelines, src/**/*.rs: Use log/tracing at debug or trace level in Rust for critical checkpoints (entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, error handling paths).

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

In `@src/openhuman/skills/schemas.rs` around lines 772 - 780, The new skills.sync
route handler handle_skills_sync lacks any debug/tracing logs; add a debug-level
tracing/log::debug call at the entry of handle_skills_sync that logs the
incoming SkillIdParams (or at least p.skill_id) after deserialization and
another debug before calling engine.rpc (including p.skill_id and the method
"skill/sync"); use the existing require_engine and engine.rpc symbols to locate
the code and ensure logs avoid sensitive data and use debug/trace level per
project guidelines.
🤖 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/skills/qjs_skill_instance/event_loop.rs`:
- Around line 324-331: The code currently calls persist_state_to_memory
unconditionally after calling lifecycle handlers like handle_cron_trigger, which
can persist stale state on errors; change the lifecycle invocations (e.g.,
handle_cron_trigger and the onSync/onTick calls referenced) to return/propagate
a Result and only call persist_state_to_memory when the call succeeded (i.e.,
match or if let Ok(_) for the Result). Specifically, update the call sites that
pass skill_id, ops_state, memory_client and the schedule_id log (the lines
around handle_cron_trigger and the other onSync/onTick blocks) to check the
handler result before invoking persist_state_to_memory so persistence happens
only on success. Ensure error branches do not call persist_state_to_memory and
still log/handle the error appropriately.
- Around line 51-63: The current fire-and-forget tokio::spawn in
persist_state_to_memory (the block that calls client.store_skill_sync(...)) can
accumulate tasks; change this to use a bounded concurrency mechanism such as a
shared JoinSet or a bounded mpsc channel with a single background worker to
apply backpressure: create a task queue for store_skill_sync calls, push the
title/content payloads into the channel (or submit into a JoinSet with a max
concurrency limit) and have a dedicated task consume and await
client.store_skill_sync so you limit concurrent in-flight writes; preserve the
existing logging (debug/info/warn) around client.store_skill_sync and ensure
errors are awaited/handled rather than leaked by unbounded tokio::spawn.

In `@tests/skills_sync_memory_test.rs`:
- Around line 82-83: Tests are race-prone because multiple tests call
set_global_engine(...) which mutates the process-wide GLOBAL_ENGINE
concurrently; fix by serializing these tests the same way other files do (e.g.,
annotate the test functions or the test module with the serialization attribute
used elsewhere such as #[serial] or #[serial_test::serial]) so only one test
mutates GLOBAL_ENGINE at a time; update tests in skills_sync_memory_test.rs to
use that serial attribute on the affected tests or module to prevent
cross-wiring of engine instances.
- Around line 317-357: The test currently calls engine.rpc(skill_id,
"skill/sync", ...) directly which bypasses the controller and won't catch a
regression in handle_skills_sync; replace that direct call with invoking the
registered controller via try_invoke_registered_rpc("openhuman.skills_sync",
params) (or the test helper that invokes registered RPC handlers) using the same
parameters/skill_id payload so the test exercises handle_skills_sync and
verifies it routes "skill/sync" instead of "skill/tick" (update the section
around the engine.rpc call and ensure the subsequent assertions remain the
same).

---

Outside diff comments:
In `@src/openhuman/skills/schemas.rs`:
- Around line 772-780: The new skills.sync route handler handle_skills_sync
lacks any debug/tracing logs; add a debug-level tracing/log::debug call at the
entry of handle_skills_sync that logs the incoming SkillIdParams (or at least
p.skill_id) after deserialization and another debug before calling engine.rpc
(including p.skill_id and the method "skill/sync"); use the existing
require_engine and engine.rpc symbols to locate the code and ensure logs avoid
sensitive data and use debug/trace level per project guidelines.
🪄 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: d6583312-530b-4a2b-84ed-9f290a85b56f

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd4d1f and 1b3c0b9.

📒 Files selected for processing (6)
  • scripts/debug-notion-sync-memory.sh
  • src/openhuman/skills/qjs_skill_instance/event_loop.rs
  • src/openhuman/skills/schemas.rs
  • tests/skills_debug_e2e.rs
  • tests/skills_notion_live.rs
  • tests/skills_sync_memory_test.rs

Comment thread src/openhuman/skills/qjs_skill_instance/event_loop.rs Outdated
Comment thread src/openhuman/skills/qjs_skill_instance/event_loop.rs Outdated
Comment thread tests/skills_sync_memory_test.rs
Comment thread tests/skills_sync_memory_test.rs Outdated
…e persistence

- Introduced a `MemoryWriteJob` struct to encapsulate the details of memory write operations.
- Added a bounded background worker using `tokio::spawn` to handle memory writes asynchronously, improving performance and responsiveness.
- Updated `persist_state_to_memory` to queue memory write jobs instead of executing them directly, allowing for better flow control and error handling.
- Enhanced logging for memory persistence operations to provide clearer insights into success and failure cases.
- Modified event handling in `handle_message` to ensure state persistence occurs only after successful operations, reducing the risk of data loss.

This update significantly enhances the memory management capabilities of the skill instance, ensuring more reliable state persistence.
- Added detailed debug logging in `handle_skills_sync` to track skill synchronization events, improving observability during skill operations.
- Updated `persist_state_to_memory` to include a memory write transaction, ensuring state persistence is handled more robustly and efficiently.
- Enhanced event handling in `handle_message` to ensure memory writes are queued correctly, improving the reliability of state persistence during skill operations.

These changes improve the overall functionality and reliability of skills synchronization and memory management.
- Added detailed debug logging in `handle_skills_sync` to track skill synchronization events, improving observability during skill operations.
- Updated `persist_state_to_memory` to include a `memory_write_tx` parameter, allowing for more efficient state persistence in the event loop.
- Enhanced tests for skills synchronization to ensure robust memory handling and state persistence during skill operations.

This update improves the reliability and traceability of skills synchronization processes, ensuring better memory management and debugging capabilities.
- Enhanced comments in `json_rpc_skills_runtime_start_tools_call_stop` to clarify the sync process routing through the `skill/sync` RPC.
- Updated assertions in `skills_sync_rpc_calls_on_sync_not_on_tick` to ensure proper handling of the new sync flow, verifying that the `skills_sync` method routes correctly to `onSync`.
- Improved logging for better observability during skill synchronization tests, ensuring that the expected behavior aligns with the updated RPC structure.

These changes improve the clarity and reliability of the end-to-end tests related to skills synchronization.
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
tests/skills_sync_memory_test.rs (1)

36-61: Improve robustness of skills directory discovery.

The hardcoded relative paths may not work in all CI/development environments. Consider documenting the expected directory structure or failing more explicitly when the directory is not found.

The current fallback logic is reasonable for local development but could be fragile.

💡 Suggestion: Add a clearer error message
 fn try_find_skills_dir() -> Option<PathBuf> {
     if let Ok(dir) = std::env::var("SKILL_DEBUG_DIR") {
         let p = PathBuf::from(&dir);
-        return if p.exists() { Some(p) } else { None };
+        if p.exists() {
+            return Some(p);
+        }
+        eprintln!("SKILL_DEBUG_DIR={} does not exist", dir);
+        return None;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/skills_sync_memory_test.rs` around lines 36 - 61, Update
try_find_skills_dir to return Result<PathBuf, anyhow::Error> (or Result<PathBuf,
String>) instead of Option and produce a clear error when the skills directory
cannot be located: keep the existing SKILL_DEBUG_DIR check and candidate search
(the candidate array and cwd/parent search using std::env::current_dir and
std::fs::read_dir), but collect the attempted paths and include them plus the
SKILL_DEBUG_DIR value in the Err returned (use canonicalize when returning a
found path); this makes callers of try_find_skills_dir receive an explicit
failure with a descriptive message rather than silently getting None.
🤖 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/skills_sync_memory_test.rs`:
- Around line 369-378: The test iterates over documents and asserts each title
contains "periodic sync", which will either be a no-op if doc_array is empty or
falsely fail if unrelated docs exist; update the check in
tests/skills_sync_memory_test.rs so that you first handle the empty-case (allow
empty doc_array when the skill publishes no state) and otherwise assert that at
least one document matches the expected "periodic sync" condition (or assert
each present doc is from the sync handler) by guarding the loop with an if/else
or using a any() check over doc_array titles to confirm existence of a "periodic
sync" title rather than blindly asserting every title contains that substring.

---

Nitpick comments:
In `@tests/skills_sync_memory_test.rs`:
- Around line 36-61: Update try_find_skills_dir to return Result<PathBuf,
anyhow::Error> (or Result<PathBuf, String>) instead of Option and produce a
clear error when the skills directory cannot be located: keep the existing
SKILL_DEBUG_DIR check and candidate search (the candidate array and cwd/parent
search using std::env::current_dir and std::fs::read_dir), but collect the
attempted paths and include them plus the SKILL_DEBUG_DIR value in the Err
returned (use canonicalize when returning a found path); this makes callers of
try_find_skills_dir receive an explicit failure with a descriptive message
rather than silently getting None.
🪄 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: 96af3c05-eb72-469c-b869-77752271997c

📥 Commits

Reviewing files that changed from the base of the PR and between 1b3c0b9 and bd264c5.

📒 Files selected for processing (4)
  • src/openhuman/skills/qjs_skill_instance/event_loop.rs
  • src/openhuman/skills/schemas.rs
  • tests/json_rpc_e2e.rs
  • tests/skills_sync_memory_test.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/skills/schemas.rs

Comment on lines +369 to +378
if let Some(doc_array) = docs.get("documents").and_then(|d| d.as_array()) {
for doc in doc_array {
let title = doc.get("title").and_then(|t| t.as_str()).unwrap_or("?");
eprintln!(" Memory doc title: {title}");
assert!(
title.contains("periodic sync"),
"Expected title to contain 'periodic sync' (from skill/sync handler), got: {title}"
);
}
}
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.

⚠️ Potential issue | 🟡 Minor

Assertion may incorrectly fail when skill publishes no state.

If the skill has no published state after sync, persist_state_to_memory correctly skips the write (empty snapshot → early return). However, this assertion iterates over doc_array which could be empty, meaning no assertion runs. If a document is present from a previous test artifact or other source, the assertion would fail on a non-"periodic sync" title.

Consider adding a guard or adjusting the logic:

💡 Proposed fix
     let namespace = format!("skill-{}", skill_id);
     let docs = memory_client
         .list_documents(Some(&namespace))
         .await
         .expect("list_documents");
+    let has_published_state = engine
+        .get_skill_state(skill_id)
+        .map(|s| !s.state.is_empty())
+        .unwrap_or(false);
+
     if let Some(doc_array) = docs.get("documents").and_then(|d| d.as_array()) {
+        if has_published_state {
+            assert!(
+                !doc_array.is_empty(),
+                "Expected memory documents after sync with published state"
+            );
+        }
         for doc in doc_array {
             let title = doc.get("title").and_then(|t| t.as_str()).unwrap_or("?");
             eprintln!("  Memory doc title: {title}");
             assert!(
                 title.contains("periodic sync"),
                 "Expected title to contain 'periodic sync' (from skill/sync handler), got: {title}"
             );
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/skills_sync_memory_test.rs` around lines 369 - 378, The test iterates
over documents and asserts each title contains "periodic sync", which will
either be a no-op if doc_array is empty or falsely fail if unrelated docs exist;
update the check in tests/skills_sync_memory_test.rs so that you first handle
the empty-case (allow empty doc_array when the skill publishes no state) and
otherwise assert that at least one document matches the expected "periodic sync"
condition (or assert each present doc is from the sync handler) by guarding the
loop with an if/else or using a any() check over doc_array titles to confirm
existence of a "periodic sync" title rather than blindly asserting every title
contains that substring.

- Updated `.env.example` to include `SKILLS_LOCAL_DIR` for local skills source directory, allowing developers to specify a path for skill discovery and installation.
- Improved comments in the environment file to clarify the usage of `SKILLS_REGISTRY_URL` for both remote and local paths, enhancing the development experience.
- Refactored `qjs_engine.rs` to prioritize the `SKILLS_LOCAL_DIR` for skill source directory resolution, improving local development workflows.
- Added utility functions in `registry_ops.rs` to support local file path handling for skill registries, enhancing flexibility in skill management.

These changes streamline the development process for skills by providing clearer configuration options and improving local development capabilities.
- Updated `try_find_skills_dir` function across multiple test files to include support for the `SKILLS_LOCAL_DIR` environment variable, improving the flexibility of skills directory resolution.
- Enhanced comments to clarify the order of directory search priorities, ensuring better understanding for developers.
- Improved error handling and logging for cases where the specified directory does not exist, aiding in debugging and test reliability.

These changes streamline the skills directory discovery process in tests, enhancing the overall development experience.
- Updated `try_find_skills_dir` function to include support for the `SKILLS_LOCAL_DIR` environment variable, allowing for more flexible skills directory resolution.
- Improved documentation to clarify the priority order for skills directory discovery.
- Refactored multiple test files to utilize the updated skills directory discovery logic, enhancing consistency and maintainability across tests.

These changes streamline the skills directory discovery process in tests, improving the overall testing framework.
… tests

- Updated the memory client verification process in `notion_live_with_real_data` to utilize `MemoryClient::new_local()` for improved clarity and consistency.
- Enhanced comments to clarify the memory store check location and removed redundant error handling for workspace directory creation.
- Simplified the error logging to focus on the memory client creation failure, improving readability and maintainability of the test code.

These changes enhance the reliability of memory verification in the Notion live tests, ensuring a clearer understanding of the memory client initialization process.
Copy link
Copy Markdown
Contributor

@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 (1)
tests/skills_sync_memory_test.rs (1)

375-384: ⚠️ Potential issue | 🟡 Minor

Assertion logic still has edge-case gaps.

This loop has two issues:

  1. Empty doc_array: If no documents exist, the loop body never runs and the test passes without validating anything.
  2. Unrelated documents: If documents exist from other sources (e.g., prior test artifacts), the assertion fails on non-"periodic sync" titles.

Consider guarding with an explicit check:

🛠️ Proposed fix
     if let Some(doc_array) = docs.get("documents").and_then(|d| d.as_array()) {
+        // Only validate titles if skill published state (otherwise no docs expected)
+        let has_published_state = engine
+            .get_skill_state(skill_id)
+            .map(|s| !s.state.is_empty())
+            .unwrap_or(false);
+        
+        if has_published_state && !doc_array.is_empty() {
+            // At least one doc should contain "periodic sync" from onSync handler
+            let has_sync_doc = doc_array.iter().any(|doc| {
+                doc.get("title")
+                    .and_then(|t| t.as_str())
+                    .map(|t| t.contains("periodic sync"))
+                    .unwrap_or(false)
+            });
+            assert!(
+                has_sync_doc,
+                "Expected at least one document with 'periodic sync' title from skill/sync handler"
+            );
+        }
+        
         for doc in doc_array {
             let title = doc.get("title").and_then(|t| t.as_str()).unwrap_or("?");
             eprintln!("  Memory doc title: {title}");
-            assert!(
-                title.contains("periodic sync"),
-                "Expected title to contain 'periodic sync' (from skill/sync handler), got: {title}"
-            );
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/skills_sync_memory_test.rs` around lines 375 - 384, The loop over
docs.get("documents").and_then(|d| d.as_array()) can pass vacuously or fail on
unrelated docs; change the logic to first assert that doc_array is Some and not
empty, then iterate and collect titles (from the existing doc.get("title") ->
title variable) and assert that at least one title contains "periodic sync"
rather than asserting every title; update the code around docs/doc_array/title
to return a clear test failure if no matching title is found.
🧹 Nitpick comments (1)
src/openhuman/skills/registry_ops.rs (1)

311-338: Synchronous recursive copy may block the async runtime.

copy_dir_recursive uses synchronous std::fs operations. When called from skill_install (an async function), this could block the Tokio runtime thread if copying large skill directories.

Consider using tokio::task::spawn_blocking or tokio::fs for the copy operation:

♻️ Async-safe alternative
+async fn copy_dir_recursive_async(src: &Path, dst: &Path) -> Result<(), String> {
+    let src = src.to_path_buf();
+    let dst = dst.to_path_buf();
+    tokio::task::spawn_blocking(move || copy_dir_recursive(&src, &dst))
+        .await
+        .map_err(|e| format!("copy task panicked: {e}"))?
+}

Then in skill_install:

-            copy_dir_recursive(&local_skill, &skill_dir)?;
+            copy_dir_recursive_async(&local_skill, &skill_dir).await?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/skills/registry_ops.rs` around lines 311 - 338,
copy_dir_recursive performs blocking std::fs operations and can stall the async
runtime when called from the async function skill_install; move the whole
recursive copy work into a blocking task or convert it to async: either (a) wrap
the current copy_dir_recursive call with tokio::task::spawn_blocking and await
the join, or (b) rewrite copy_dir_recursive to use tokio::fs async APIs
(tokio::fs::create_dir_all, read_dir, copy, etc.) and call that from
skill_install; update references to the function name copy_dir_recursive (or
replace with an async copy_dir_recursive_async) and ensure proper error mapping
back to the existing Result<String, ...> usage in skill_install.
🤖 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/skills/registry_ops.rs`:
- Around line 27-30: The is_local_path function only checks for Unix '/' and
"file://" URIs so Windows absolute paths (e.g. "C:\...") are misclassified;
update is_local_path to also detect Windows drive-letter absolute paths by
either using std::path::Path::new(url).is_absolute() or adding a drive-letter
check (match r"^[A-Za-z]:[\\/]"). Keep existing checks for '/' and "file://",
but ensure Path::is_absolute or the regex covers Windows formats (both backslash
and forward slash) so Windows registry file paths are treated as local.

---

Duplicate comments:
In `@tests/skills_sync_memory_test.rs`:
- Around line 375-384: The loop over docs.get("documents").and_then(|d|
d.as_array()) can pass vacuously or fail on unrelated docs; change the logic to
first assert that doc_array is Some and not empty, then iterate and collect
titles (from the existing doc.get("title") -> title variable) and assert that at
least one title contains "periodic sync" rather than asserting every title;
update the code around docs/doc_array/title to return a clear test failure if no
matching title is found.

---

Nitpick comments:
In `@src/openhuman/skills/registry_ops.rs`:
- Around line 311-338: copy_dir_recursive performs blocking std::fs operations
and can stall the async runtime when called from the async function
skill_install; move the whole recursive copy work into a blocking task or
convert it to async: either (a) wrap the current copy_dir_recursive call with
tokio::task::spawn_blocking and await the join, or (b) rewrite
copy_dir_recursive to use tokio::fs async APIs (tokio::fs::create_dir_all,
read_dir, copy, etc.) and call that from skill_install; update references to the
function name copy_dir_recursive (or replace with an async
copy_dir_recursive_async) and ensure proper error mapping back to the existing
Result<String, ...> usage in skill_install.
🪄 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: 7487f606-5d17-458e-b5de-b2f164bf21c3

📥 Commits

Reviewing files that changed from the base of the PR and between bd264c5 and 283fb90.

📒 Files selected for processing (7)
  • .env.example
  • src/openhuman/skills/qjs_engine.rs
  • src/openhuman/skills/registry_ops.rs
  • tests/skills_debug_e2e.rs
  • tests/skills_notion_live.rs
  • tests/skills_rpc_e2e.rs
  • tests/skills_sync_memory_test.rs
✅ Files skipped from review due to trivial changes (1)
  • tests/skills_debug_e2e.rs

Comment on lines +27 to +30
/// Check if a URL is a local file path (absolute path or file:// URI).
fn is_local_path(url: &str) -> bool {
url.starts_with('/') || url.starts_with("file://")
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Windows support is expected/documented
rg -i 'windows|win32|msvc' Cargo.toml README.md .github/ -l 2>/dev/null | head -5
rg -n 'cfg.*windows' src/openhuman/skills/

Repository: tinyhumansai/openhuman

Length of output: 182


🏁 Script executed:

# Check Cargo.toml for supported targets
cat Cargo.toml | grep -A 20 -E '\[.*target.*\]|target\..*|windows|win32'

Repository: tinyhumansai/openhuman

Length of output: 794


🏁 Script executed:

# Check README.md for Windows references context
rg -B 2 -A 2 -i 'windows' README.md

Repository: tinyhumansai/openhuman

Length of output: 711


🏁 Script executed:

# Check how is_local_path is actually used in the codebase
rg 'is_local_path' src/ -B 2 -A 2

Repository: tinyhumansai/openhuman

Length of output: 1230


🏁 Script executed:

# Check if there are other path handling functions that might indicate Windows support approach
rg 'path|Path|PathBuf|normalize' src/openhuman/skills/registry_ops.rs -n | head -20

Repository: tinyhumansai/openhuman

Length of output: 1289


🏁 Script executed:

# Check the installer-smoke test to see if it tests Windows path handling
rg -B 5 -A 5 'installer-smoke' .github/workflows/ -A 20

Repository: tinyhumansai/openhuman

Length of output: 48


🏁 Script executed:

# Check if there's any Windows path handling elsewhere in the codebase
rg 'cfg.*windows|target_os.*windows|\\\\|drive|C:' src/ --type rs

Repository: tinyhumansai/openhuman

Length of output: 92


🏁 Script executed:

# Check the skill_install function to see how paths are actually passed to is_local_path
sed -n '233,260p' src/openhuman/skills/registry_ops.rs

Repository: tinyhumansai/openhuman

Length of output: 1377


🏁 Script executed:

# Check environment variable usage for paths on Windows
rg 'SKILLS_LOCAL_DIR|env::var' src/ -B 2 -A 2 --type rs

Repository: tinyhumansai/openhuman

Length of output: 92


🏁 Script executed:

# Check how registry_fetch is called to understand if Windows paths could be passed
rg 'registry_fetch' src/ -B 3 -A 3

Repository: tinyhumansai/openhuman

Length of output: 4706


🏁 Script executed:

# Check if file:// URI handling works on Windows for file paths
rg 'file://' src/ -B 2 -A 2

Repository: tinyhumansai/openhuman

Length of output: 3099


🏁 Script executed:

# Check if there are any other path normalization utilities in the codebase
rg 'normalize|canonicalize|path.*unix|path.*windows' src/ -i

Repository: tinyhumansai/openhuman

Length of output: 45613


Windows users cannot use absolute paths for local registry files. The is_local_path() function only recognizes Unix absolute paths (/) and file:// URIs. Windows paths like C:\path\to\registry.json will be treated as remote URLs, causing HTTP fetch failures.

Consider supporting Windows drive letters:

 fn is_local_path(url: &str) -> bool {
-    url.starts_with('/') || url.starts_with("file://")
+    url.starts_with('/')
+        || url.starts_with("file://")
+        || (cfg!(windows) && url.len() > 1 && url.chars().nth(1) == Some(':'))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Check if a URL is a local file path (absolute path or file:// URI).
fn is_local_path(url: &str) -> bool {
url.starts_with('/') || url.starts_with("file://")
}
/// Check if a URL is a local file path (absolute path or file:// URI).
fn is_local_path(url: &str) -> bool {
url.starts_with('/')
|| url.starts_with("file://")
|| (cfg!(windows) && url.len() > 1 && url.chars().nth(1) == Some(':'))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/skills/registry_ops.rs` around lines 27 - 30, The is_local_path
function only checks for Unix '/' and "file://" URIs so Windows absolute paths
(e.g. "C:\...") are misclassified; update is_local_path to also detect Windows
drive-letter absolute paths by either using
std::path::Path::new(url).is_absolute() or adding a drive-letter check (match
r"^[A-Za-z]:[\\/]"). Keep existing checks for '/' and "file://", but ensure
Path::is_absolute or the regex covers Windows formats (both backslash and
forward slash) so Windows registry file paths are treated as local.

@senamakel senamakel merged commit c134d96 into main Apr 2, 2026
14 checks passed
@senamakel senamakel deleted the fix/skills-sync branch April 4, 2026 21:55
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…mory (tinyhumansai#183)

* feat(debug): add script for Notion sync memory verification and enhance memory persistence

- Introduced `debug-notion-sync-memory.sh` to facilitate live testing of the Notion skill with memory verification.
- The script validates the full flow from skill start to memory persistence, ensuring required environment variables are set.
- Updated `handle_skills_sync` to change the RPC method from `skill/tick` to `skill/sync` for better clarity in the sync process.
- Implemented `persist_state_to_memory` function to ensure published ops state is saved to memory after sync, cron, and tick events.
- Enhanced end-to-end tests to validate memory persistence during skill sync and tick operations, ensuring robust functionality.

This update improves the debugging process for the Notion skill and enhances the overall reliability of memory operations.

* style: apply cargo fmt formatting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(memory): implement background memory-write worker for skill state persistence

- Introduced a `MemoryWriteJob` struct to encapsulate the details of memory write operations.
- Added a bounded background worker using `tokio::spawn` to handle memory writes asynchronously, improving performance and responsiveness.
- Updated `persist_state_to_memory` to queue memory write jobs instead of executing them directly, allowing for better flow control and error handling.
- Enhanced logging for memory persistence operations to provide clearer insights into success and failure cases.
- Modified event handling in `handle_message` to ensure state persistence occurs only after successful operations, reducing the risk of data loss.

This update significantly enhances the memory management capabilities of the skill instance, ensuring more reliable state persistence.

* feat(skills): enhance skills synchronization and memory persistence

- Added detailed debug logging in `handle_skills_sync` to track skill synchronization events, improving observability during skill operations.
- Updated `persist_state_to_memory` to include a memory write transaction, ensuring state persistence is handled more robustly and efficiently.
- Enhanced event handling in `handle_message` to ensure memory writes are queued correctly, improving the reliability of state persistence during skill operations.

These changes improve the overall functionality and reliability of skills synchronization and memory management.

* feat(skills): enhance skills synchronization and memory persistence

- Added detailed debug logging in `handle_skills_sync` to track skill synchronization events, improving observability during skill operations.
- Updated `persist_state_to_memory` to include a `memory_write_tx` parameter, allowing for more efficient state persistence in the event loop.
- Enhanced tests for skills synchronization to ensure robust memory handling and state persistence during skill operations.

This update improves the reliability and traceability of skills synchronization processes, ensuring better memory management and debugging capabilities.

* refactor(tests): update JSON-RPC sync handling in end-to-end tests

- Enhanced comments in `json_rpc_skills_runtime_start_tools_call_stop` to clarify the sync process routing through the `skill/sync` RPC.
- Updated assertions in `skills_sync_rpc_calls_on_sync_not_on_tick` to ensure proper handling of the new sync flow, verifying that the `skills_sync` method routes correctly to `onSync`.
- Improved logging for better observability during skill synchronization tests, ensuring that the expected behavior aligns with the updated RPC structure.

These changes improve the clarity and reliability of the end-to-end tests related to skills synchronization.

* feat(env): enhance environment configuration for skills development

- Updated `.env.example` to include `SKILLS_LOCAL_DIR` for local skills source directory, allowing developers to specify a path for skill discovery and installation.
- Improved comments in the environment file to clarify the usage of `SKILLS_REGISTRY_URL` for both remote and local paths, enhancing the development experience.
- Refactored `qjs_engine.rs` to prioritize the `SKILLS_LOCAL_DIR` for skill source directory resolution, improving local development workflows.
- Added utility functions in `registry_ops.rs` to support local file path handling for skill registries, enhancing flexibility in skill management.

These changes streamline the development process for skills by providing clearer configuration options and improving local development capabilities.

* feat(tests): enhance skills directory discovery in test files

- Updated `try_find_skills_dir` function across multiple test files to include support for the `SKILLS_LOCAL_DIR` environment variable, improving the flexibility of skills directory resolution.
- Enhanced comments to clarify the order of directory search priorities, ensuring better understanding for developers.
- Improved error handling and logging for cases where the specified directory does not exist, aiding in debugging and test reliability.

These changes streamline the skills directory discovery process in tests, enhancing the overall development experience.

* feat(tests): enhance skills directory discovery in test files

- Updated `try_find_skills_dir` function to include support for the `SKILLS_LOCAL_DIR` environment variable, allowing for more flexible skills directory resolution.
- Improved documentation to clarify the priority order for skills directory discovery.
- Refactored multiple test files to utilize the updated skills directory discovery logic, enhancing consistency and maintainability across tests.

These changes streamline the skills directory discovery process in tests, improving the overall testing framework.

* refactor(tests): streamline memory client verification in Notion live tests

- Updated the memory client verification process in `notion_live_with_real_data` to utilize `MemoryClient::new_local()` for improved clarity and consistency.
- Enhanced comments to clarify the memory store check location and removed redundant error handling for workspace directory creation.
- Simplified the error logging to focus on the memory client creation failure, improving readability and maintainability of the test code.

These changes enhance the reliability of memory verification in the Notion live tests, ensuring a clearer understanding of the memory client initialization process.

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.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.

1 participant