Skip to content

feat(skill): generate runtime skills within active sessions#219

Merged
xinhuagu merged 10 commits into
mainfrom
codex/issue-132-dynamic-skill-generator
Mar 13, 2026
Merged

feat(skill): generate runtime skills within active sessions#219
xinhuagu merged 10 commits into
mainfrom
codex/issue-132-dynamic-skill-generator

Conversation

@xinhuagu

@xinhuagu xinhuagu commented Mar 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • add session-scoped runtime skill overlay support to the skill registry and skill tool
  • generate FORK-mode runtime skills from repeated tool sequence patterns and persist them as drafts on session end
  • refresh skill descriptions per request so new runtime skills become visible without restarting the daemon

Testing

  • ./gradlew :aceclaw-core:test --tests dev.aceclaw.core.agent.SkillRegistryTest :aceclaw-tools:test --tests dev.aceclaw.tools.SkillToolTest :aceclaw-daemon:test --tests dev.aceclaw.daemon.DynamicSkillGeneratorTest --no-daemon
  • ./gradlew :aceclaw-daemon:test --tests dev.aceclaw.daemon.SkillIntegrationTest :aceclaw-tools:test --tests dev.aceclaw.tools.SkillToolTest --no-daemon
  • ./gradlew :aceclaw-core:test :aceclaw-tools:test :aceclaw-daemon:test --no-daemon

Closes #132

Summary by CodeRabbit

  • New Features

    • Session-scoped runtime skills (visible only to owning session), capped at three per session.
    • Automatic generation and session-end persistence of runtime skill drafts from repeated tool use.
    • Per-session skill descriptions used during prompt assembly and per-request tools bound to session context.
    • Execution results now include assistant-generated messages alongside step results.
    • Ability to create session-bound tool instances for request-scoped behavior.
  • Tests

    • Comprehensive tests for generation, persistence, isolation, overrides, bash filtering, and the 3-skill limit.

@greptile-apps greptile-apps 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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffcfce4c-d3d0-4e28-94cb-a6c40deee8e5

📥 Commits

Reviewing files that changed from the base of the PR and between 3a5f1de and dd80a8e.

📒 Files selected for processing (1)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java

📝 Walkthrough

Walkthrough

Adds session-scoped runtime skill support: SkillRegistry gains per-session overlays and runtime registration; DynamicSkillGenerator creates, registers, and persists session-only draft skills from repeated tool sequences; StreamingAgentHandler, SkillTool, and related tools are updated to pass sessionId, use session-aware descriptions/lookups, and invoke generation during post-turn learning.

Changes

Cohort / File(s) Summary
Session-aware skill registry
aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java, aceclaw-core/src/test/java/dev/aceclaw/core/agent/SkillRegistryTest.java
Adds per-session runtime overlay storage (ConcurrentHashMap), session-aware getters (get(sessionId, ...), names(sessionId), all(sessionId)), formatting helpers, runtime registration/clearing (registerRuntime, runtimeSkills, clearRuntime), MAX_RUNTIME_SKILLS_PER_SESSION, and tests for caps, overrides, and isolation.
Dynamic runtime skill generation
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java, aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java
New DynamicSkillGenerator: detects repeated tool sequences, builds LLM prompts, parses/sanitizes JSON drafts, enforces per-session caps and disallowed-tool rules, registers runtime skills via SkillRegistry, persists drafts to .aceclaw/skills-drafts, and exposes maybeGenerate/persistDrafts API with tests.
Agent lifecycle & prompt wiring
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java, aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java
Wires DynamicSkillGenerator into lifecycle; switches skill descriptions to a session-aware provider (Function<String,String>), threads sessionId and per-turn requestToolNames through flows, adds async post-request learning and post-processing bookkeeping, updates multiple method signatures/fields, and persists runtime drafts at session end.
Tool-level session scoping
aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java, aceclaw-tools/src/test/java/dev/aceclaw/tools/SkillToolTest.java
Adds per-request binding (forRequest(sessionId, handler) / currentSessionId), switches lookups to session-aware registry calls (get(sessionId, ...), names(sessionId)), and tests verifying runtime skills visible only to owning session.
Session-bound deferred tool
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/deferred/DeferCheckTool.java
Adds forSession(String sessionId) to create session-bound DeferCheckTool instances to avoid shared mutable session context.
Planner message propagation
aceclaw-core/src/main/java/dev/aceclaw/core/planner/PlanExecutionResult.java, aceclaw-core/src/main/java/dev/aceclaw/core/planner/SequentialPlanExecutor.java
Adds messages component to PlanExecutionResult and collects/generated messages in SequentialPlanExecutor, updating result construction to include messages.

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Agent/SelfImprovement
    participant Gen as DynamicSkillGenerator
    participant LLM as LLM Client
    participant Reg as SkillRegistry
    participant Disk as Draft Persistence

    Agent->>Gen: maybeGenerate(sessionId, projectPath, turn, history, insights, sessionToolNames)
    Note over Gen: analyze turn for repeated tool sequence\ncheck disallowed tools & cap
    alt eligible (sequence detected & allowed & < 3)
        Gen->>LLM: proposeDraft(systemPrompt, toolSequence)
        LLM-->>Gen: draft JSON (name, description, body)
        Gen->>Gen: sanitize & resolve unique runtime name
        Gen->>Reg: registerRuntime(sessionId, SkillConfig)
        Reg-->>Gen: registration result
        Gen->>Disk: persistDrafts(sessionId, projectPath)
        Disk-->>Gen: file written
        Gen-->>Agent: Optional.of(skill)
    else not eligible
        Gen-->>Agent: Optional.empty()
    end
