Skip to content

feat(skills,tools): schema↔serde roundtrip tests, triggers field, bus subscriber, registry integrity#2718

Open
zahica1234 wants to merge 2 commits into
tinyhumansai:mainfrom
zahica1234:fix/schema-serde-roundtrip-tests
Open

feat(skills,tools): schema↔serde roundtrip tests, triggers field, bus subscriber, registry integrity#2718
zahica1234 wants to merge 2 commits into
tinyhumansai:mainfrom
zahica1234:fix/schema-serde-roundtrip-tests

Conversation

@zahica1234
Copy link
Copy Markdown
Contributor

@zahica1234 zahica1234 commented May 27, 2026

Summary

Four independent, focused improvements that collectively close the parameters_schema()serde alignment gap and bring the skills event-trigger pipeline from stub to working.


1. Schedule serde roundtrip tests (tools/impl/cron/add.rs)

The CronAddTool deserializes its schedule argument with serde_json::from_value::<Schedule>. No test previously verified that the JSON shapes documented in parameters_schema() actually deserialize correctly — the same class of silent mismatch that caused issue #2252 (window_days).

Added 7 tests:

  • Each Schedule variant (Cron, At, Every) deserializes from the schema-documented shape
  • Optional tz field round-trips correctly
  • Missing kind fails with a clear error
  • Unknown kind value fails with a clear error
  • Schema required array contains name and schedule

2. triggers: field in SkillFrontmatter (skills/ops_types.rs)

Added pub triggers: Vec<String> between allowed_tools and extra. Each entry is a trigger pattern of the form "domain" or "domain/event_slug" (e.g. "composio", "cron", "channel/inbound_message"). A bare domain (no slash) matches any event in that domain. Fully documented with cross-references to skills::bus.

3. Full TriggeredSkillSubscriber implementation (skills/bus.rs)

Replaced the 17-line no-op stub with a complete implementation:

  • TriggerPattern — parses "domain" or "domain/event_slug" strings; normalises to lowercase; "domain/*" treated as bare domain
  • TriggeredSkillIndex — built from &[Skill] at startup; matching_skills(&event) returns names of skills whose patterns match; entries sorted for deterministic logging
  • TriggeredSkillSubscriberEventHandler impl that logs matched skills on each DomainEvent; activation handoff left to the integration layer (no harness context needed here)
  • register_triggered_skill_subscriber() — public API, returns None when no skills declare triggers
  • register_skill_cleanup_subscriber() — kept as a safe no-op for call-site backward compat

Includes 20+ unit tests covering parse edge cases, domain matching, index build/sort/dedup, and the matching logic.

4. Tool registry integrity test (tool_registry/ops.rs)

Added all_registry_entries_have_non_empty_name_and_description — asserts every entry produced by registry_entries() has a non-blank name and description. Catches silent metadata gaps (e.g. an MCP tool with an empty description) before they reach the LLM tool surface.


Testing

cargo check   # clean (only pre-existing warnings in slack_backfill.rs)
cargo fmt     # no diff

All new tests are in-module #[cfg(test)] blocks and run with cargo test.


🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Skills can declare domain-based trigger patterns (domain or domain/event) to be matched against incoming events; the system builds a deterministic index and logs matched skills when events occur.
    • Missing trigger declarations are treated as empty (no triggers).
  • Tests & Validation

    • Added validation ensuring registry entries have non-empty names/descriptions.
    • Expanded tests for cron schedule parameter parsing and schema requirements.

Review Change Stack

… subscriber, registry integrity

- cron/add.rs: add 7 Schedule serde roundtrip tests covering Cron/At/Every
  variants, optional tz field, missing-kind and unknown-kind rejection, and
  schema required-field contract. Guards the serde↔schema alignment gap that
  caused the window_days silent-failure class of bugs.

- skills/ops_types.rs: add `triggers: Vec<String>` field to SkillFrontmatter
  between allowed_tools and extra. Each entry is a trigger pattern of the form
  "domain" or "domain/event_slug"; bare domain matches any event in that domain.
  Documented with full cross-references to skills::bus.

