fix(cli): add --skill flag to actor run command #971
Merged
hurui200320
merged 1 commits from 2026-03-19 09:30:36 +00:00
feature/m4-actor-run-skill into master
Dismiss Review
Are you sure you want to dismiss this review?
Labels
Clear labels
auto/needs-reevaluation
controller-managed
overdue
auto/blocked-by-deps
auto/ci-timeout
auto/claimed-implementer
auto/claimed-merge
auto/claimed-reviewer
auto/driver-down
auto/invariant-violation
auto/last-attempt-tier-0
auto/last-attempt-tier-1
auto/last-attempt-tier-2
auto/last-attempt-tier-min
Automation Tracking
auto/needs-conflict-resolution
auto/needs-implementer
auto/postmortem
auto/ready-to-merge
auto/restart-throttled
auto/revert
auto/sentinel
auto/stale-inactivity
auto/unstable
Blocked
Needs Feedback
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
Controller deferred this PR; awaiting Phase 6+ scope-evaluator or operator re-enablement.
Auto-agents controller manages this PR/issue (see tools/controller/deploy/RUNBOOK.md). Remove this label to abandon controller management.
PR blocked by an open issue dependency. Operator must close the dep (or remove the dependency link) before the merge driver can act. Auto-cleared by merge_drive when no open deps remain.
Most recent merge cycle hit CI timeout. Driver excludes this PR while last merge_cycle row is < 30 min old; label persists thereafter as visible history.
Currently being processed by an implementer worker.
Currently being processed by the merge driver.
Currently being processed by a reviewer worker.
Merge driver heartbeat stale; pipeline halted. Closed automatically on next clean tick.
Detected master commit violating the strict merge invariant. Tracked as an issue (not a PR label); kept here for label completeness.
In-cycle escalation: most recent attempt ran at the Tier 0 slot (`tier-0`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 1 slot (`tier-1`). Slot's model defined in .opencode/models/tiers.yaml.
In-cycle escalation: most recent attempt ran at the Tier 2 slot (`tier-2`). Slot's model defined in .opencode/models/tiers.yaml. Gated behind IMPLEMENTER_ESCALATION_TIER2_ENABLED.
In-cycle escalation: most recent attempt ran at the Tier -1 slot (`tier-min`). Slot's model defined in .opencode/models/tiers.yaml. Suffix is ``-min`` (not ``--1``) so the Forgejo UI reads naturally.
Tracking issues used by the AI Automation system for agents to communicate and report.
Rebase conflict needs LLM conflict-resolver.
Failing CI needs implementer attention.
Documenting a driver incident or rollback.
Reviewer has APPROVED this PR and no later REQUEST_CHANGES is outstanding. The merge driver requires this label to even consider a PR for merging. Set by the reviewer worker on APPROVE; cleared on REQUEST_CHANGES.
Train repeatedly lost master-tempo races. Driver excludes via merge_cycle until cooldown elapses; label persists as visible history.
Revert PR backing out an invariant violation. Fast-tracked through the merge driver.
Sentinel PR duplicated from upstream into a personal fork by tools/duplicate_prs_to_fork.py for pipeline testing. Lives only in the fork; the canonical pipeline never sees it.
No implementer activity for N days. Flagged for human review. Auto-cleared on next push to head branch.
Repeatedly fails on current master (>= 3 ci-fail-on-rebased-sha releases in 12 h). Excluded from driver until human triage.
A ticket in a blocked state and unable to complete until some other task is completed first.
Bounty
$100
A bounty of $100 for any open-source contributor who provides a MR that solves this issue
Bounty
$1000
A bounty of $1000 for any open-source contributor who provides a MR that solves this issue
Bounty
$10000
A bounty of $10000 for any open-source contributor who provides a MR that solves this issue
Bounty
$20
A bounty of $20 for any open-source contributor who provides a MR that solves this issue
Bounty
$2000
A bounty of $2000 for any open-source contributor who provides a MR that solves this issue
Bounty
$250
A bounty of $250 for any open-source contributor who provides a MR that solves this issue
Bounty
$50
A bounty of $50 for any open-source contributor who provides a MR that solves this issue
Bounty
$500
A bounty of $500 for any open-source contributor who provides a MR that solves this issue
Bounty
$5000
A bounty of $5000 for any open-source contributor who provides a MR that solves this issue
Bounty
$750
A bounty of $750 for any open-source contributor who provides a MR that solves this issue
MoSCoW
Could have
Could have feature in order to satisfy the epic/legendary.
MoSCoW
Must have
Must have feature in order to satisfy the epic/legendary.
MoSCoW
Should have
Should have feature in order to satisfy the epic/legendary.
There are questions in the ticket that can not be completed until the project owner provides clarity.
Points
1
1 man-hours worth of work for an expert with no learning curve.
Points
13
13 man-hours worth of work for an expert with no learning curve.
Points
2
2 man-hours worth of work for an expert with no learning curve.
Points
21
21 man-hours worth of work for an expert with no learning curve.
Points
3
3 man-hours worth of work for an expert with no learning curve.
Points
34
34 man-hours worth of work for an expert with no learning curve.
Points
5
5 man-hours worth of work for an expert with no learning curve.
Points
55
55 man-hours worth of work for an expert with no learning curve.
Points
8
8 man-hours worth of work for an expert with no learning curve.
Points
88
88 man-hours worth of work for an expert with no learning curve.
Priority
Backlog
This ticket has backlogged priority and is not to be worked on yet
Priority
CI Blocker
Critical priority issue that blocks CI/CD pipeline and prevents PR merges
Priority
Critical
The priority is critical
Priority
High
The priority is high
Priority
Low
The priority is low
Priority
Medium
The priority is medium
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
When an epic or legendary is in review it must be signed off by owner, tech lead, and scrum master before being marked as completed.
A ticket for learning a tool or technology that is needed to be able to do future planning and design.
State
Completed
The ticket has been fully implemented, completed, and merged with the source code. This label should only be applied once a ticket is closed.
State
Duplicate
A ticket that represents the same content as an existing ticket.
State
In Progress
A ticket that is actively being developed.
State
In Review
A ticket that has had some code completed to implement but is waiting to pass peer review and is not yet merged in.
State
Paused
This ticket's work started but wasn't finished. It's on hold (likely in a feature branch) and will be resumed later, either due to a blocker or a delay.
State
Unverified
All new tickets start in this state. A developer may set it to show the ticket is unverified. This means we haven't agreed to work on it. It will either move to a verified state or be closed as wontdo.
State
Verified
The issue has been verified by a developer as legitimate. It will be worked on and verified tickets are now considered part of the backlog.
State
Wont Do
This ticket has been decided it wont be done. This may mean the bug has been determined to not be real (cant verify) or the feature is one we have decided we dont want to adopt.
Type
Automation
Any edits or discussion about the AI automated coding system.
Type
Bug
Something that doesnt work as intended.
Type
Discussion
Anytime a ticket represents a discussion about a subject and doesnt fall into one of the other categories.
Type
Documentation
An error or improvement needed in the documentation.
Type
Epic
Any first tier epic. That is, an epic which contains only issues as children and will not have sub-epics.
Type
Feature
Some new functionality not present.
Type
Legendary
A type of Epic which will contain other Epics.
Type
Refactor
A code change that restructures existing code without changing its external behavior.
Type
Support
Someone needs help using the project.
Type
Task
A generic task that doesnt fit into the other type categories.
Type
Testing
Work exclusively focusing on fixing or expanding testing.
No Label
Type
Task
Projects
Clear projects
No project
Assignees
aditya (Aditya Chhabra)
aleenaumair (Aleena Umair)
brent.edwards (Brent Edwards)
CoreRasurae (Luis Mendes)
drew (Drew Morris)
eugen.thaci (Eugen Thaci)
freemo (Jeffrey Phillips Freeman)
HAL9000 (HAL 9000)
HAL9001 (HAL9001)
hamza.khyari (Hamza Khyari)
hurui200320 (Rui Hu)
justin.morris
khird (Kyle Hird)
org.cleveragents
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Blocks
#887 fix(cli): add missing `--skill` flag to `actor run`
cleveragents/cleveragents-core
Reference: cleveragents/cleveragents-core#971
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feature/m4-actor-run-skill"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add the missing
--skillrepeatable flag toactor runandactor-runCLI commands, aligning the implementation with the specification (CLI Synopsis line 277). The flag enables ad-hoc skill injection at runtime without modifying YAML configuration.Closes #887
Changes
DI Container
container.py: Added_build_skill_service()factory andskill_serviceSingleton provider, following the established_build_*pattern. Falls back to in-memorySkillService()when the database is unavailable. Exception handling narrowed to(ImportError, OperationalError, DatabaseError, OSError)withexc_info=Truefor traceability.CLI Layer
actor.py: Added--skillTyper option (list[str] | None, repeatable,metavar="NAME"). Help text notes that skills only augment tool-bearing agents. Wrapped constructor in the existingtry/exceptblock soCleverAgentsExceptionfrom skill resolution is properly caught.actor_run.py: Same--skilloption withmetavar="NAME". Exception handler catchesCleverAgentsException(matching master — not broadened toCleverAgentsError).skill.py: Removed module-level_servicecache._get_skill_service()now always delegates toget_container().skill_service()so thatreset_container()correctly invalidates the cached instance._reset_skill_service()now overrides the container's provider viaproviders.Object(). Removed deadvalidate_skill_names()function.Runtime Layer
application.py(438 lines, down from 625):ReactiveCleverAgentsAppgainsskill_namesparameter with automatic deduplication viadict.fromkeys._resolve_skills()obtainsSkillServicefrom the DI container (no CLI layer import). Separateexcept KeyErrorandexcept ValueErrorproduce distinct error messages ("not found in registry"vs"resolution failed: {exc}"). Skill tools are only injected into agents that already have tools (if self._resolved_skill_tools and tools:), preventing LLM agents from being converted to pass-throughSimpleToolAgentinstances. When skill tools are skipped for tool-less agents,logger.debugemits a diagnostic message._sanitize_skill_name()validates skill name format with tightened regex:^[\w.-]{1,127}/[\w.-]{1,127}$withre.ASCIIflag. Zero-tool skill warning now useslogger.warning(notprint(stderr)), ensuring structured log output and proper log-level filtering.graph_executor.py(334 lines): Extracted graph execution logic. Type annotations improved.Tests
_sanitize_skill_nameedge cases (empty string, too-long name, ANSI escape codes, disallowed characters),_build_skill_servicehappy+fallback paths,_get_skill_servicecontainer delegation.actor.pyandactor_run.pyexercise the real error chain (mock onlyget_container(), not the entireReactiveCleverAgentsApp), testing_resolve_skills()→CleverAgentsException→except CleverAgentsException→ exit code 2 end-to-end.ContextManagerwas instantiated andexists()was called in dedicated Then steps.@coveragetags added to all new scenarios.robot/skill_actor_run.robot+robot/helper_skill_actor_run.py): unknown-skill error path and valid-skill acceptance path.Changelog
## UnreleasedinCHANGELOG.md.Review Fixes Applied (Brent Edwards, Rounds 1 & 2)
print(stderr)for zero-tool skill warninglogger.warning("Skill '%s' resolved to zero tools", name), removed unusedimport syslogger.debugwhen skipped; updated--skillhelp text to note "only augments tool-bearing agents"container.pyat 739 lines_build_skill_service); extracting factories is a separate refactoring taskCleverAgentsException→CleverAgentsErrorbroadens catch scopeactor_run.pytoexcept CleverAgentsExceptionmatching master--skillskill_actor_run.robotwith 2 test cases (unknown-skill error, valid-skill acceptance)GraphExecutor._follow_chained_edgesstatic-calling-staticKnown Limitations / Deferred Items
actor.pyat 679 lines (500-line guideline)--skill. Refactoring the shared_execute()closure is a separate task.container.pyat 739 lines (500-line guideline)_build_skill_service()andskill_serviceprovider. Refactoring into sub-modules is a separate task.actor.pyandactor_run.pyrun()SimpleToolAgentonly executestools[0]GraphExecutor._follow_chained_edgesstatic-calling-static patternQuality Gates (post-rebase onto
2434253c)nox -s lint: ✅ PASSnox -s typecheck: ✅ PASS (0 errors)nox -s unit_tests: ✅ PASS (11,455 scenarios, 0 failures)nox -s integration_tests: ✅ PASS (1,599/1,600 — 1 flakyUko Indexer > File Watchingpasses in isolation)nox -s e2e_tests: ✅ PASS (37 tests, 37 passed)nox -s coverage_report: ✅ 97% (meets threshold)master(2434253c)Self-Review:
fix(cli): add --skill flag to actor run commandVerdict: REQUEST CHANGES
Specification Compliance
All 6 acceptance criteria from ticket #887 are met. The
--skillflag aligns with the spec (line 277), the help text matches spec line 4582, the flag is repeatable, optional, and errors on unknown skill names. The flag is present in bothactor.pyandactor_run.py. No blocking compliance gaps.Critical Issues
C1. Silent DB fallback in
_create_skill_service()produces misleading errorssrc/cleveragents/reactive/application.py:127-128except Exception:silently falls back to an empty in-memorySkillService()when the DB connection fails. If the database is unreachable, all skill lookups silently fail with"Skill 'X' not found in registry"even though the skill exists in the DB. No warning is logged. This violates CONTRIBUTING.md: "Do not use bare catch-all exception handlers without re-raising unless you have specific recovery logic."logger.warning("Failed to create DB-backed SkillService, falling back to in-memory", exc_info=True). (2) Narrow the catch toImportError(SQLAlchemy not installed) and let other exceptions propagate. (3) Consider if silent degradation is appropriate here at all.C2.
_validate_skill_names()has zero real test coveragefeatures/steps/actor_cli_run_steps.py(all skill-related steps)_validate_skill_namesis fully mocked viapatch(...). The actual function body — which imports_get_skill_service, iterates names, callsservice.get_skill(), and raisestyper.Exit(code=2)— is never executed by any test. Both copies of this function (inactor.py:32-46andactor_run.py:16-30) have zero line coverage of actual logic._get_skill_service(not_validate_skill_namesitself) and exercise the real validation logic — both success and KeyError paths.Major Issues
M1.
_resolve_skills()only catchesKeyError, missesValueErrorsrc/cleveragents/reactive/application.py:96resolve_tools()can raiseValueErrorfor cycle detection, empty skill names, and missing included skills. These propagate uncaught as generic "Unexpected error" messages.except (KeyError, ValueError) as exc:.M2. DRY violation:
_validate_skill_names()duplicated identically in two modulessrc/cleveragents/cli/commands/actor.py:32-46andactor_run.py:16-30actor.py:28acknowledges this. If a bug fix is applied to one copy, the other may be missed._get_skill_service()inskill.py, or a new_shared.py).M3.
_create_skill_service()returnsAny— defeats type checkingsrc/cleveragents/reactive/application.py:108Anymeans Pyright cannot verify.resolve_tools()calls. Violates CONTRIBUTING.md type safety requirements.-> SkillService:.M4.
_create_skill_service()duplicates_get_skill_service()without singleton caching — double initializationsrc/cleveragents/reactive/application.py:107-128_get_skill_service()fromskill.py, but without the module-level singleton. Each call creates a new SQLAlchemy engine and session factory. Combined with CLI validation using_get_skill_service(), skills are validated against one service instance and resolved against a different one (TOCTOU risk)._get_skill_service()fromskill.pyinstead of creating a parallel implementation. This also eliminates the TOCTOU between validation and resolution.M5.
entry.overridesbranch in_resolve_skills()is untestedsrc/cleveragents/reactive/application.py:93-94if entry.overrides: tool_dict["overrides"] = dict(entry.overrides)branch is never exercised — all test fixtures createResolvedToolEntrywithoutoverrides.overrides={"timeout": 600}and assert the dict contains it.M6.
_create_skill_service()has zero test coveragesrc/cleveragents/reactive/application.py:107-128M7. "Unknown skill" test assertions are partially tautological
features/steps/actor_cli_run_steps.py:909-911exit_code == 2. Since_validate_skill_namesis mocked withside_effect=_raise_exit(code=2), the test passes regardless of whether the source code actually validates skills. No assertion on error message content or thatReactiveCleverAgentsAppwas NOT constructed.ReactiveCleverAgentsAppwas never called. Better: test the real function per C2.Minor Issues
m1. Duplicate
--skillflags produce duplicate tool entriessrc/cleveragents/reactive/application.py:82-95--skill a --skill aresolves the same skill twice, appending duplicate tools to every agent.self._skill_names = list(dict.fromkeys(skill_names or [])).m2. Skill tools injected into ALL agents — may convert LLM agents to tool agents
src/cleveragents/reactive/application.py:167-168SimpleToolAgentbecauseif tools:is now truthy. This changes agent behavior. Untested path.if self._resolved_skill_tools and tools:.m3.
_validate_skill_names()doesn't catchValueErrorfor empty string skill namessrc/cleveragents/cli/commands/actor.py:42-46get_skill("")raisesValueError("Skill name cannot be empty"), notKeyError. This propagates as "Unexpected error" instead of a clean CLI message.(KeyError, ValueError)or validate for empty strings first.m4. Exception base class inconsistency between CLI modules
src/cleveragents/cli/commands/actor.py:198vsactor_run.py:167actor.pycatchesCleverAgentsError, whileactor_run.pycatchesCleverAgentsException. If_resolve_skills()raisesCleverAgentsExceptionandCleverAgentsErroris not its parent,actor.pywould mishandle it.m5. No test for
--skillcombined with--contextfeatures/actor_cli_coverage.feature--skilland--contexttogether. A regression causing interference would go undetected.m6. No format validation on
--skillinputsrc/cleveragents/cli/commands/actor.py:125-131namespace/nameformat.m7. User-supplied skill names echoed directly in error messages
src/cleveragents/cli/commands/actor.py:45m8. Cross-module use of private function
_get_skill_service()src/cleveragents/cli/commands/actor.py:38get_skill_service()or create a public factory.Suggestions
logger.warning.TypedDictfor skill tool dicts instead ofdict[str, Any]to enable compile-time checking.SkillServiceto the project's DI container instead of manual wiring.skill_sourcefield in the basic resolve test (only checked in the merge test).features/mocks/per CONTRIBUTING.md (codebase-wide pattern though).Summary
The core feature implementation is correct and all acceptance criteria are met. However, the critical issues (silent error swallowing in
_create_skill_service()and zero real test coverage on_validate_skill_names()) and major items (missingValueErrorhandling, DRY violations, type safety, TOCTOU risk) warrant changes before merge.d92c5b1f16to4604f5d13dReview Response — All Critical, Major, and Applicable Minor Issues Addressed
Force-pushed
4604f5d1with the following changes in response to the self-review.Critical Issues
C1. Silent DB fallback in
_create_skill_service()→ FIXEDlogger.warning("Failed to create DB-backed SkillService, falling back to in-memory", exc_info=True)to_get_skill_service()inskill.py._create_skill_service()method inapplication.pyhas been entirely removed (see M4), so the duplicate silent fallback is gone.C2.
_validate_skill_names()has zero real test coverage → FIXED_get_skill_service(returning a mock service) instead of patching_validate_skill_namesitself.validate_skill_names()function body executes in every test:get_skill()is called on the mock service,KeyErrorpropagation triggerstyper.Exit(code=2), etc.mock_service.get_skillwas called with the correct arguments.ReactiveCleverAgentsAppwas never instantiated.Major Issues
M1.
_resolve_skills()only catchesKeyError→ FIXEDexcept (KeyError, ValueError) as exc:.M2. DRY violation:
_validate_skill_names()duplicated → FIXEDvalidate_skill_names()(public) inskill.py.actor.pyandactor_run.pyimport and call this single implementation.M3.
_create_skill_service()returnsAny→ FIXED_resolve_skills()now calls_get_skill_service()fromskill.py, which returnsSkillService.M4.
_create_skill_service()duplicates_get_skill_service()→ FIXED_create_skill_service()fromReactiveCleverAgentsApp._resolve_skills()now does a lazy import of_get_skill_servicefromskill.py, reusing the module-level singleton.M5.
entry.overridesbranch untested → FIXEDResolvedToolEntry(overrides={"timeout": 600, "retries": 3}).overrideskey is present and contains the correct dict.M6.
_create_skill_service()has zero test coverage → N/A (method removed)_create_skill_service()method no longer exists._get_skill_service()inskill.pyalready has dedicated test coverage viaskill_cli_coverage_boost.feature.M7. "Unknown skill" test assertions are tautological → FIXED
context.result.exit_code == 2"Error: Skill 'local/nonexistent' not found in registry" in context.result.outputcontext.mock_app_cls.assert_not_called()(ReactiveCleverAgentsApp never instantiated)Minor Issues
m1. Duplicate skills produce duplicate tool entries → FIXED
self._skill_names = list(dict.fromkeys(skill_names or [])).m2. Skill tools injected into ALL agents → ACKNOWLEDGED
--skillaugments the entire run. If a more selective injection is needed, that should be specified in a follow-up ticket.m3.
_validate_skill_names()doesn't catchValueError→ FIXEDvalidate_skill_names()inskill.pynow catches(KeyError, ValueError).m4. Exception base class inconsistency → FIXED
actor_run.pynow catchesCleverAgentsError(same base asactor.py) instead ofCleverAgentsException.m5. No test for
--skillcombined with--context→ FIXEDactor_cli_coverage.feature.m6. No format validation on
--skillinput → DEFERREDm7. User-supplied skill names echoed directly → DEFERRED
m8. Cross-module use of private
_get_skill_service()→ PARTIALLY ADDRESSEDvalidate_skill_names()is now the public API that both CLI modules import. The internal_get_skill_serviceremains underscore-prefixed but is used cross-module by the reactive layer via lazy import. A full rename toget_skill_service()would touch 60+ references across test files and is better scoped as a separate cleanup issue.Suggestions
S1. No warning when skill resolves to zero tools → IMPLEMENTED
logger.warning("Skill '%s' resolved to zero tools — no-op injection", name).S4. Assert
skill_sourcefield in basic resolve test → IMPLEMENTEDskill_sourceassertions instep_app_has_resolved_tools.S2, S3, S5 → DEFERRED (TypedDict, DI container, mock extraction — separate scope).
Quality Gates
nox -s lintnox -s typechecknox -s unit_testsnox -s coverage_reportnox -s integration_testsCode Review:
fix(cli): add --skill flag to actor run commandReviewer: Rui Hu (
hurui200320)Verdict: REQUEST CHANGES
This review covers the current state of the codebase on branch
feature/m4-actor-run-skill(commit4604f5d1). The previous self-review (comment 65047) and response (comment 65051) have been verified — most items are addressed, but several remain open and new issues were found. Only unresolved issues are listed below.Specification Compliance
All 6 acceptance criteria from ticket #887 are met. The
--skillflag aligns with spec line 4582, the help text matches, the flag is repeatable, optional, and errors on unknown skill names. Bothactor.pyandactor_run.pyentry points have the flag. No spec compliance gaps found.Critical Issues
C1. Skill injection converts LLM agents into pass-through SimpleToolAgents
src/cleveragents/reactive/application.py:154-162_make_agent_instance()unconditionally appends_resolved_skill_toolsto every agent's tool list, including agents oftype == "llm"with zero original tools. After injection,toolsis non-empty, so theif tools:branch returns aSimpleToolAgentinstead ofSimpleLLMAgent. The LLM agent loses its system prompt, model configuration, and LLM invocation entirely. Since injected skill tools have"operation": "identity", the agent becomes a no-op pass-through.C2. Appended skill tools are silently ignored by
SimpleToolAgent.process()src/cleveragents/reactive/stream_router.py:156SimpleToolAgent.process()only executesself.tools[0]. When skill tools are appended to an agent that already has tools (application.py:157), the skill tools at indices 1, 2, ... are never executed. The user believes skills are being applied, but they have zero runtime effect for agents with existing tools.SimpleToolAgent.process(), or (c) document this as a known limitation and clarify the expected behavior in the spec.C3. Architectural layer violation — Reactive layer imports from CLI layer
src/cleveragents/reactive/application.py:86_resolve_skills()imports_get_skill_servicefromcleveragents.cli.commands.skill. This creates a dependency from the runtime/domain layer (reactive) into the presentation layer (cli.commands), violating the Dependency Inversion Principle. This is the only place in the entirereactive/package that imports fromcli/. It also imports a private function (underscore-prefixed), coupling to an implementation detail.SkillServicein the DI container (application/container.py) as a proper provider, consistent with other services (ActorService, PlanService, etc.). Then inject it intoReactiveCleverAgentsAppvia constructor parameter or container resolution.Major Issues
M1. Misleading error message masks cycle detection and resolution failures
src/cleveragents/reactive/application.py:108-111_resolve_skills()catches bothKeyErrorandValueErrorbut always reports"Skill '{name}' not found in registry".ValueErroris raised for cycle detection, empty skill names, and missing included skills — none of which are "not found" errors. The originalValueErrormessage is lost at the user level.(KeyError, ValueError), but the error message was not differentiated.M2. DB failure silently masquerades as "skill not found"
src/cleveragents/cli/commands/skill.py:104-109SkillServicecreation fails, the fallback creates an empty in-memorySkillService(). All subsequentget_skill()calls raiseKeyError, causingvalidate_skill_names()to report "Skill 'X' not found in registry" even for skills that exist in the DB. Thelogger.warningadded in the C1 fix is only visible at verbose logging levels (not the default).typer.echoto stderr, or log at a level that is visible by default.M3. Missing CHANGELOG.md entry
CHANGELOG.md— not modified## Unreleaseddescribing the--skillflag addition.M4.
application.pyexceeds 500-line limitsrc/cleveragents/reactive/application.py— 625 lines_parse_graph_topology,_initialize_graph_context,_match_router_rule,_follow_chained_edges,_execute_graph_route— ~170 lines) into a separate module.M5. Double skill resolution — validate then re-resolve
src/cleveragents/cli/commands/actor.py:113-114andsrc/cleveragents/reactive/application.py:86-117service.get_skill(name)invalidate_skill_names(), thenservice.resolve_tools(name)(which callsget_skill()again internally) in_resolve_skills(). The service is designed for DB persistence — ifget_skillhits the DB, every skill incurs two round-trips.validate_skill_names()call. Let_resolve_skills()inReactiveCleverAgentsAppbe the single point of validation and resolution. ItsCleverAgentsExceptionis already caught by the CLI'sexcept CleverAgentsErrorhandler.M6. Missing
--skill+--contextcombined test foractor_run.pyfeatures/actor_run_cli_coverage.featureactor_cli_coverage.featurehas 4 skill scenarios including a combined test, butactor_run_cli_coverage.featureonly has 3. Since both CLI entry points have independent code paths, a regression inactor_run.py's combined path would go undetected.M7. Duplicate skill deduplication behavior not tested
src/cleveragents/reactive/application.py:41dict.fromkeys, but no test verifies this. If the dedup line were removed, no test would catch the regression.resolve_toolsis called once per unique name.Minor Issues
m1. Zero-tool skill warning invisible at default log level
src/cleveragents/reactive/application.py:92-96verbose=0, logging isCRITICAL. Thelogger.warningis never displayed. Users get no feedback when--skillresolves to nothing.typer.echoto stderr, or raise an error.m2. Validation and resolution use different operations (semantic gap)
skill.py:138-139vsapplication.py:91validate_skill_names()callsget_skill()(existence check), while_resolve_skills()callsresolve_tools()(structural resolution including cycle detection). A skill with a circular include chain passes validation but fails resolution with different error paths.m3. Missing edge case test: empty string skill name (
--skill "")m4. Inconsistent mock import style within PR
features/steps/reactive_application_coverage_boost_steps.py:313, 376, 387, 416, 505actor_cli_run_steps.pyimports mocks at module level; this file imports them inside individual step functions (5 times).m5. "Skill + Context" combined test doesn't verify context behavior
features/steps/actor_cli_run_steps.py:931-978--skillside. No assertion verifiesContextManagerwas properly used alongside skills.Thensteps verifyingContextManagerinstantiation and interaction.m6. Unnecessary
list()copy of resolved skill toolssrc/cleveragents/reactive/application.py:157list(self._resolved_skill_tools)creates an unnecessary copy. The list is never mutated after_resolve_skills().self._resolved_skill_toolsdirectly.Suggestions
actor.pyandactor_run.pyrun()functions is nearly verbatim (~146 lines each). Extract shared execution logic into a common helper. (Pre-existing, worsened by this PR.)skill.pyis 1,169 lines — over 2x the 500-line limit. Consider splitting. (Pre-existing.)SkillServicein the DI container to align with the project's DI preference over singletons.Summary
The core feature implementation meets all acceptance criteria and the CLI flag definition matches the specification exactly. However, the critical issues — particularly the skill injection converting LLM agents into pass-throughs (C1), the fact that injected skill tools are never actually executed by
SimpleToolAgent(C2), and the architectural layer violation (C3) — require changes before merge. The first two issues mean--skillis effectively non-functional for the majority of use cases, despite passing all existing tests.4604f5d13dtoaa18d0f7e6Review Response — Second Review Issues Addressed
Force-pushed
aa18d0f7with the following changes in response to the second code review (comment 65077).Critical Issues
C1. Skill injection converts LLM agents into pass-through SimpleToolAgents → FIXED
_make_agent_instance()inapplication.pynow guards injection:if self._resolved_skill_tools and tools:. LLM agents with zero original tools are never converted toSimpleToolAgent.llm-type agent remains aSimpleLLMAgentwhile a tool agent correctly receives injected skill tools.C2. Appended skill tools are silently ignored by
SimpleToolAgent.process()→ DEFERRED to #974fix(reactive): SimpleToolAgent only executes first tool — skill tools at later indices are ignored.SimpleToolAgent.process()(only usestools[0]), not introduced by this PR. Fixing it requires redesigning the tool execution pipeline (multi-tool processing, tool routing, or LLM-based tool selection), which is out of scope for the--skillCLI flag addition.C3. Architectural layer violation — Reactive layer imports from CLI layer → FIXED
_build_skill_service()factory andskill_serviceSingleton provider tocontainer.py, following the established_build_*pattern used by 7 other DB-backed services._resolve_skills()inapplication.pynow obtainsSkillServicefromget_container().skill_service()— importing fromcleveragents.application.container(application layer), not fromcleveragents.cli.commands.skill(presentation layer).from cleveragents.cli.commands.skill import _get_skill_service) has been completely removed from the reactive layer.Major Issues
M1. Misleading error message masks cycle detection and resolution failures → FIXED
except KeyErrorandexcept ValueErrorwith distinct messages:KeyError→"Skill '{name}' not found in registry"ValueError→"Skill '{name}' resolution failed: {exc}"M2. DB failure silently masquerades as "skill not found" → FIXED
typer.echo("Warning: database unavailable — skill lookups use in-memory store (run 'agents init' first)", err=True)in_get_skill_service()inskill.py. This message is always visible to CLI users regardless of log level._build_skill_service()also logs via structlog at WARNING level.M3. Missing CHANGELOG.md entry → FIXED
## Unreleaseddescribing the--skillflag addition.M4.
application.pyexceeds 500-line limit → FIXEDsrc/cleveragents/reactive/graph_executor.py(334 lines).GraphExecutorclass encapsulates:_parse_topology,_initialize_context,_match_router_rule,_follow_chained_edges,execute, andstrip_routing_prefixes.application.pyis now 411 lines (down from 625), well under the 500-line limit.ReactiveCleverAgentsAppfor existing test compatibility.M5. Double skill resolution — validate then re-resolve → FIXED
validate_skill_names()import and call from bothactor.pyandactor_run.py._resolve_skills()inReactiveCleverAgentsAppis now the single point of validation and resolution.ReactiveCleverAgentsApp(...)constructor is now inside thetry/exceptblock in both CLI modules, soCleverAgentsExceptionfrom skill resolution is caught and displayed as"Error: ..."with exit code 2.M6. Missing
--skill+--contextcombined test foractor_run.py→ FIXEDactor_run_cli_coverage.feature.M7. Duplicate skill deduplication behavior not tested → FIXED
actor_run_cli_coverage.feature.reactive_application_coverage_boost.featurethat verifiesresolve_toolsis called once per unique skill name.Minor Issues
m1. Zero-tool skill warning invisible at default log level → ACKNOWLEDGED
logger.warningin_resolve_skills()is only visible atverbose >= 2. This is consistent with how other reactive layer warnings behave. Users can use-vvto see warnings. The M2 fix addresses the DB-level visibility concern separately.m2. Validation and resolution use different operations → ADDRESSED by M5
m3. Missing edge case test: empty string skill name → FIXED
ValueErrorfromresolve_tools("")produces aCleverAgentsExceptionwith"resolution failed"in the message.m4. Inconsistent mock import style → PARTIALLY ADDRESSED
reactive_application_coverage_boost_steps.pycontinues to use local imports in step functions becauseMagicMock/patchare only needed in specific steps, not all steps in the module. This matches the step-level isolation pattern used elsewhere.m5. "Skill + Context" combined test doesn't verify context behavior → FIXED
ContextManagermock and verify it was instantiated.m6. Unnecessary
list()copy of resolved skill tools → FIXEDlist(tools) + list(self._resolved_skill_tools)tolist(tools) + self._resolved_skill_tools— only the first operand needs copying to avoid mutating the config list.Suggestions
S1, S2, S3 → DEFERRED (DI container for SkillService is now done via C3; TypedDict and mock extraction are separate scope).
Quality Gates
nox -s lintnox -s typechecknox -s unit_testsnox -s coverage_reportnox -s integration_testsPM Day 36 Triage: --skill flag for actor run. Closes #887. Self-reviews by @hurui200320 identified and fixed Critical issues (C1: skill injection breaks LLM agents, C2: tools silently ignored, C3: architecture layer violation). All addressed. New issue #974 created for deferred SimpleToolAgent multi-tool execution. Reviewer needed: @CoreRasurae. M5 scope.
aa18d0f7e6to10ac32b0fe10ac32b0fetoc3c81efb6fSelf-QA Verdict: ✅ Approved
This PR has completed 3 automated self-QA review/fix cycles. All critical and major issues identified during the process have been resolved. The PR is now ready for external review.
Self-QA Summary
Key Improvements Made During Self-QA
validate_skill_names())except Exception:to specific exception typesSkillServiceinstances (CLI and reactive layer now share the DI singleton)_sanitize_skill_name()) with ASCII regex, namespace/name enforcement, and control character stripping@coveragetags, consolidated step definitions)GraphExecutortypes fromAnyto specific typesQuality Gates (Final)
Full implementation notes for all 3 cycles are posted on ticket #887.
c3c81efb6ftoef73dafae6PM Status — Day 37 — Rebase Required
This PR has merge conflicts and cannot be merged in its current state. 42% of all open PRs (21 of 50) have conflicts — this is a project-wide issue that must be resolved.
@hurui200320 — Please rebase this PR onto
masterby Day 39 EOD (2026-03-19). If you cannot rebase by then, please post a comment explaining the blocker.PM rebase request — Day 37
Code Review — PR #971
fix(cli): add --skill flag to actor runReviewer: @brent.edwards | Size: XL (+1488/−346, 12 files) | Focus: DI, CLI wiring, runtime, security
P1:must-fix (1)
1.
print()to stderr instead oflogger.warningfor zero-tool skillapplication.py:117-119— The zero-tool warning usesprint(f"Warning: ...", file=sys.stderr)while the rest of the module usesstructloglogging. This bypasses log-level filtering, structured output, and log aggregation. If a user passes--skill local/empty-skillin a CI pipeline that parses structured logs, this warning is invisible.Fix:
logger.warning("skill_resolved_zero_tools", skill_name=name)P2:should-fix (3)
2. Skill tools silently skipped for tool-less agents —
application.py:183:if self._resolved_skill_tools and tools:means pure LLM agents never receive skill tools even when--skillis specified. This is arguably correct (can't add tools toSimpleLLMAgent) but undocumented. Add alogger.debugwhen skipped, and note in--skillhelp text.3.
container.pyat 739 lines (was 680 on master, +59). File size pressure worsening. Consider extracting_build_*factories in a follow-up.4. Exception rename from
CleverAgentsExceptiontoCleverAgentsErrorinactor_run.pyis an unrelated broadening bundled into the skill-flag PR. Should be a separate commit for clean bisectability.P3:nit (2)
5. No Robot Framework smoke test for
--skill. PRs #972/#973 both include.robotfiles.6.
GraphExecutor._follow_chained_edgesis@staticmethodbut callsGraphExecutor._invoke_agentby class name — works but unusual pattern.Positive Observations
^[\w.-]{1,127}/[\w.-]{1,127}$withre.ASCIIis tight and correct_build_skill_serviceexception handling narrowed to(ImportError, OperationalError, DatabaseError, OSError)— much better than genericexceptapplication.pydropped from 563→436 lines viaGraphExecutorextraction — excellentdict.fromkeyspreserves insertion order — correctVerdict: REQUEST_CHANGES — P1-1 is a quick fix.
Code Review Round 2 — PR #971
fix(cli): add --skill flag to actor runReviewer: @brent.edwards | Focus: Verification of Round 1 fixes + deep second pass
Prior Findings: 0 of 6 resolved
All Round 1 findings remain open:
print(stderr)for zero-tool skillprint(..., file=sys.stderr)atapplication.py:118-120container.pyat 739 lines--skillUpgraded Finding
P2-4 elevated:
CleverAgentsException→CleverAgentsErrorbroadens catch scopeactor_run.py:148—CleverAgentsExceptionis a subclass ofCleverAgentsError. Catching the base class instead of the subclass broadens what's silently caught — any code raisingCleverAgentsError(but not the subclass) will now be caught instead of propagating. This is a behavioral change bundled into a feature PR without test coverage.Fix: Revert to
CleverAgentsExceptionin this PR, or split into its own PR with tests.Confirmed Clean from Second Pass
_sanitize_skill_nameregexre.ASCIIrejects Unicode confusablesapplication.pyandgraph_executor.py_servicecache_build_skill_servicefallbackSkillService()raisesKeyErrordownstreamVerdict: REQUEST_CHANGES — P1-1 (
print(stderr)) is a one-line fix:ef73dafae6to7127effd26Review Response — Brent Edwards Rounds 1 & 2 Addressed
Force-pushed
7127effdwith the following changes. Branch has been rebased onto latestmaster(ab1fd19b) per PM Day 37 request.P1-1:
print(stderr)for zero-tool skill warning → FIXEDprint(f"Warning: Skill '{name}' resolved to zero tools", file=sys.stderr)withlogger.warning("Skill '%s' resolved to zero tools", name)inReactiveCleverAgentsApp._resolve_skills().import sysfromapplication.py.structlog/logging usage.P2-2: Skill tools silently skipped for tool-less agents → FIXED
logger.debug("Skipping skill tool injection for agent '%s' (agent has no base tools)", name)in_make_agent_instance()when_resolved_skill_toolsis non-empty but the agent has no base tools.--skillhelp text in bothactor.pyandactor_run.pyfrom"Skill to attach (repeatable)"to"Skill to attach; only augments tool-bearing agents (repeatable)"— making the behavior explicit to CLI users.P2-3:
container.pyat 739 lines → ACKNOWLEDGED_build_skill_service). Extracting_build_*factories into sub-modules is a separate refactoring task outside the scope of the--skillCLI flag addition.P2-4↑ (elevated):
CleverAgentsException→CleverAgentsErrorbroadens catch scope → FIXEDactor_run.pyfromexcept CleverAgentsErrorback toexcept CleverAgentsException, matching the pre-existing master behavior.from cleveragents.core.exceptions import CleverAgentsException, UnsafeConfigurationError.actor.pyretainsCleverAgentsErroras it was already that way on master.P3-5: No Robot Framework smoke test for
--skill→ FIXEDrobot/skill_actor_run.robotwith 2 test cases:--skill local/nonexistent-skillexits with code 2 and error message containing "not found in registry".--skill local/smoke-skillis accepted by the CLI when the skill exists in the in-memory registry.robot/helper_skill_actor_run.pyas the Python helper script following the established pattern.P3-6:
GraphExecutor._follow_chained_edgesstatic-calling-static → ACKNOWLEDGEDgraph_executor.py.Quality Gates (post-fix)
nox -s lintnox -s typechecknox -s unit_testsnox -s integration_testsnox -s coverage_reportBranch rebased onto latest
master(ab1fd19b), resolving merge conflict inCHANGELOG.md.7127effd26to46670916394667091639to629891ed3dCode Review — PR #971
fix(cli): add --skill flag to actor run commandWell-structured implementation. The
GraphExecutorextraction is a clean architectural improvement that reducesapplication.pyfrom 625 to 438 lines. The--skilloption,_resolve_skills(), and skill tool injection are well-designed. 24+ Behave scenarios + 2 Robot tests + benchmarks demonstrate thorough coverage. Quality gates all pass (11,130 scenarios, 97% coverage).Approved with acknowledged tech debt:
Code duplication — ~47 identical lines between
actor.pyandactor_run.py. The PR body acknowledges this and defers to a future refactoring task. This is acceptable as a conscious decision.File size —
container.pyat 739 lines andactor.pyat 679 lines both exceed the 500-line guideline from CONTRIBUTING.md. Again acknowledged and deferred. Consider creating the follow-up issue now so it's tracked.Skill name regex —
^[\w.-]{1,127}/[\w.-]{1,127}$withre.ASCIIis a good constraint. Well-designed validation.Exception narrowing — Good practice catching
(ImportError, OperationalError, DatabaseError, OSError)instead of bareexcept Exception.Planning Agent — Day 39 PR Review Assessment
PR #971 (--skill flag for actor run) has been through 3 rounds of self-QA by @hurui200320 and 2 rounds of external review by @brent.edwards. This is one of the most thoroughly reviewed PRs in the project.
Summary of review cycles:
SimpleToolAgentlimitation deferred to #974 (C2), DI container wiring replaces CLI import (C3)Architecture assessment: The DI container approach for
SkillServiceis correct. The LLM agent guard (if self._resolved_skill_tools and tools:) is sound. The deferred item (#974 —SimpleToolAgentonly executes first tool) is a legitimate pre-existing limitation.Merge recommendation: Ready for merge. Rebased on latest master. All gates passing. Reviewers (@brent.edwards, @freemo) should approve. The deferred items are properly tracked:
629891ed3dtoaf94b7edb2New commits pushed, approval review dismissed automatically according to repository settings
af94b7edb2to78b1518310Rebase onto latest master — Day 39 response
Force-pushed
78b15183— branch rebased onto latestmaster(2434253c) per PM Day 37 request.Merge conflicts resolved
Three files had conflicts due to parallel work on
master:CHANGELOG.md— Both master (E2E acceptance tests,session_factoryDI provider, Google API key redaction — #745) and this branch (--skillflag — #887) added entries under## Unreleased. Kept both entries.features/application_container_coverage_boost.feature— Master added_build_session_factoryscenarios; this branch added_build_skill_servicescenarios. Kept both sets of scenarios.features/steps/application_container_coverage_boost_steps.py— Same pattern: master added session factory step definitions and import; this branch added skill service step definitions and import. Merged both imports and both step definition blocks.Quality Gates (all passing post-rebase)
nox -s lintnox -s typechecknox -s unit_testsnox -s integration_testsUko Indexer > File Watchingpasses in isolation, unrelated)nox -s e2e_testsnox -s coverage_reportPR is now mergeable with no conflicts.