Skip to content

feat(skills): catalog-driven run_skill flow + mid-session skill refresh#3722

Merged
sanil-23 merged 6 commits into
tinyhumansai:mainfrom
sanil-23:feat/skill-catalog-runskill
Jun 17, 2026
Merged

feat(skills): catalog-driven run_skill flow + mid-session skill refresh#3722
sanil-23 merged 6 commits into
tinyhumansai:mainfrom
sanil-23:feat/skill-catalog-runskill

Conversation

@sanil-23

@sanil-23 sanil-23 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Surface installed skills via the compact ## Installed Skills catalogue and run them in an isolated skill_executor worker via run_skill — instead of the brittle per-turn SKILL.md body injection.
  • skill_executor does what its bounded tools allow and emits a ## Handoff Plan for steps it can't perform (integrations, memory); the orchestrator completes those under the approval gate.
  • Remove the keyword-matched SKILL.md injection (it baked full skill text permanently into chat history) and delete the now-unused workflows::inject module.
  • Mid-session skill refresh: a new DomainEvent::WorkflowsChanged (install/uninstall/create) drives refresh_workflows at the turn boundary + a one-shot [skills update] user-turn note — mirroring the existing integration/MCP mid-session pattern (upstream/main added the identical mechanism for MCP servers in parallel).

Problem

Skills were surfaced two ways, both flawed:

  1. A keyword/name/tag matcher prepended the entire SKILL.md body onto the user turn and stored it — so full skill text lingered permanently in chat history (microcompact only clears tool results, not user messages), and it fired unintentionally on incidental wording.
  2. The catalogue steered to run_workflow (the heavy autonomous orchestrator run) rather than the bounded, isolated skill_executor.

Separately, ## Installed Skills is a session-build snapshot: a skill installed mid-conversation didn't appear until restart.

Solution

  • Discovery, not injection: the compact catalogue is the standing skill surface; bodies never enter the conversation. Execution goes through run_skillskill_executor (isolated worker).
  • Capability gap → handoff: when a skill needs a tool skill_executor lacks, it returns a ## Handoff Plan; the orchestrator executes those steps under the approval gate.
  • Mid-session refresh: the system-prompt ## Installed Skills block is frozen at turn 1 for KV-cache stability, so — like the MCP mechanism — the user-turn [skills update] announcement surfaces a mid-session install to the live session; run_skill loads/runs it fresh from disk. Event-gated by WorkflowsChanged so the hot path does no extra disk scans.

Validated end-to-end on the managed openhuman provider (live app): catalogue → run_skillskill_executor (loaded SKILL.md, resolved the managed Python runtime, ran a shell step under the approval gate) → real result. Also exercised the capability-gap path with a Gmail skill (google-workspace): skill_executor returned a ## Handoff Plan, and the orchestrator completed the read via integrations_agentGMAIL_FETCH_EMAILS.

Submission Checklist

  • Tests added — 7 unit tests: render_installed_skills (lists + steers to run_skill; empty omitted), skill_announcement_note (empty→None; mentions ids + run_skill), refresh_workflows (discovers an on-disk skill, parks it, idempotent), drain_skill_events/ensure_skill_events_listener (drains WorkflowsChanged once), DomainEvent::WorkflowsChanged (domain/variant_name). All passing locally.
  • Diff coverage ≥ 80% — new tests cover the catalogue render, the run_skill steering, refresh_workflows, the event drain, the announcement note, and the new event variant; field/init lines are exercised via agent build + the existing turn tests.
  • Coverage matrix updated — N/A: behaviour-only change to existing agent-harness internals; no new feature row.
  • All affected feature IDs listed under ## RelatedN/A: no matrix rows touched.
  • No new external network dependencies introduced — only an internal bus event.
  • Manual smoke checklist updated — N/A: no release-cut surface changed (agent-harness internals + prompts).
  • Linked issue closed — N/A: no tracking issue.

Impact

  • Desktop/CLI agent harness only. Removes a source of chat-history bloat (no more full SKILL.md in user turns). No UI/schema changes. Note: the claude-code inference provider still can't run skills due to a separate stale DISALLOWED_CC_BUILTINS list (out of scope); the managed provider works end-to-end.

Summary by CodeRabbit