- skills/bus.rs: replace 17-line no-op stub with full implementation:
  TriggerPattern (parse + matches), TriggeredSkillIndex (build, is_empty, len,
  domains, matching_skills), TriggeredSkillSubscriber (EventHandler impl),
  register_triggered_skill_subscriber(), register_skill_cleanup_subscriber()
  no-op kept for backward compat. Includes 20+ unit tests.

- tool_registry/ops.rs: add all_registry_entries_have_non_empty_name_and_description
  integrity test — asserts every registered tool has a non-blank name and
  description, catching silent metadata gaps before they reach the LLM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@zahica1234 zahica1234 requested a review from a team May 27, 2026 00:11
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

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: cdbd4967-14d0-4775-9b00-ab1d40a8412c

📥 Commits

Reviewing files that changed from the base of the PR and between de00aa2 and e920e5f.

📒 Files selected for processing (2)
  • src/openhuman/skills/bus.rs
  • src/openhuman/tools/impl/cron/add.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/skills/bus.rs
  • src/openhuman/tools/impl/cron/add.rs

📝 Walkthrough

Walkthrough

Adds a triggers field to skill frontmatter, implements TriggerPattern parsing/matching and a deterministic TriggeredSkillIndex, registers a global event subscriber that logs matched skills per domain event, and adds unit tests plus registry and cron schema validation tests.

Changes

Triggered Skills Event Bus & Quality Improvements

Layer / File(s) Summary
Skill trigger data model
src/openhuman/skills/ops_types.rs
SkillFrontmatter gains triggers: Vec<String> field with #[serde(default)], documented to accept domain or domain/slug patterns.
Module docs and imports
src/openhuman/skills/bus.rs
Module-level docs and imports for the triggered-skill subscriber and supporting types.
Trigger pattern parsing and matching
src/openhuman/skills/bus.rs
TriggerPattern parses and normalizes trigger strings into lowercase domain and optional slug, treats empty/* slug as domain-only, and exposes matches() (currently matches by domain only).
Triggered skill indexing
src/openhuman/skills/bus.rs
TriggeredSkillIndex builds a sorted, deterministic index from skill triggers, exposes unique domains and matching skill-name queries for DomainEvent.
Event handler and registration API
src/openhuman/skills/bus.rs
TriggeredSkillSubscriber implements EventHandler to compute matched skills and emit debug logs; register_triggered_skill_subscriber() builds the index, returns None when empty, logs metadata, and registers a global subscriber; register_skill_cleanup_subscriber() remains a safe no-op.
TriggerPattern tests
src/openhuman/skills/bus.rs
Unit tests covering parsing normalization, wildcard/domain-only semantics, and slug-qualified pattern behavior.
TriggeredSkillIndex tests
src/openhuman/skills/bus.rs
Tests for index building: skipping skills with no triggers, deterministic name sorting, unique sorted domains, and matching results across multiple skills/events.
Register cleanup test
src/openhuman/skills/bus.rs
Test confirming repeated calls to register_skill_cleanup_subscriber() are side-effect-free.
Tool registry name and description validation
src/openhuman/tool_registry/ops.rs
New test ensures each registry entry has a non-empty name and description after trimming.
Cron tool schema and deserialization validation
src/openhuman/tools/impl/cron/add.rs
Tests that Schedule deserializes from schema-documented JSON shapes, rejects missing/unknown kind, and that the tool schema requires name and schedule.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

working

🐰 A skills bus comes to town,
Triggers parsed both up and down,
Domains sorted, matches logged bright,
Tests hop in to check the light,
Hooray, the rabbits code tonight! 🐇✨

🚥 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 title accurately and specifically summarizes the PR's main changes: schema-serde roundtrip tests, triggers field addition, bus subscriber implementation, and registry integrity testing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 27, 2026
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: 3

🧹 Nitpick comments (1)
src/openhuman/skills/bus.rs (1)

152-154: ⚡ Quick win

Align the handler name with the repo's bus naming convention.

The subscriber type already carries the Subscriber suffix, so the runtime name should stay at the stable <domain>::<purpose> form rather than skills::triggered_skill_subscriber.

Suggested change
     fn name(&self) -> &str {
-        "skills::triggered_skill_subscriber"
+        "skills::triggered_skill"
     }

As per coding guidelines, src/openhuman/**/bus.rs: Register domain event handlers in <domain>/bus.rs with <Purpose>Subscriber pattern and name() returning '<domain>::<purpose>'.