Loading
sequenceDiagram
    participant Client as Client Request
    participant Handler as StreamingAgentHandler
    participant Reg as SkillRegistry
    participant Tool as SkillTool

    Client->>Handler: incoming request(sessionId)
    Handler->>Reg: formatDescriptions(sessionId)
    Reg-->>Handler: merged disk + runtime descriptions
    Handler->>Tool: forRequest(sessionId, eventHandler)
    Tool->>Reg: names(sessionId)
    Reg-->>Tool: session-visible names
    Client->>Handler: invoke skillName
    Handler->>Tool: execute(skillName)
    Tool->>Reg: get(sessionId, skillName)
    Reg-->>Tool: SkillConfig (runtime or disk)
    Tool-->>Handler: execution result
    Handler-->>Client: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Block Major Correctness And Security Risks ❓ Inconclusive No verification output was provided to convert into the required JSON format. Please provide the verification output that needs to be transformed.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(skill): generate runtime skills within active sessions' directly summarizes the main change: introducing session-scoped runtime skill generation capability.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #132: session-scoped runtime skill generation via DynamicSkillGenerator, hot-loading into SkillRegistry, per-request skill description refresh, session-end persistence, 3-skill limit, and bash prevention.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #132 requirements. PlanExecutionResult and SequentialPlanExecutor message-tracking additions support DynamicSkillGenerator's learning flow. No unrelated modifications detected.
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/issue-132-dynamic-skill-generator
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@qodo-code-review

Copy link
Copy Markdown
ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Generate runtime skills within active sessions with session-scoped visibility
✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add session-scoped runtime skill overlay support to skill registry
• Generate FORK-mode runtime skills from repeated tool sequence patterns
• Persist generated runtime skills as drafts on session end
• Refresh skill descriptions per request for dynamic visibility
Diagram
flowchart LR
  A["Turn with<br/>Repeated Sequence"] -->|"insights"| B["DynamicSkillGenerator"]
  B -->|"maybeGenerate"| C["Runtime Skill<br/>Created"]
  C -->|"registerRuntime"| D["SkillRegistry<br/>Session Overlay"]
  D -->|"names/get<br/>with sessionId"| E["SkillTool"]
  E -->|"execute"| F["Skill Invoked<br/>in Session"]
  G["Session End"] -->|"persistDrafts"| H["Draft Files<br/>in .aceclaw/skills-drafts"]
  D -->|"clearRuntime"| I["Session Cleanup"]
Loading

Grey Divider

File Changes

1. aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java ✨ Enhancement +97/-0

Add session-scoped runtime skill overlay support

• Added ConcurrentHashMap for session-scoped runtime skill storage
• Implemented overloaded get(), names(), and all() methods accepting sessionId parameter
• Added registerRuntime(), runtimeSkills(), and clearRuntime() methods for runtime skill
 lifecycle
• Refactored formatDescriptions() to support session-specific skill visibility

aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java


2. aceclaw-core/src/test/java/dev/aceclaw/core/agent/SkillRegistryTest.java 🧪 Tests +28/-0

Test session-scoped runtime skill visibility

• Added test runtimeSkillsAreVisibleOnlyToOwningSession() validating session isolation
• Verifies runtime skills appear in session-specific queries but not in global queries
• Tests skill descriptions include runtime skills for owning session only

aceclaw-core/src/test/java/dev/aceclaw/core/agent/SkillRegistryTest.java


3. aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java ✨ Enhancement +454/-0

Implement dynamic runtime skill generation engine

• New class generating session-scoped runtime skills from repeated tool sequences
• Implements maybeGenerate() to create FORK-mode skills when repeated sequence insights detected
• Implements persistDrafts() to save generated skills as markdown files on session end
• Includes LLM-based draft proposal with fallback template generation
• Filters disallowed tools (bash, skill, task, task_output) and limits 3 skills per session

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java


View more (5)
4. aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java 🧪 Tests +153/-0

Test dynamic skill generation and persistence

• Added comprehensive tests for runtime skill generation and persistence
• Tests skill generation from repeated sequences and draft file creation
• Validates filtering of bash-containing sequences and per-session skill limits
• Verifies session isolation and skill visibility after persistence

aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java


5. aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java ✨ Enhancement +23/-4

Wire dynamic skill generator into daemon lifecycle

• Instantiated DynamicSkillGenerator with LLM client and skill registry
• Wired generator to agent handler via setDynamicSkillGenerator()
• Changed skillDescriptions from static string to function provider for session-aware formatting
• Added session end callback to persist runtime skill drafts via persistDrafts()
• Updated skill tool registration logic to support runtime-only scenarios

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java


6. aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java ✨ Enhancement +17/-4

Enable session-aware skill descriptions and generation

• Added currentSessionId field to SkillTool for session-scoped skill visibility
• Changed skillDescriptions from static string to Function<String, String> provider
• Updated setContextAssemblyConfig() to accept skill description provider function
• Integrated DynamicSkillGenerator invocation in self-improvement post-turn analysis
• Added setter setDynamicSkillGenerator() for generator configuration

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java


7. aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java ✨ Enhancement +11/-3

Add session context to skill tool execution

• Added currentSessionId volatile field for session-scoped skill lookup
• Implemented setCurrentSessionId() setter for session context management
• Updated inputSchema() and execute() to use session-aware skill registry queries
• Modified error messages to reflect session-specific available skills

aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java


8. aceclaw-tools/src/test/java/dev/aceclaw/tools/SkillToolTest.java 🧪 Tests +73/-0

Test session-scoped runtime skill invocation

• Added test validating runtime skills are executable within owning session
• Added test confirming runtime skills are hidden from other sessions
• Verifies proper error handling for cross-session skill access attempts

aceclaw-tools/src/test/java/dev/aceclaw/tools/SkillToolTest.java


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Mar 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (3)

Grey Divider


Action required

1. registerRuntime() lacks 3-skill cap📎 Requirement gap ⛯ Reliability
Description
SkillRegistry.registerRuntime() allows unlimited runtime skill registrations per session, so
callers can bypass the 3-per-session limit. This violates the requirement to prevent registering
more than 3 runtime skills in a session.
Code

aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[R205-213]

+    public boolean registerRuntime(String sessionId, SkillConfig skill) {
+        Objects.requireNonNull(sessionId, "sessionId");
+        Objects.requireNonNull(skill, "skill");
+        if (sessionId.isBlank()) {
+            throw new IllegalArgumentException("sessionId must not be blank");
+        }
+        var sessionSkills = runtimeRegistry.computeIfAbsent(sessionId, ignored -> new ConcurrentHashMap<>());
+        return sessionSkills.putIfAbsent(skill.name(), skill) == null;
+    }
Evidence
PR Compliance ID 3 requires blocking the 4th runtime skill registration per session. The new
registerRuntime() method inserts into a per-session map with no count/limit check, so any code
path can register more than 3 runtime skills for the same sessionId.

Enforce maximum of 3 runtime skills generated per session
aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[205-213]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SkillRegistry.registerRuntime()` currently allows unlimited runtime skill registrations per session, which means other callers can register a 4th+ runtime skill even though the system must cap runtime skills at 3 per session.
## Issue Context
`DynamicSkillGenerator` enforces a 3-skill limit internally, but the registry-level API does not, so the constraint is not guaranteed system-wide.
## Fix Focus Areas
- aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[205-213]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Draft body not bash-validated📎 Requirement gap ⛨ Security
Description
The auto-generated runtime skill draft body is accepted and persisted without validation for
bash commands, relying only on LLM instructions. This can result in persisted auto-generated
skills containing bash without any explicit approval flag.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[R250-264]

+    private RuntimeSkillDraft parseDraft(String text) throws LlmException {
+        try {
+            JsonNode node = objectMapper.readTree(extractJson(text));
+            String name = trimToNull(node.path("name").asText(""));
+            String description = trimToNull(node.path("description").asText(""));
+            String argumentHint = trimToNull(node.path("argument_hint").asText(""));
+            String body = trimToNull(node.path("body").asText(""));
+            if (description == null || body == null) {
+                throw new LlmException("Runtime skill response missing description/body");
+            }
+            return new RuntimeSkillDraft(
+                    name != null ? name : "runtime-workflow",
+                    description,
+                    argumentHint,
+                    body);
Evidence
PR Compliance ID 4 requires disallowing bash commands in auto-generated skills by default unless an
explicit approval flag is enabled. The code only instructs the LLM "Do not mention bash" and blocks
tool sequences containing the bash tool name, but it never checks the returned body for bash
commands before registering/persisting it.

Disallow bash commands in auto-generated skills unless explicit user approval flag is enabled
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[202-216]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[250-264]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Auto-generated runtime skill drafts can include bash commands in the `body` because the code does not validate the generated content; it only instructs the LLM not to mention bash.
## Issue Context
Compliance requires bash commands be disallowed in auto-generated skills unless an explicit user approval flag is enabled.
## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[195-228]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[250-264]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. No session toolset enforcement📎 Requirement gap ⛨ Security
Description
Runtime skill tool lists are derived from the observed tool sequence and filtered only by a fixed
denylist, not intersected with the current session’s allowed-tools set. This allows a generated
runtime skill to reference tools that are not permitted for that session.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[R68-91]

+    public java.util.Optional<SkillConfig> maybeGenerate(String sessionId,
+                                                         Path projectPath,
+                                                         Turn turn,
+                                                         List<AgentSession.ConversationMessage> sessionHistory,
+                                                         List<Insight> insights) {
+        Objects.requireNonNull(sessionId, "sessionId");
+        Objects.requireNonNull(projectPath, "projectPath");
+        Objects.requireNonNull(turn, "turn");
+        Objects.requireNonNull(sessionHistory, "sessionHistory");
+        Objects.requireNonNull(insights, "insights");
+
+        if (!hasRepeatedSequenceInsight(insights)) {
+            return java.util.Optional.empty();
+        }
+
+        var toolSequence = extractToolSequence(turn.newMessages());
+        if (toolSequence.size() < 3) {
+            return java.util.Optional.empty();
+        }
+
+        var allowedTools = filterAllowedTools(toolSequence);
+        if (allowedTools.isEmpty()) {
+            return java.util.Optional.empty();
+        }
Evidence
PR Compliance ID 5 requires runtime-generated skills be restricted to the session’s allowed tools.
maybeGenerate() does not accept the session allowed-tools set, and filterAllowedTools() only
blocks a static set (bash, skill, task, task_output), so tools outside the session
permission set are not rejected/removed during generation/registration.

Restrict runtime-generated skills to the current session's allowed tools
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[68-72]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[83-91]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[52-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The runtime skill generator currently does not enforce that generated skills only use tools permitted by the current session. It filters only a static denylist, which is not equivalent to the session allowed-tools policy.
## Issue Context
Compliance requires runtime-generated skills be constrained to the session’s allowed tools; any disallowed tools should be rejected or removed during generation/registration.
## Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[68-126]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[294-306]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Cross-session skill leak🐞 Bug ⛨ Security
Description
SkillTool keeps the active sessionId in a mutable field on a singleton tool instance, so concurrent
requests for different sessions can overwrite it and cause runtime skills from one session to be
visible/executable in another. This violates the PR’s stated session-scoped visibility and can lead
to wrong-skill execution across sessions.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[R262-266]

      for (var tool : toolRegistry.all()) {
          if (tool instanceof SkillTool st) {
              st.setCurrentHandler(eventHandler);
+                st.setCurrentSessionId(sessionId);
          }
Evidence
Tools are registered once into a global ToolRegistry and shared across all requests;
StreamingAgentHandler only serializes turns per-session (not globally), yet it mutates SkillTool’s
shared currentSessionId field per request. Therefore two sessions running concurrently can race on
SkillTool.currentSessionId, making SkillTool.names()/get()/execute() consult the wrong
session-scoped overlay.

aceclaw-core/src/main/java/dev/aceclaw/core/agent/ToolRegistry.java[17-45]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[341-353]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[156-159]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[260-275]
aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java[35-120]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SkillTool` stores `currentSessionId` as mutable shared state on a singleton tool instance. `StreamingAgentHandler` sets/clears it per request, but requests for different sessions can run concurrently, so the shared field can be overwritten and cause cross-session runtime skill visibility/execution.
### Issue Context
The daemon registers a single `SkillTool` into a global `ToolRegistry`, and `StreamingAgentHandler` only serializes turns *within the same session*, not across sessions.
### Fix Focus Areas
- aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java[35-120]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[260-341]
- aceclaw-core/src/main/java/dev/aceclaw/core/agent/ToolRegistry.java[17-55]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[341-353]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Session-end draft race🐞 Bug ⛯ Reliability
Description
DynamicSkillGenerator.maybeGenerate runs in an untracked virtual thread after a turn, while session
end calls persistDrafts which removes the session state and clears runtime skills. maybeGenerate can
run after persistDrafts and recreate state/runtime skills (via computeIfAbsent), leaving skills
uncleared and drafts unpersisted.
Code

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[R594-597]

+                    if (dynamicSkillGenerator != null) {
+                        dynamicSkillGenerator.maybeGenerate(
+                                sessionIdRef, projectPathRef, turnRef, historyRef, insights);
+                    }
Evidence
The generator is invoked asynchronously; session end persistence removes the per-session generator
state and clears the registry. Since maybeGenerate recreates state with computeIfAbsent and has no
session-closed guard, it can re-register runtime skills after persistDrafts has already run.

aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[576-601]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[660-742]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[93-97]
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[129-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`maybeGenerate()` is executed in an untracked virtual thread, but session end calls `persistDrafts()` which removes generator state and clears runtime skills. Without synchronization, generation can occur after persistence/cleanup, recreating state and leaving runtime skills/drafts inconsistent.
### Issue Context
`persistDrafts()` uses `sessionStates.remove(sessionId)` and `maybeGenerate()` uses `computeIfAbsent`, so state can be recreated after removal.
### Fix Focus Areas
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java[576-601]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java[660-742]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[68-127]
- aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java[129-160]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. Runtime precedence inconsistency🐞 Bug ✓ Correctness
Description
SkillRegistry.get(sessionId,name) returns a runtime skill when it collides with a disk-backed skill
name, but SkillRegistry.all(sessionId) uses putIfAbsent so disk-backed skills win in
formatDescriptions(sessionId). This can make the model see the disk-backed description while
SkillTool executes the runtime skill of the same name.
Code

aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[R159-165]

+    public List<SkillConfig> all(String sessionId) {
+        var combined = new LinkedHashMap<String, SkillConfig>(registry);
+        if (sessionId != null && !sessionId.isBlank()) {
+            var runtime = runtimeRegistry.get(sessionId);
+            if (runtime != null && !runtime.isEmpty()) {
+                runtime.forEach(combined::putIfAbsent);
+            }
Evidence
The session-aware lookup path prefers runtime entries, but the session-aware enumeration path keeps
disk entries on name collisions; because formatDescriptions(sessionId) is built from all(sessionId),
the description set can diverge from what SkillTool executes using get(sessionId,name).

aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[110-125]
aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[159-168]
aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[190-198]
aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java[112-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SkillRegistry.get(sessionId,name)` prefers runtime skills, but `SkillRegistry.all(sessionId)` keeps disk-backed skills on name collision (`putIfAbsent`). This can desync what the model sees in `formatDescriptions(sessionId)` from what `SkillTool` executes.
### Issue Context
This only manifests on runtime-vs-disk name collisions, but the API currently allows them via `registerRuntime`.
### Fix Focus Areas
- aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[110-168]
- aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java[190-198]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java Outdated
Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java Outdated
Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java (1)

40-76: ⚠️ Potential issue | 🔴 Critical

Don't store session context on the shared SkillTool instance.

AceClawDaemon registers one SkillTool, and StreamingAgentHandler mutates this field per request. Two overlapping sessions can overwrite currentSessionId between inputSchema() and execute(), which lets one session see or invoke another session's runtime skills and can also route sub-agent events through the wrong handler. Make the skill tool request-scoped in the permission-aware registry, or pass session context down the call path instead of keeping it on the singleton.

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

In `@aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java` around lines 40
- 76, SkillTool stores mutable request-specific state (currentSessionId,
currentHandler) on a shared instance causing cross-request leaks; fix by
removing those fields and either making SkillTool request-scoped in the
permission-aware SkillRegistry or by threading the request context (sessionId
and StreamEventHandler) through the call path: stop using
setCurrentSessionId/setCurrentHandler on the singleton and instead (a) create a
per-request SkillTool (or a RequestContext wrapper) inside
StreamingAgentHandler/AceClawDaemon when resolving skills from SkillRegistry, or
(b) change APIs that use currentSessionId/currentHandler (e.g. inputSchema(),
execute(), and where SubAgentRunner is invoked) to accept sessionId and
StreamEventHandler as parameters so runtime-scoped skills and sub-agent event
routing use the request-local values.
🧹 Nitpick comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java (1)

437-445: Null-guard the record's list field.

Current call sites pass a value, but the compact constructor still turns a future null into an NPE inside List.copyOf(...). The repo guideline asks records to normalize nullable lists up front.

♻️ Proposed fix
     private RuntimeSkillRecord {
-        allowedTools = List.copyOf(allowedTools);
+        allowedTools = allowedTools != null ? List.copyOf(allowedTools) : List.of();
     }

As per coding guidelines, "Always null-guard List fields in record constructors: signals = signals != null ? List.copyOf(signals) : List.of()."

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

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`
around lines 437 - 445, RuntimeSkillRecord's compact constructor calls
List.copyOf(allowedTools) which will NPE if allowedTools is null; modify the
compact constructor in RuntimeSkillRecord to null-guard allowedTools (e.g.,
assign allowedTools = allowedTools != null ? List.copyOf(allowedTools) :
List.of()) so the record normalizes nullable list inputs safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java`:
- Around line 159-165: The all(String sessionId) method currently uses
putIfAbsent which preserves disk-backed SkillConfig entries even when a runtime
skill with the same name exists (causing mismatch with get(sessionId, name)
which prefers runtime); update all to allow runtime entries to overwrite disk
ones by replacing the putIfAbsent call with a direct put from runtime into
combined so the overlay formatting matches lookup (or alternatively, if you
prefer rejecting collisions, implement collision detection inside
registerRuntime(sessionId, name, skill) and throw/log there); adjust the logic
in all(String) and/or registerRuntime(...) so both lookup (get) and listing
(all) are consistent.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`:
- Around line 133-159: The code removes the session state from sessionStates
before performing disk I/O, which loses the only in-memory copy on failure and
allows races with maybeGenerate and clearRuntime; change the logic in
DynamicSkillGenerator so you retrieve (but do not remove) the state from
sessionStates (use sessionStates.get(sessionId) or peek), set a "closing" flag
on the state (or call state.markClosing()) while synchronized to prevent new
generation, then perform the directory/create/write/move operations inside the
synchronized block; only after all writes succeed remove the state from
sessionStates and then call skillRegistry.clearRuntime(sessionId); ensure
synchronization uses the same state object and that maybeGenerate checks the
closing flag so it won't create a fresh state while flush is in progress.
- Around line 105-108: The SkillConfig is constructed with draft.argumentHint(),
but the markdown serializer/renderer that persists the draft never includes this
field so the persisted draft loses its invocation hint; update the draft
serialization path in DynamicSkillGenerator (the markdown renderer/serializer
used when saving drafts) to include argumentHint (draft.argumentHint()) in the
rendered output so the persisted draft round-trips the same metadata as the
runtime SkillConfig; ensure the serializer that produces the persisted markdown
(the method responsible for converting a draft to markdown/serialized form)
writes the argument hint and that the deserializer/loader restores it into
SkillConfig on load.
- Around line 79-93: The code currently checks
hasRepeatedSequenceInsight(insights) but then dedupes the current turn via
extractToolSequence before matching, causing distinct ordered repeats (e.g.,
search->open->search) to be lost; update DynamicSkillGenerator so you first keep
the full ordered tool sequence returned by
extractToolSequence(turn.newMessages()) and use that ordered sequence (not a
deduped list) when matching against hasRepeatedSequenceInsight and when
computing the runtime-skill duplicate signature via signatureOf; only after
those checks derive the deduped/allowed list by calling filterAllowedTools on
the ordered sequence to produce allowedTools used for skill generation. Apply
the same change at the other occurrence (the block around methods/lines
referenced by the reviewer, e.g., the later block at ~294-305) so insight
matching and duplicate detection always use the full ordered sequence while
allowed-tools are derived separately.
- Around line 183-193: The fallbackDraft is returned/registered verbatim and may
contain disallowed tool mentions; ensure you run the same post-generation
validation/sanitization used for LLM drafts on the fallback before returning or
persisting it. In generateDraft wrap the fallbackDraft result with the existing
validation/sanitization routine (the same logic you call after proposeDraft) so
that any banned tool names or unsafe content is removed/normalized before
registration; apply the identical change to the other fallback/registration path
in this class that currently uses fallbackDraft so both code paths use the
validated/sanitized draft.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`:
- Around line 594-597: The current fire-and-forget call to
dynamicSkillGenerator.maybeGenerate(sessionIdRef, projectPathRef, turnRef,
historyRef, insights) should be removed from the fast-path and moved into a
synchronized post-request hook invoked by both executePlannedPrompt and
executeResumedPlan; implement a per-session generation queue (or executor) that
enqueues generation tasks instead of launching untracked work, ensure the
post-request hook drains that per-session queue before calling
persistDrafts(sessionId, ...) and before session teardown, and add proper
lifecycle hooks to the UDS listener/shutdown sequence to wait for these drained
tasks (pay attention to thread safety and virtual-thread usage when interacting
with dynamicSkillGenerator and session state).

---

Outside diff comments:
In `@aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java`:
- Around line 40-76: SkillTool stores mutable request-specific state
(currentSessionId, currentHandler) on a shared instance causing cross-request
leaks; fix by removing those fields and either making SkillTool request-scoped
in the permission-aware SkillRegistry or by threading the request context
(sessionId and StreamEventHandler) through the call path: stop using
setCurrentSessionId/setCurrentHandler on the singleton and instead (a) create a
per-request SkillTool (or a RequestContext wrapper) inside
StreamingAgentHandler/AceClawDaemon when resolving skills from SkillRegistry, or
(b) change APIs that use currentSessionId/currentHandler (e.g. inputSchema(),
execute(), and where SubAgentRunner is invoked) to accept sessionId and
StreamEventHandler as parameters so runtime-scoped skills and sub-agent event
routing use the request-local values.

---

Nitpick comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`:
- Around line 437-445: RuntimeSkillRecord's compact constructor calls
List.copyOf(allowedTools) which will NPE if allowedTools is null; modify the
compact constructor in RuntimeSkillRecord to null-guard allowedTools (e.g.,
assign allowedTools = allowedTools != null ? List.copyOf(allowedTools) :
List.of()) so the record normalizes nullable list inputs safely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 87a4eda5-eeb5-4bac-b687-1f7dda60720f

📥 Commits

Reviewing files that changed from the base of the PR and between 1bfd665 and 3af968e.

📒 Files selected for processing (8)
  • aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java
  • aceclaw-core/src/test/java/dev/aceclaw/core/agent/SkillRegistryTest.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java
  • aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java
  • aceclaw-tools/src/test/java/dev/aceclaw/tools/SkillToolTest.java

Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java Outdated
Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java Outdated
Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java (1)

578-581: ⚠️ Potential issue | 🟠 Major

Move dynamic-skill generation out of this fire-and-forget hook.

This is the only place in the handler that invokes maybeGenerate(), so successful executePlannedPrompt() and executeResumedPlan() paths never register runtime skills. Launching it on an untracked virtual thread also makes the next request observe stale skill descriptions if prompt assembly wins the race. Put generation behind a common post-request hook that covers direct, planned, and resumed flows and has a defined completion point. As per coding guidelines, aceclaw-daemon/**: Check UDS listener lifecycle, shutdown ordering, and thread safety with virtual threads.

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

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`
around lines 578 - 581, The dynamic-skill generation call in
StreamingAgentHandler (dynamicSkillGenerator.maybeGenerate(...)) must be removed
from this fire-and-forget hook and moved into a single post-request completion
hook that is invoked by all request exit paths (direct, executePlannedPrompt,
executeResumedPlan) so skill registration happens deterministically; implement
the hook (e.g., onRequestComplete or postRequestFinalize) to call
dynamicSkillGenerator.maybeGenerate(sessionIdRef, projectPathRef, turnRef,
historyRef, insights, requestToolNames) and await its completion (no untracked
virtual thread spawn), ensure the hook is called synchronously or its future
joined before responding, and audit UDS listener lifecycle, shutdown ordering,
and thread-safety around dynamicSkillGenerator use per aceclaw-daemon
guidelines.
🧹 Nitpick comments (1)
aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java (1)

139-168: Keep runtime-skill ordering deterministic.

Because the overlay is backed by ConcurrentHashMap, names(sessionId), all(sessionId), and the derived formatDescriptions(sessionId) expose runtime skills in hash iteration order. Since that text is injected into tool schemas and prompts, the same session can advertise a different ordering after rehashes or additional registrations. Sorting on read, or storing the overlay in an ordered map, would make prompt assembly stable.

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

In `@aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java` around
lines 139 - 168, The runtime overlay uses a ConcurrentHashMap so iteration order
is non-deterministic; fix names(String) and all(String) (and any callers like
formatDescriptions(sessionId)) by deterministically merging runtime entries:
when you retrieve var runtime = runtimeRegistry.get(sessionId), if runtime is
non-null and non-empty, iterate over its keys/entries in a stable order (e.g.,
runtime.keySet().stream().sorted() or
runtime.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey))) and
add them into the LinkedHashSet/LinkedHashMap respectively rather than using
runtime.keySet() or runtime.forEach directly; this guarantees stable ordering
without changing the runtimeRegistry type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`:
- Around line 1296-1303: The constructor currently sets
skillDescriptionsProvider to a non-null Function but doesn't guard against that
function returning null; ensure any invocation yields a non-null String by
wrapping the provided Function (in the StreamingAgentHandler constructor) with a
null-coalescing wrapper that calls the original Function and returns "" when the
result is null, so downstream code (e.g., where the provider is used during
request assembly) always receives a non-null string; update the constructor
assignment of skillDescriptionsProvider to use this wrapper and add a quick unit
test or comment noting the non-null guarantee.

---

Duplicate comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`:
- Around line 578-581: The dynamic-skill generation call in
StreamingAgentHandler (dynamicSkillGenerator.maybeGenerate(...)) must be removed
from this fire-and-forget hook and moved into a single post-request completion
hook that is invoked by all request exit paths (direct, executePlannedPrompt,
executeResumedPlan) so skill registration happens deterministically; implement
the hook (e.g., onRequestComplete or postRequestFinalize) to call
dynamicSkillGenerator.maybeGenerate(sessionIdRef, projectPathRef, turnRef,
historyRef, insights, requestToolNames) and await its completion (no untracked
virtual thread spawn), ensure the hook is called synchronously or its future
joined before responding, and audit UDS listener lifecycle, shutdown ordering,
and thread-safety around dynamicSkillGenerator use per aceclaw-daemon
guidelines.

---

Nitpick comments:
In `@aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java`:
- Around line 139-168: The runtime overlay uses a ConcurrentHashMap so iteration
order is non-deterministic; fix names(String) and all(String) (and any callers
like formatDescriptions(sessionId)) by deterministically merging runtime
entries: when you retrieve var runtime = runtimeRegistry.get(sessionId), if
runtime is non-null and non-empty, iterate over its keys/entries in a stable
order (e.g., runtime.keySet().stream().sorted() or
runtime.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey))) and
add them into the LinkedHashSet/LinkedHashMap respectively rather than using
runtime.keySet() or runtime.forEach directly; this guarantees stable ordering
without changing the runtimeRegistry type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e10f21c-d041-4ac1-93e0-3c5b570ab288

📥 Commits

Reviewing files that changed from the base of the PR and between 3af968e and 862fa53.

📒 Files selected for processing (7)
  • aceclaw-core/src/main/java/dev/aceclaw/core/agent/SkillRegistry.java
  • aceclaw-core/src/test/java/dev/aceclaw/core/agent/SkillRegistryTest.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/deferred/DeferCheckTool.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java
  • aceclaw-tools/src/main/java/dev/aceclaw/tools/SkillTool.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java

Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java (1)

1534-1541: ⚠️ Potential issue | 🟠 Major

Avoid dropping in-flight post-processing before teardown.

clearSessionMetrics() removes sessionPostProcessing on Line 1540. If cleanup calls this before awaitSessionPostProcessing(), waiting becomes a no-op and late runtime-skill draft work can be skipped during session close.

Suggested fix
 public void clearSessionMetrics(String sessionId) {
     java.util.Objects.requireNonNull(sessionId, "sessionId");
+    awaitSessionPostProcessing(sessionId);
     sessionMetrics.remove(sessionId);
     sessionInjectedCandidateIds.remove(sessionId);
     sessionDoomLoops.remove(sessionId);
     sessionProgressDetectors.remove(sessionId);
     sessionPostProcessing.remove(sessionId);
 }
As per coding guidelines, `aceclaw-daemon/**`: Check UDS listener lifecycle, shutdown ordering, and thread safety with virtual threads.

Also applies to: 1543-1553

♻️ Duplicate comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java (1)

1357-1359: ⚠️ Potential issue | 🟡 Minor

Coalesce null results from skillDescriptionsProvider.

The function reference is null-guarded, but its return value is not. A custom provider can still return null, which is forwarded at Line 1644.

Suggested fix
-        this.skillDescriptionsProvider = skillDescriptionsProvider != null
-                ? skillDescriptionsProvider : ignored -> "";
+        this.skillDescriptionsProvider = skillDescriptionsProvider != null
+                ? sessionId -> {
+                    var value = skillDescriptionsProvider.apply(sessionId);
+                    return value != null ? value : "";
+                }
+                : ignored -> "";
As per coding guidelines, `**/*.java`: Check for null return values before using methods that may return null.

Also applies to: 1644-1644

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

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`
around lines 1357 - 1359, The assigned skillDescriptionsProvider may return null
even though the reference is non-null; ensure callers never receive null by
coalescing its return value to an empty string either by wrapping the provider
in the constructor (store a lambda that calls the original and returns result ==
null ? "" : result) or by null-checking the result at use sites (e.g., where
skillDescriptionsProvider.apply(...) is invoked in StreamingAgentHandler,
referenced at the call around line 1644) so that
skillDescriptionsProvider.apply(...) always yields a non-null String; update the
constructor or the call site to use Objects.requireNonNullElse(value, "") or
equivalent.
🧹 Nitpick comments (1)
aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java (1)

509-511: Null-guard allowedTools in RuntimeSkillRecord constructor.

The record constructor copies allowedTools but does not null-guard it. Align this with the repo’s record-constructor rule.

♻️ Suggested patch
     private RuntimeSkillRecord {
-        allowedTools = List.copyOf(allowedTools);
+        allowedTools = allowedTools != null ? List.copyOf(allowedTools) : List.of();
     }

As per coding guidelines, "Always null-guard List fields in record constructors: signals = signals != null ? List.copyOf(signals) : List.of()".

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

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`
around lines 509 - 511, The record constructor for RuntimeSkillRecord copies
allowedTools without a null check; update the compact constructor so
allowedTools is null-guarded (e.g., set allowedTools = allowedTools != null ?
List.copyOf(allowedTools) : List.of()) to ensure the field is never null and
follows the repo rule for List fields in record constructors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java`:
- Around line 550-554: DynamicSkillGenerator wiring and session-end persistence
are wrongly guarded by the memoryStore null check, disabling runtime-skill
features when memoryStore fails; move the construction of DynamicSkillGenerator
and the call to agentHandler.setDynamicSkillGenerator(dynamicSkillGenerator) out
of the if (memoryStore != null) block so the generator is always created (using
only llmClient, agentHandler::getModelForSession, skillRegistry), and separately
keep only the persistence-related subscription/handlers (the session-end
persistence logic) inside the memoryStore null-guard so that persistence is
skipped when memoryStore is unavailable but runtime-skill generation still
functions.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`:
- Around line 204-210: hasRepeatedSequenceInsight currently only checks pattern
type and signature match; update it to also require the insight's occurrence
count be >= 3 so weak repeated-sequence insights are ignored. Inside
hasRepeatedSequenceInsight, after casting to Insight.PatternInsight (the stream
branch using Insight.PatternInsight.class::cast), add a check that the insight's
occurrence count (e.g., insight.occurrenceCount() or the existing
property/method that returns how many times the sequence was seen) is >= 3
before matching signature; keep using signatureOf(toolSequence) and
extractInsightSequenceSignature(insight.description()) for the signature
comparison and ensure you reference PatternType.REPEATED_TOOL_SEQUENCE as
already done.

In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`:
- Around line 1549-1553: The current catch-all around future.get(30,
TimeUnit.SECONDS) masks InterruptedException and conflates different failures;
update the try/catch to explicitly catch InterruptedException (call
Thread.currentThread().interrupt() and log that the wait was interrupted for
sessionId), TimeoutException (log the timeout for sessionId), and
ExecutionException (log the execution failure and include e.getCause()/message
for sessionId), rather than catching Exception; reference the existing
future.get call and sessionId/log.warn usage so you adjust the exception
handling in StreamingAgentHandler around the post-request learning wait
accordingly.

---

Duplicate comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java`:
- Around line 1357-1359: The assigned skillDescriptionsProvider may return null
even though the reference is non-null; ensure callers never receive null by
coalescing its return value to an empty string either by wrapping the provider
in the constructor (store a lambda that calls the original and returns result ==
null ? "" : result) or by null-checking the result at use sites (e.g., where
skillDescriptionsProvider.apply(...) is invoked in StreamingAgentHandler,
referenced at the call around line 1644) so that
skillDescriptionsProvider.apply(...) always yields a non-null String; update the
constructor or the call site to use Objects.requireNonNullElse(value, "") or
equivalent.

---

Nitpick comments:
In `@aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java`:
- Around line 509-511: The record constructor for RuntimeSkillRecord copies
allowedTools without a null check; update the compact constructor so
allowedTools is null-guarded (e.g., set allowedTools = allowedTools != null ?
List.copyOf(allowedTools) : List.of()) to ensure the field is never null and
follows the repo rule for List fields in record constructors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db7eb7cf-b67f-4b5e-97fb-fe49d63d638a

📥 Commits

Reviewing files that changed from the base of the PR and between 862fa53 and d219006.

📒 Files selected for processing (6)
  • aceclaw-core/src/main/java/dev/aceclaw/core/planner/PlanExecutionResult.java
  • aceclaw-core/src/main/java/dev/aceclaw/core/planner/SequentialPlanExecutor.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/DynamicSkillGenerator.java
  • aceclaw-daemon/src/main/java/dev/aceclaw/daemon/StreamingAgentHandler.java
  • aceclaw-daemon/src/test/java/dev/aceclaw/daemon/DynamicSkillGeneratorTest.java

Comment thread aceclaw-daemon/src/main/java/dev/aceclaw/daemon/AceClawDaemon.java Outdated
@xinhuagu xinhuagu merged commit 393e074 into main Mar 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(skill): DynamicSkillGenerator — runtime skill creation during tasks

1 participant