Release Notes

  • New Features

    • Agent sessions now receive real-time notifications when skills are installed mid-conversation, allowing dynamic catalog refresh without restarting.
    • Skills execute in isolated workers with improved step-by-step handling; unsupported steps generate handoff plans for agent review.
  • Bug Fixes

    • Removed per-turn skill body injection, preventing chat history bloat and improving context efficiency.

Surface installed skills via the compact `## Installed Skills` catalogue and
run them in an isolated worker, instead of the brittle per-turn SKILL.md body
injection that polluted chat history.

- Orchestrator catalogue steers to `run_skill` (isolated `skill_executor`
  worker) with handoff-plan guidance, not `run_workflow`.
- `skill_executor` does what it can with its bounded tools and emits a
  `## Handoff Plan` for steps needing tools it lacks (integrations, memory);
  the orchestrator completes those under the approval gate.
- Remove the keyword-matched SKILL.md body injection (left full skill text
  permanently in chat history); delete the now-unused `workflows::inject`
  module. Discovery is the compact catalogue; execution is via `run_skill`.
- Mid-session skill refresh, mirroring the integration/MCP pattern: a new
  `DomainEvent::WorkflowsChanged` (install/uninstall/create) drives
  `refresh_workflows` at the turn boundary plus a one-shot `[skills update]`
  user-turn announcement, so a skill installed mid-conversation appears in the
  catalogue and is runnable without a restart.

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23 sanil-23 requested a review from a team June 16, 2026 12:49
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 22a916ea-721d-4894-87ef-939b5ea5e6b8

📥 Commits

Reviewing files that changed from the base of the PR and between f1ce3e4 and 1868144.

📒 Files selected for processing (4)
  • src/openhuman/agent/harness/session/tests.rs
  • src/openhuman/agent/harness/session/turn/tools.rs
  • src/openhuman/skill_runtime/agent/skill_executor/prompt.md
  • src/openhuman/workflows/ops_create.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/skill_runtime/agent/skill_executor/prompt.md
  • src/openhuman/agent/harness/session/turn/tools.rs

📝 Walkthrough

Walkthrough

Replaces the per-turn SKILL.md body injection model with an event-driven live-refresh mechanism. Workflow create/install/uninstall operations now publish a DomainEvent::WorkflowsChanged event; agent sessions subscribe, drain, and reload their skill catalogue mid-conversation, injecting a one-shot announcement note. The inject.rs module is removed entirely, and orchestrator/skill-executor prompts are updated to use run_skill with ## Handoff Plan semantics.

Changes

Live Skill Refresh and inject.rs Removal