🤖 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/skills/bus.rs` around lines 152 - 154, The runtime name
currently returns "skills::triggered_skill_subscriber" which duplicates the
Subscriber suffix; update the name() implementation on the subscriber type (fn
name(&self) -> &str) to return the stable domain-purpose form
"skills::triggered_skill" so it follows the '<domain>::<purpose>' convention
while the type keeps the 'Subscriber' suffix.
🤖 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/skills/bus.rs`:
- Around line 64-73: The matches() method currently ignores self.event_slug
causing event-specific patterns to match entire domains; update matches (in the
matches function) to reject slugged patterns until a stable event slug API
exists by returning false when self.event_slug.is_some(), e.g. after the domain
check: if self.event_slug.is_some() { return false }, and add a TODO referencing
DomainEvent::slug() so we can replace the rejection with a proper slug
comparison once DomainEvent exposes slug().
- Around line 95-107: The current filter_map drops malformed trigger strings
silently when mapping frontmatter.triggers via TriggerPattern::parse; update the
closure in the iterator that builds (skill.name.clone(), patterns) so that any
parse failure emits a structured warning (including skill.name and the raw
trigger text) and/or appends a message to the skill's warnings list instead of
swallowing it; specifically, when iterating skill.frontmatter.triggers call
TriggerPattern::parse, on Err log a grep-friendly warning (with skill.name and
trigger) and continue, and only collect successful parses into patterns so
callers still get the successful patterns while malformed entries are visible
via logs or skill warnings.

In `@src/openhuman/tools/impl/cron/add.rs`:
- Around line 822-835: The test cron_add_tool_schema_requires_name_and_schedule
currently asserts a hardcoded schema; replace that by calling
CronAddTool::parameters_schema() to obtain the real schema and then inspect its
"required" array for "name" and "schedule". Specifically, in the test, invoke
CronAddTool::parameters_schema(), parse or index into its "required" field the
same way you did for the hardcoded schema, and assert that the required array
contains "name" and "schedule".

---

Nitpick comments:
In `@src/openhuman/skills/bus.rs`:
- Around line 152-154: The runtime name currently returns
"skills::triggered_skill_subscriber" which duplicates the Subscriber suffix;
update the name() implementation on the subscriber type (fn name(&self) -> &str)
to return the stable domain-purpose form "skills::triggered_skill" so it follows
the '<domain>::<purpose>' convention while the type keeps the 'Subscriber'
suffix.
🪄 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: 762521c2-159a-4900-a0b4-7a50eb2ce874

📥 Commits

Reviewing files that changed from the base of the PR and between 99ad663 and de00aa2.

📒 Files selected for processing (4)
  • src/openhuman/skills/bus.rs
  • src/openhuman/skills/ops_types.rs
  • src/openhuman/tool_registry/ops.rs
  • src/openhuman/tools/impl/cron/add.rs

Comment thread src/openhuman/skills/bus.rs
Comment thread src/openhuman/skills/bus.rs
Comment thread src/openhuman/tools/impl/cron/add.rs
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR adds event-triggered skills support — a solid improvement to the skills subsystem. The code is well-structured, documented, and tested. However, there's one critical gap that needs fixing.

Key Changes

  1. Bus subscriber — Complete implementation of TriggeredSkillSubscriber with TriggerPattern parsing and skill indexing
  2. Frontmatter field — New triggers: Vec<String> field in SkillFrontmatter
  3. Tests — Comprehensive roundtrip tests for serde schemas and registry integrity

Issues

[MAJOR] Incomplete trigger pattern matching

The TriggerPattern struct supports patterns like "domain/event_slug" (e.g., "composio/trigger_received"), documented and parsed correctly. However, the matches() function only checks the domain and ignores the event_slug field entirely.

This means:

  • Pattern "composio/trigger_received" will match all composio events, not just trigger_received
  • Users will get silent behavior mismatch — the pattern looks specific but isn't

The code has a TODO comment acknowledging slug matching is pending, but the API claims to support it (in struct docs, pattern examples, and tests). This creates a footgun.

Fix options:

  1. Implement slug matching using DomainEvent discriminant (preferred, but needs DomainEvent API changes)
  2. Remove slug support — only accept bare domain patterns like "composio" (simpler, but breaks the documented API)
  3. Add a clear warning in the struct docs that slugs are ignored pending a future API addition

I'd go with option 1 — the hard way is the right way. If that's not feasible, option 2 + update the docs to reflect what actually works.

What's Good

  • Well-documented — clear comments on trigger format, cross-references to modules, intent explanation
  • Comprehensive tests — parsing edge cases, index building, skill matching, serde roundtrips all covered
  • Good patterns — Arc for shared ownership, OnceLock usage hints in docs, sorted outputs for determinism
  • Backward compatregister_skill_cleanup_subscriber() kept as no-op, triggers: field defaults to empty
  • Registry integrity check — catches silent metadata gaps before they hit the LLM

No Other Issues

No lint warnings, no unwraps without messages, no PII in logs, no test coverage gaps. The serde roundtrip tests are exactly what #2252 needed.

}

/// Returns true when this pattern matches the given event.
pub fn matches(&self, event: &DomainEvent) -> bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[major] This function only checks domain and ignores event_slug. A pattern like "composio/trigger_received" will match all composio events, not just trigger_received.

The struct docs and tests claim slug support works, but it doesn't (pending a stable slug() method on DomainEvent). This creates silent behavior mismatch.

Either: (1) implement slug matching properly once DomainEvent exposes a slug method, (2) remove slug support and only accept bare domains, or (3) clearly document that slug patterns are ignored for now.

- bus.rs: slug-qualified trigger patterns now return false from matches()
  instead of silently matching the entire domain; guards against over-firing
  until DomainEvent::slug() exists
- bus.rs: malformed trigger strings now emit log::warn instead of being
  swallowed silently by filter_map
- bus.rs: EventHandler::name() corrected to "skills::triggered_skill"
  (dropped redundant _subscriber suffix per naming convention)
- bus.rs: add test slugged_pattern_rejected_until_slug_api_exists to pin
  the intentional rejection behaviour
- add.rs: replace hardcoded JSON fixture in schema test with a real
  CronAddTool::parameters_schema() call so future field renames break the test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@graycyrus
Copy link
Copy Markdown
Contributor

@zahica1234 unresolved review feedback — please address before we review.

@graycyrus
Copy link
Copy Markdown
Contributor

Unresolved review feedback from coderabbitai[bot] — please address before we review.

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Summary

Four well-crafted changes closing the schema↔serde alignment gap and bringing the event-trigger pipeline from stub to working.

Prior Finding Resolved

Your fix for the slug-matching issue is the right call: reject slug-qualified patterns until DomainEvent exposes slug(), with explicit documentation, a test that verifies the safe default behavior, and a TODO for the proper implementation. This prevents silent overfiring and makes the limitation crystal clear. Approved.

Review

bus.rs: Full implementation of TriggeredSkillIndex and TriggeredSkillSubscriber. Pattern parsing is solid, index building is clean (sorted for determinism), and matching logic is safe. Logging is appropriate. Public API is clear with good usage example.

ops_types.rs: triggers field properly placed, well-documented with cross-references to skills::bus, defaults sensibly.

tool_registry/ops.rs: Defensive integrity test catches empty name/description metadata gaps before reaching the LLM surface. Good catch.

cron/add.rs: Comprehensive serde roundtrip tests covering all Schedule variants, optional tz, missing/unknown kind, and schema requirements. Prevents the #2252 class of silent field-name mismatches.

Test coverage: 20+ new tests covering parse edge cases, domain/slug matching, index building/sorting, skill name determinism, and schema validation. Cargo check, fmt, test all pass. Coverage gate pass (80%+ on diff).

CI: All 30 checks green. No conflicts.

Breaking changes: None. Bare domain patterns work immediately; slug patterns are safely deferred. New API is opt-in, returns None when no triggers declared.

This is solid work — ready to merge.

@oxoxDev oxoxDev assigned oxoxDev and unassigned oxoxDev May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants