fix(cli): add --skill flag to actor run command #971
No reviewers
Labels
No labels
auto/needs-reevaluation
controller-managed
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
Bounty
$100
Bounty
$1000
Bounty
$10000
Bounty
$20
Bounty
$2000
Bounty
$250
Bounty
$50
Bounty
$500
Bounty
$5000
Bounty
$750
MoSCoW
Could have
MoSCoW
Must have
MoSCoW
Should have
Needs Feedback
Points
1
Points
13
Points
2
Points
21
Points
3
Points
34
Points
5
Points
55
Points
8
Points
88
Priority
Backlog
Priority
CI Blocker
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Signed-off: Owner
Signed-off: Scrum Master
Signed-off: Tech Lead
Spike
State
Completed
State
Duplicate
State
In Progress
State
In Review
State
Paused
State
Unverified
State
Verified
State
Wont Do
Type
Automation
Type
Bug
Type
Discussion
Type
Documentation
Type
Epic
Type
Feature
Type
Legendary
Type
Refactor
Type
Support
Type
Task
Type
Testing
No project
No assignees
3 participants
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
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
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)--skillflag toactor runSelf-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.d92c5b1f164604f5d13dReview 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.4604f5d13daa18d0f7e6Review 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.
aa18d0f7e610ac32b0fe10ac32b0fec3c81efb6f--skillflag toactor run#887Self-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.
c3c81efb6fef73dafae6PM 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:ef73dafae67127effd26Review 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.7127effd2646670916394667091639629891ed3dCode 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:
629891ed3daf94b7edb2New commits pushed, approval review dismissed automatically according to repository settings
af94b7edb278b1518310Rebase 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.