Layer / File(s) Summary
WorkflowsChanged event contract
src/core/event_bus/events.rs, src/core/event_bus/events_tests.rs
Adds DomainEvent::WorkflowsChanged { reason: String } variant and updates domain() and variant_name() routing to classify it under the "workflow" domain; includes unit test coverage for domain and name mapping.
Workflow ops publish WorkflowsChanged
src/openhuman/workflows/ops_create.rs, src/openhuman/workflows/ops_install.rs
create_workflow_inner, install_workflow_from_url, and uninstall_workflow each publish WorkflowsChanged with a reason string ("create", "install", "uninstall") after their operations succeed.
Agent struct: new skill-tracking fields and builder init
src/openhuman/agent/harness/session/types.rs, src/openhuman/agent/harness/session/builder/setters.rs
Agent gains skill_events_rx (lazy broadcast receiver), pending_skill_announcement (Vec), and announced_skills (HashSet); builder initializes them to None/empty.
Session event subscription, drain, and workflow refresh
src/openhuman/agent/harness/session/turn/tools.rs, src/openhuman/agent/harness/session/tests.rs
ensure_skill_events_listener lazily subscribes to WorkflowsChanged; drain_skill_events drains broadcast events handling lag/closed states; refresh_workflows reloads metadata, detects set changes, and enqueues new skill IDs into pending_skill_announcement guarded by announced_skills; includes event-listener drain and filesystem-backed refresh-discovery tests.
Turn-level skill refresh and one-shot announcement injection
src/openhuman/agent/harness/session/turn/core.rs, src/openhuman/agent/harness/session/turn/mod.rs, src/openhuman/agent/harness/session/turn_tests.rs
Agent::turn() calls ensure_skill_events_listener at turn start, then drain_skill_events + refresh_workflows on non-first turns; removes old SKILL.md body injection; introduces skill_announcement_note helper and injects one-shot announcement from pending_skill_announcement into user turn content; includes unit tests for announcement-note generation.
inject.rs removal and workflows module cleanup
src/openhuman/workflows/mod.rs, src/openhuman/workflows/README.md
Removes pub mod inject from module exports and updates README.md to document the new run_skill isolation model instead of per-turn SKILL.md body matching/injection.
Orchestrator and skill-executor prompt updates
src/openhuman/agent_registry/agents/orchestrator/prompt.md, src/openhuman/agent_registry/agents/orchestrator/prompt.rs, src/openhuman/skill_runtime/agent/skill_executor/prompt.md
Orchestrator prompts switch from run_workflow/workflow_id to run_skill/skill name with ## Handoff Plan semantics and approval-gate routing; skill executor prompt adds runtime-resolution steps, unsupported-tool handoff rules, and exact ## Handoff Plan formatting; includes unit test coverage for orchestrator installed-skills section.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AgentTurn as Agent::turn()
  participant EventBus as DomainEvent Bus
  participant WorkflowOps as ops_install / ops_create
  participant WorkflowStore as Disk / Workflow Metadata

  WorkflowOps->>EventBus: publish WorkflowsChanged { reason }
  User->>AgentTurn: next user message
  AgentTurn->>AgentTurn: ensure_skill_events_listener()
  AgentTurn->>EventBus: drain_skill_events() → changed=true
  AgentTurn->>WorkflowStore: refresh_workflows("event") → load metadata
  WorkflowStore-->>AgentTurn: updated workflow list
  AgentTurn->>AgentTurn: enqueue new IDs → pending_skill_announcement
  AgentTurn->>AgentTurn: skill_announcement_note(pending_skills) → prepend to user content
  AgentTurn-->>User: turn executes with refreshed skill catalogue
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • tinyhumansai/openhuman#3457: The skill-registry install flow delegates to workflows::ops_install::install_workflow_from_url, which this PR extends to publish WorkflowsChanged events triggering the new live-session refresh path.
  • tinyhumansai/openhuman#3412: Both PRs modify Agent::turn() and the SKILL.md injection path; #3412 focuses on skills→workflows rename while this PR removes per-turn injected skill bodies and adds event-driven refresh/announcements.

Suggested reviewers

  • senamakel
  • graycyrus

Poem

🐇 Hops along the event bus lane,
No more injecting skill bodies — plain!
WorkflowsChanged rings the bell,
The agent wakes and all is well.
run_skill now leads the way,
With Handoff Plans to save the day! ✨

🚥 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 'feat(skills): catalog-driven run_skill flow + mid-session skill refresh' directly and specifically summarizes the main changes: a new catalog-driven skill execution model via run_skill with mid-session refresh capability, which aligns perfectly with the PR's core objective of replacing keyword-matched injection with a catalog-driven approach.
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.


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

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. labels Jun 16, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b358dcaea

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +395 to +400
log::info!(
"[agent_loop] installed-skills set changed ({trigger}): {} -> {} skills; refreshing catalogue (system prompt section rebuilds next turn)",
self.workflows.len(),
latest.len()
);
self.workflows = latest;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh the prompt-visible skills catalog

When a workflow change is observed after the first turn, this only replaces self.workflows; it does not update the already-stored system message that contains ## Installed Skills. In the inspected Agent::turn flow, the system prompt is pushed only when history is empty and later turns deliberately reuse it, so uninstalling a skill leaves it advertised to the model and installing/updating one does not actually refresh the catalog the comment/log claim is being rebuilt. This can make live sessions call removed skills or miss changed skills until a new session starts; the refreshed catalog needs to be surfaced through the stored prompt or a per-turn injected catalog/removal note.

Useful? React with 👍 / 👎.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 16, 2026

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

🤖 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/agent/harness/session/turn/core.rs`:
- Around line 174-184: The skills event listener is not initialized early enough
in the turn lifecycle, causing `WorkflowsChanged` events to be missed before the
first `drain_skill_events()` call at line 182. Arm the skills event listener at
the beginning of turn initialization (next to where the composio listener is set
up) rather than conditionally in the drain call, so that the listener is
subscribed and ready to capture workflow changes from the start of the session.
This ensures that mid-session skill installs/uninstalls are reliably detected
even on the first turn when `drain_skill_events()` is first invoked.

In `@src/openhuman/agent/harness/session/turn/tools.rs`:
- Around line 249-305: Add comprehensive unit tests for the new stateful session
behavior introduced by the functions `ensure_skill_events_listener`,
`drain_skill_events`, and `refresh_workflows`. Create tests that cover lag
handling (TryRecvError::Lagged case), closed receiver handling
(TryRecvError::Closed case), no-op set comparisons when no changes occur, and
one-shot announcement queuing behavior. Follow the coding guidelines by
implementing these tests either as inline tests using #[cfg(test)] mod tests or
in a sibling *_tests.rs file.

In `@src/openhuman/skill_runtime/agent/skill_executor/prompt.md`:
- Line 35: The fenced code block in the prompt.md file is missing a language
specification, which violates markdownlint rule MD040. Add the language tag
`text` immediately after the opening triple backticks (``` becomes ```text) on
the line before the "## Completed" section to properly specify the code block
language.

In `@src/openhuman/workflows/ops_create.rs`:
- Around line 386-392: Add a regression test to verify that the
`create_workflow_inner` function publishes a `WorkflowsChanged` event with
reason "create" when a workflow is successfully created. Create the test either
inline in `src/openhuman/workflows/ops_create.rs` using a `#[cfg(test)] mod
tests` block or in a sibling `ops_create_tests.rs` file following the project's
coding guidelines. The test should mock or verify the `publish_global` call to
ensure the `DomainEvent::WorkflowsChanged` event is emitted during workflow
creation, preventing regression of this live-refresh behavior.
🪄 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: 02e5f62b-6476-44b1-ae21-44e4b69003c5

📥 Commits

Reviewing files that changed from the base of the PR and between eae73c9 and 0b358dc.

📒 Files selected for processing (14)
  • src/core/event_bus/events.rs
  • src/openhuman/agent/harness/session/builder/setters.rs
  • src/openhuman/agent/harness/session/turn/core.rs
  • src/openhuman/agent/harness/session/turn/mod.rs
  • src/openhuman/agent/harness/session/turn/tools.rs
  • src/openhuman/agent/harness/session/types.rs
  • src/openhuman/agent_registry/agents/orchestrator/prompt.md
  • src/openhuman/agent_registry/agents/orchestrator/prompt.rs
  • src/openhuman/skill_runtime/agent/skill_executor/prompt.md
  • src/openhuman/workflows/README.md
  • src/openhuman/workflows/inject.rs
  • src/openhuman/workflows/mod.rs
  • src/openhuman/workflows/ops_create.rs
  • src/openhuman/workflows/ops_install.rs
💤 Files with no reviewable changes (1)
  • src/openhuman/workflows/inject.rs

Comment thread src/openhuman/agent/harness/session/turn/core.rs Outdated
Comment on lines +249 to +305
/// Lazily attach this session to the global event bus so it can observe
/// [`crate::core::event_bus::DomainEvent::WorkflowsChanged`] (skill
/// install / uninstall / create). Mirror of
/// [`Self::ensure_composio_integrations_listener`].
pub(in super::super) fn ensure_skill_events_listener(&mut self) {
if self.skill_events_rx.is_some() {
return;
}
if let Some(bus) = crate::core::event_bus::global() {
self.skill_events_rx = Some(bus.raw_receiver());
log::debug!(
"[agent_loop] armed installed-skills listener for session='{}'",
self.event_session_id
);
}
}

/// Drain pending [`crate::core::event_bus::DomainEvent::WorkflowsChanged`]
/// events. Returns `true` when at least one was observed (or the listener
/// lagged) and the caller should re-scan the installed skill set via
/// [`Self::refresh_workflows`]. Mirror of
/// [`Self::drain_composio_integrations_changed_events`].
pub(in super::super) fn drain_skill_events(&mut self) -> bool {
self.ensure_skill_events_listener();
let Some(rx) = self.skill_events_rx.as_mut() else {
return false;
};
use tokio::sync::broadcast::error::TryRecvError;

let mut saw_signal = false;
let mut closed = false;
loop {
match rx.try_recv() {
Ok(crate::core::event_bus::DomainEvent::WorkflowsChanged { reason }) => {
saw_signal = true;
log::info!("[agent_loop] received installed-skills changed event ({reason})");
}
Ok(_) => {}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Lagged(skipped)) => {
saw_signal = true;
log::warn!(
"[agent_loop] installed-skills listener lagged by {} event(s); forcing catalogue re-check",
skipped
);
}
Err(TryRecvError::Closed) => {
closed = true;
break;
}
}
}
if closed {
self.skill_events_rx = None;
}
saw_signal
}

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.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add focused tests for skill-event drain + workflow-refresh behavior.

These new flows (ensure_skill_events_listener, drain_skill_events, refresh_workflows) introduce stateful session behavior but ship without targeted tests for lag/closed handling, no-op set comparisons, and one-shot announcement queuing.

As per coding guidelines, “Write Rust tests inline with #[cfg(test)] mod tests or in sibling *_tests.rs files when new/changed behavior is added.”

Also applies to: 360-407

🤖 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/agent/harness/session/turn/tools.rs` around lines 249 - 305,
Add comprehensive unit tests for the new stateful session behavior introduced by
the functions `ensure_skill_events_listener`, `drain_skill_events`, and
`refresh_workflows`. Create tests that cover lag handling (TryRecvError::Lagged
case), closed receiver handling (TryRecvError::Closed case), no-op set
comparisons when no changes occur, and one-shot announcement queuing behavior.
Follow the coding guidelines by implementing these tests either as inline tests
using #[cfg(test)] mod tests or in a sibling *_tests.rs file.

Source: Coding guidelines

Comment thread src/openhuman/skill_runtime/agent/skill_executor/prompt.md Outdated
Comment thread src/openhuman/workflows/ops_create.rs
sanil-23 and others added 4 commits June 16, 2026 20:05
…resh

- render_installed_skills: lists skills + steers to run_skill; empty omitted
- skill_announcement_note: empty -> None; non-empty mentions ids + run_skill
- refresh_workflows: discovers an on-disk skill, parks it for announcement, idempotent
- drain_skill_events / ensure_skill_events_listener: WorkflowsChanged drains once
- DomainEvent::WorkflowsChanged: domain() = workflow, variant_name() = WorkflowsChanged

Co-Authored-By: Claude <noreply@anthropic.com>
…-field access)

Keeps session/tests.rs to its public-surface-only convention.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…atalogue docs

Addresses CodeRabbit review on tinyhumansai#3722:
- Arm ensure_skill_events_listener() at turn start (next to the composio
  listener) so WorkflowsChanged events during turn 1 aren't missed before the
  first drain (broadcast subscriptions are not retroactive).
- Correct the comment/log/doc that claimed the system-prompt '## Installed
  Skills' block is rebuilt each turn. It is frozen at turn 1 for KV-cache
  stability; the user-turn announcement is what surfaces a mid-session install
  to the live session (mirroring the MCP mechanism). refresh_workflows updates
  the tracked set for the next diff + a fresh session's catalogue.

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed in the latest commits:

1. Arm the skills event listener before first-turn steady state (core.rs) — ✅ Fixed. ensure_skill_events_listener() is now called at turn start next to ensure_composio_integrations_listener(), so a WorkflowsChanged during turn 1 is captured at the next drain (broadcast subs aren't retroactive).

2. Refresh the prompt-visible skills catalog (tools.rs) — Good catch on the inaccurate wording; corrected the comment/log/doc. You're right that the system-prompt ## Installed Skills block is frozen at turn 1 for KV-cache stability and is not rebuilt mid-session. By design, the user-turn [skills update] announcement is what surfaces a mid-session install to the live session — identical to the MCP mid-session mechanism (use_mcp_server is a static delegate, so its prose note on the user turn is the whole mechanism). run_skill then loads/runs the skill fresh from disk, so it doesn't depend on the frozen catalogue. refresh_workflows updates the tracked set to keep the next diff correct and to feed a fresh session's rendered catalogue. Known limitation (shared with the integration/MCP announcements): removals aren't proactively retracted from the frozen block — a follow-up could add a removal note.

3. Add focused tests for skill-event drain + workflow-refresh (tools.rs) — ✅ Already added in a later commit (your review predated it): skill_listener_drains_workflows_changed_events (drain once → empty), refresh_workflows_picks_up_skill_installed_on_disk (discovers an on-disk skill, parks it for announcement, idempotent), skill_announcement_note_*, render_installed_skills_*, and workflows_changed_domain_and_name. All passing locally.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@sanil-23 Thanks for the thorough follow-up — this all makes sense now:

  1. Listener arming — Calling ensure_skill_events_listener() at the same point as the Composio listener is the right place; noted.

  2. Frozen catalogue by design — The clarification is helpful: the ## Installed Skills system-prompt block is intentionally frozen after turn 1 for KV-cache stability, with the [skills update] user-turn announcement carrying mid-session installs (same pattern as MCP). run_skill loads from disk directly, so it's unaffected by the frozen view. This is a reasonable design trade-off.

  3. Test coverage — Good to know the full suite (skill_listener_drains_workflows_changed_events, refresh_workflows_picks_up_skill_installed_on_disk, skill_announcement_note_*, render_installed_skills_*, workflows_changed_domain_and_name) landed in a later commit — that addresses the gap.

On the removal retraction limitation you flagged — would you like me to open a follow-up issue tracking "emit a [skills update: removed] user-turn note when a skill is uninstalled mid-session"? It's a clean, bounded follow-up that parallels what you'd want for integration/MCP removals too.

(ᵔ ᵕ ᵔ)🐇


✏️ Learnings added
Learnt from: sanil-23
URL: https://github.com/tinyhumansai/openhuman/pull/3722

Timestamp: 2026-06-16T14:54:03.398Z
Learning: In `src/openhuman/agent/harness/session/turn/tools.rs` and related files (PR `#3722`), the `## Installed Skills` system-prompt block rendered by `render_installed_skills` is intentionally frozen after turn 1 for KV-cache stability. Mid-session skill installs are surfaced to the live session via a one-shot `[skills update]` user-turn announcement (produced by `skill_announcement_note`) rather than by rebuilding the system prompt — identical to the MCP mid-session announcement mechanism. `run_skill` loads skill instructions fresh from disk, so it does not depend on the frozen catalogue. `refresh_workflows` updates the tracked set to keep the next diff correct and to feed fresh sessions. Known limitation (shared with integration/MCP announcements): skill removals are not proactively retracted from the frozen block.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@sanil-23

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

All review comments have been addressed in the latest commits (listener arming at turn start, corrected the frozen-catalogue docs/log, and the requested tests were added). CI is fully green (incl. Coverage Gate ≥ 80%). Please re-review to clear the stale changes-requested verdict if everything looks good.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

@sanil-23 Thanks for the detailed summary — all three original concerns are addressed (listener arming, frozen-catalogue clarification, and the full test suite). Kicking off a fresh full review now against the latest commits.

(ᵔ ᵕ ᵔ)🐇

✅ Action performed

Full review finished.

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review — PR #3722

Net-negative simplification (~-453 LOC): drops the brittle per-turn SKILL.md body injection (workflows::inject, 830 LOC, polluted chat history permanently) for a compact ## Installed Skills catalogue + run_skill delegate in an isolated worker, plus a WorkflowsChanged mid-session refresh mirroring the composio/MCP announcement mechanism. Well-documented, sound design.

Note: the carried-over P1 listener-arming race is already FIXED in headcore.rs arms ensure_skill_events_listener unconditionally before the first-turn branch, so a turn-1 WorkflowsChanged is buffered and drained. Not a remaining blocker. Verified clean: dangling callers of deleted workflows::inject = NONE.

Requesting changes on the items below.

Major

  • M1 — emission wiring untested. The 3 new publish_global(WorkflowsChanged) calls (ops_create.rs:392, ops_install.rs:342 + ~516) are the trigger half of the feature and nothing asserts they fire. The refresh_workflows test writes a skill straight to disk and bypasses create/install — deleting any emission line stays green while silently killing mid-session refresh. Add one regression test driving create_workflow_inner (cheapest, no network) asserting WorkflowsChanged lands on a bus receiver.
  • M2 — drain_skill_events lag/closed arms untested (tools.rs:344-378). Only the happy Ok+Empty path is covered. Lagged (returns true, forces re-check) and Closed (nulls the rx) carry real behaviour — a missed Lagged→true drops a refresh under load. Cover by flooding >64 events into the bounded bus to force lag.

Minor

  • m1 — MD040 (skill_executor/prompt.md:~35): the ## Handoff Plan fenced block opens bare; add a language tag (e.g. ```text).
  • m2 — uninstall has no symmetric catalogue retraction. Install is surfaced via the [skills update] note; uninstall is not, so an uninstalled skill stays advertised mid-session and run_skill against it fails at the worker. Acceptable for this PR, but track it (don't leave it as a PR comment).

Questions

  • No linked tracking issue — per workflow norms, file one for this replacement (and the m2 uninstall-retraction follow-up) before merge.

… (review)

Addresses oxoxDev review on tinyhumansai#3722:
- M1: create_workflow_inner_emits_workflows_changed asserts the publish_global
  emission fires (the refresh_workflows test wrote to disk directly and bypassed
  create/install, so a dropped emission would have stayed green).
- M2: skill_listener_treats_lag_as_signal (floods 256 events past the 64-slot
  bus -> Lagged returns true) and skill_listener_closed_channel_nulls_rx_and_is_
  not_a_signal (injected dropped-sender receiver -> Closed returns false + nulls
  rx), via two #[cfg(test)] helpers.
- m1 (MD040): add 'text' language tag to the Handoff Plan fence.
- m2: note the uninstall-retraction gap in code, tracked in tinyhumansai#3738 (filed).

Co-Authored-By: Claude <noreply@anthropic.com>
@sanil-23

Copy link
Copy Markdown
Contributor Author

Thanks @oxoxDev — all items addressed in 186814456:

M1 — emission wiring untested ✅ Added create_workflow_inner_emits_workflows_changed (ops_create.rs): subscribes a bus receiver, calls create_workflow_inner, and asserts DomainEvent::WorkflowsChanged { reason: "create" } actually fires. Deleting any publish_global emission line now fails a test instead of staying green.

M2 — drain_skill_events lag/closed arms untested ✅ Added two tests (tests.rs):

  • skill_listener_treats_lag_as_signal — floods 256 events past the 64-slot bounded bus, asserting Lagged → true (a refresh isn't dropped under load).
  • skill_listener_closed_channel_nulls_rx_and_is_not_a_signal — injects a receiver whose sender was dropped (via a small #[cfg(test)] set_skill_events_rx_for_test helper, since the global singleton bus never closes), asserting Closed → false and that the rx is nulled so the next drain re-arms.

m1 — MD040 ✅ Added a text language tag to the ## Handoff Plan fence.

m2 — uninstall has no symmetric retraction ✅ Tracked, not left as a comment: filed #3738 and referenced it in a code comment at the refresh_workflows diff site. (Confirmed your note that the listener-arming race is already fixed in head — thanks for re-verifying.)

Question — linked tracking issue#3738 covers the uninstall-retraction follow-up. (Happy to also file a broader tracking issue for the injection→catalogue replacement itself if you'd prefer one separate from the PR.)

All 4 new tests pass locally; pushing the branch now triggers CI.

@oxoxDev oxoxDev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review of 186814456 — addresses the prior REQUEST_CHANGES. Majors resolved; approving.

M1 (emission untested) — mostly resolved.

  • create_workflow_inner_emits_workflows_changed (ops_create.rs:687) calls create_workflow_inner directly and asserts WorkflowsChanged{reason:"create"} lands on the bus — dropping the emit (ops_create.rs:388) now goes red. Real coverage, not a disk-write bypass.
  • Residual (non-blocking): the install (ops_install.rs:347) and uninstall (ops_install.rs:521) emits are still droppable-green — no test goes through those two paths. The consumer side is now fully covered so the wiring is exercised end-to-end; worth a symmetric pin when those async paths get a test harness.

M2 (drain lag/closed arms) — resolved.

  • skill_listener_treats_lag_as_signal (tests.rs) floods 256 past the 64-slot bus → forces Lagged → asserts drain returns true. Pins lag-is-signal.
  • skill_listener_closed_channel_nulls_rx_and_is_not_a_signal injects a closed rx via set_skill_events_rx_for_test, asserts drain returns false AND rx nulled so the next drain re-arms. Covers the Closed arm + null. Both #[cfg(test)] helpers are clean.

m1 (MD040) — fixed. prompt.md fence is now ```text.

m2 (uninstall retraction) — tracked. tools.rs comment documents that removals aren't retracted yet and links #3738 (open, matching title). Doubles as the tracking issue the prior round flagged as missing.

Nice work on the closed-channel injection helper — that arm is awkward to reach without it.

@sanil-23

Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve

All three original concerns are addressed (you confirmed this), the human maintainer @oxoxDev has approved, and CI is fully green. Please resolve your review threads so the standing changes-requested verdict clears for merge.

@sanil-23

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants