feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm #11

Closed
opened 2026-06-03 05:58:51 +00:00 by hurui200320 · 6 comments
Member

Background

Only an internal _merge_configs(base, new) instance method exists on ReactiveConfigParser. It is not publicly importable and mutates its input. The CleverThis router needs a public merge_configs() function to combine a platform base config with the actor config stored in the database (e.g., injecting default system prompts, org-level LLM settings, or per-tenant overrides).

Spec references: ADR-2024 (Router-Facing API), Actor Configuration Standard §3.1

What Is Currently Missing

  • No public merge_configs function exists.
  • The internal _merge_configs mutates base in place.
  • The internal method only handles two dicts; no variadic support.

Acceptance Criteria

Implement merge_configs(*dicts: dict[str, Any]) -> dict[str, Any] and export it from cleveractors/__init__.py:

  1. Merge semantics per Actor Configuration Standard §3.1:
    • Key absent from accumulated result → add it.
    • Both values are mappings → deep-merge recursively.
    • Both values are sequences → append new sequence to existing.
    • Otherwise → replace with new value.
  2. Returns a new dict — inputs are never mutated.
  3. Zero-argument call returns {}.
  4. Works with arbitrarily nested dicts and lists.
  5. Exported from cleveractors/__init__.py and listed in __all__.

Subtasks

  • Implement merge_configs(*dicts) as a module-level function
  • Ensure no input mutation (copy where needed)
  • Handle zero-argument call returning {}
  • Write BDD/unit tests: empty call, two dicts, three dicts, nested maps, nested sequences, scalar override
  • Export from cleveractors/__init__.py and __all__
  • Verify project coverage threshold is maintained

Definition of Done

  • All subtasks checked off.
  • from cleveractors import merge_configs works without error.
  • All existing and new tests pass.
  • Coverage at or above project threshold.
## Background Only an internal `_merge_configs(base, new)` instance method exists on `ReactiveConfigParser`. It is not publicly importable and mutates its input. The CleverThis router needs a public `merge_configs()` function to combine a platform base config with the actor config stored in the database (e.g., injecting default system prompts, org-level LLM settings, or per-tenant overrides). **Spec references:** ADR-2024 (Router-Facing API), Actor Configuration Standard §3.1 ## What Is Currently Missing - No public `merge_configs` function exists. - The internal `_merge_configs` mutates `base` in place. - The internal method only handles two dicts; no variadic support. ## Acceptance Criteria Implement `merge_configs(*dicts: dict[str, Any]) -> dict[str, Any]` and export it from `cleveractors/__init__.py`: 1. Merge semantics per Actor Configuration Standard §3.1: - Key absent from accumulated result → add it. - Both values are mappings → deep-merge recursively. - Both values are sequences → append new sequence to existing. - Otherwise → replace with new value. 2. Returns a **new dict** — inputs are never mutated. 3. Zero-argument call returns `{}`. 4. Works with arbitrarily nested dicts and lists. 5. Exported from `cleveractors/__init__.py` and listed in `__all__`. ## Subtasks - [x] Implement `merge_configs(*dicts)` as a module-level function - [x] Ensure no input mutation (copy where needed) - [x] Handle zero-argument call returning `{}` - [x] Write BDD/unit tests: empty call, two dicts, three dicts, nested maps, nested sequences, scalar override - [x] Export from `cleveractors/__init__.py` and `__all__` - [x] Verify project coverage threshold is maintained ## Definition of Done - All subtasks checked off. - `from cleveractors import merge_configs` works without error. - All existing and new tests pass. - Coverage at or above project threshold.
Author
Member

Implementation Notes & Inferred Metadata

Inferred Metadata (missing from issue body)

The issue body lacks a ## Metadata section as required by CONTRIBUTING.md. The following values have been inferred and are recorded here for traceability:

  • Commit Message: feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm
    (taken verbatim from the issue title, which is in Conventional Changelog format)
  • Branch: feature/merge-configs-api

Reference

  • Epic cleveragents/cleveragents-webapp#275
  • Design docs: repo=cleveragents/cleveragents-webapp, branch=develop, path=docs/
  • ADR docs: repo=cleveragents/cleveragents-webapp, branch=develop, path=docs/adr/

Spec Reference — §3.1 Deep-Merge Algorithm

Sourced from docs/actor-standard.md in cleveragents/cleveragents-webapp@develop:

A compliant implementation MUST support the merging of multiple Actor configuration files, applied in the order specified, using the following semantics:

  • For each key encountered in a subsequent file:
    • If the key is absent from the merged result, it is added.
    • If the key is present and both values are mappings, they are deeply merged (recursively).
    • If the key is present and both values are sequences, the new sequence is appended to the existing sequence.
    • Otherwise, the new value replaces the existing value.

Design Decisions

  1. Module placement: merge_configs() will be implemented as a module-level function in a new file src/cleveractors/config_utils.py. This follows the Single Responsibility Principle and keeps reactive/config_parser.py focused on parsing.

  2. Immutability: Unlike the existing _merge_configs instance method (which mutates base in place), the public merge_configs() function will use copy.deepcopy() to build a new accumulator at each step, ensuring input dicts are never modified.

  3. Variadic design: Instead of merge_configs(base, new), the public API accepts *dicts to allow merging an arbitrary number of configs in a single call (including zero args → returns {}).

  4. Existing _merge_configs unchanged: The internal method on ReactiveConfigParser will remain as-is to avoid breaking the existing parse path. The new public function is independent.

  5. Export: merge_configs will be added to cleveractors/__init__.py and __all__ so consumers can do from cleveractors import merge_configs.


Test Plan (BDD scenarios)

Feature file: features/merge_configs.feature
Steps file: features/steps/merge_configs_steps.py

Scenarios:

  • Zero-argument call returns {}
  • Single dict returns a copy (not identity)
  • Two dicts: missing-key insertion
  • Two dicts: scalar override
  • Two dicts: mapping deep-merge (recursive)
  • Two dicts: sequence concatenation
  • Three dicts: chained merge correctness
  • Deeply nested dicts and lists
  • Input mutation guard (inputs unchanged after call)

Implementation locations

  • src/cleveractors/config_utils.py — new module with merge_configs() function
  • src/cleveractors/__init__.py — add import and __all__ entry
  • features/merge_configs.feature — BDD scenarios
  • features/steps/merge_configs_steps.py — step definitions
  • CHANGELOG.md — new entry for this feature
## Implementation Notes & Inferred Metadata ### Inferred Metadata (missing from issue body) The issue body lacks a `## Metadata` section as required by CONTRIBUTING.md. The following values have been inferred and are recorded here for traceability: - **Commit Message**: `feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm` *(taken verbatim from the issue title, which is in Conventional Changelog format)* - **Branch**: `feature/merge-configs-api` ### Reference + Epic cleveragents/cleveragents-webapp#275 + Design docs: repo=cleveragents/cleveragents-webapp, branch=develop, path=docs/ + ADR docs: repo=cleveragents/cleveragents-webapp, branch=develop, path=docs/adr/ --- ### Spec Reference — §3.1 Deep-Merge Algorithm Sourced from `docs/actor-standard.md` in `cleveragents/cleveragents-webapp@develop`: > A compliant implementation MUST support the merging of multiple Actor configuration files, applied in the order specified, using the following semantics: > > - For each key encountered in a subsequent file: > - If the key is absent from the merged result, it is added. > - If the key is present and both values are mappings, they are deeply merged (recursively). > - If the key is present and both values are sequences, the new sequence is appended to the existing sequence. > - Otherwise, the new value replaces the existing value. --- ### Design Decisions 1. **Module placement**: `merge_configs()` will be implemented as a module-level function in a new file `src/cleveractors/config_utils.py`. This follows the Single Responsibility Principle and keeps `reactive/config_parser.py` focused on parsing. 2. **Immutability**: Unlike the existing `_merge_configs` instance method (which mutates `base` in place), the public `merge_configs()` function will use `copy.deepcopy()` to build a new accumulator at each step, ensuring input dicts are never modified. 3. **Variadic design**: Instead of `merge_configs(base, new)`, the public API accepts `*dicts` to allow merging an arbitrary number of configs in a single call (including zero args → returns `{}`). 4. **Existing `_merge_configs` unchanged**: The internal method on `ReactiveConfigParser` will remain as-is to avoid breaking the existing parse path. The new public function is independent. 5. **Export**: `merge_configs` will be added to `cleveractors/__init__.py` and `__all__` so consumers can do `from cleveractors import merge_configs`. --- ### Test Plan (BDD scenarios) Feature file: `features/merge_configs.feature` Steps file: `features/steps/merge_configs_steps.py` Scenarios: - Zero-argument call returns `{}` - Single dict returns a copy (not identity) - Two dicts: missing-key insertion - Two dicts: scalar override - Two dicts: mapping deep-merge (recursive) - Two dicts: sequence concatenation - Three dicts: chained merge correctness - Deeply nested dicts and lists - Input mutation guard (inputs unchanged after call) --- ### Implementation locations - `src/cleveractors/config_utils.py` — new module with `merge_configs()` function - `src/cleveractors/__init__.py` — add import and `__all__` entry - `features/merge_configs.feature` — BDD scenarios - `features/steps/merge_configs_steps.py` — step definitions - `CHANGELOG.md` — new entry for this feature
Author
Member

Implementation Complete — PR Submitted

All 6 subtasks are checked off. Pull request #19 has been submitted for review.

Files Changed

File Change
src/cleveractors/config_utils.py Newmerge_configs() and _apply_merge()
src/cleveractors/__init__.py Added merge_configs import and __all__ entry
features/merge_configs.feature New — 11 BDD scenarios
features/steps/merge_configs_steps.py New — step definitions
robot/config.robot 4 new integration test cases
robot/CleverActorsLib.py 4 new Robot keywords
benchmarks/merge_configs_benchmark.py New — 5 ASV benchmark methods
CHANGELOG.md New entry under [Unreleased] > Added

Quality Gate Summary

  • ruff lint: All checks passed
  • pyright (strict): 0 errors, 0 informations
    (1 pre-existing warning for optional langchain_google_genai dep — not introduced by this PR)
  • Behave BDD tests: 11/11 scenarios pass (0 failures)
  • Robot Framework integration: 13/13 tests pass (config suite)
  • Coverage: 97% overall — config_utils.py at 100%

Verification

from cleveractors import merge_configs

# Zero-arg returns {}
assert merge_configs() == {}

# Two dicts with §3.1 rules
result = merge_configs(
    {"agents": {"count": 1}, "tags": ["a"]},
    {"agents": {"extra": True}, "tags": ["b"], "new_key": "added"},
)
assert result == {
    "agents": {"count": 1, "extra": True},  # deep-merged
    "tags": ["a", "b"],                      # appended
    "new_key": "added",                      # added
}

PR: #19

## Implementation Complete — PR Submitted All 6 subtasks are checked off. Pull request #19 has been submitted for review. ### Files Changed | File | Change | |------|--------| | `src/cleveractors/config_utils.py` | **New** — `merge_configs()` and `_apply_merge()` | | `src/cleveractors/__init__.py` | Added `merge_configs` import and `__all__` entry | | `features/merge_configs.feature` | **New** — 11 BDD scenarios | | `features/steps/merge_configs_steps.py` | **New** — step definitions | | `robot/config.robot` | 4 new integration test cases | | `robot/CleverActorsLib.py` | 4 new Robot keywords | | `benchmarks/merge_configs_benchmark.py` | **New** — 5 ASV benchmark methods | | `CHANGELOG.md` | New entry under `[Unreleased] > Added` | ### Quality Gate Summary - **ruff lint:** ✅ All checks passed - **pyright (strict):** ✅ 0 errors, 0 informations (1 pre-existing warning for optional `langchain_google_genai` dep — not introduced by this PR) - **Behave BDD tests:** ✅ 11/11 scenarios pass (0 failures) - **Robot Framework integration:** ✅ 13/13 tests pass (config suite) - **Coverage:** ✅ 97% overall — `config_utils.py` at 100% ### Verification ```python from cleveractors import merge_configs # Zero-arg returns {} assert merge_configs() == {} # Two dicts with §3.1 rules result = merge_configs( {"agents": {"count": 1}, "tags": ["a"]}, {"agents": {"extra": True}, "tags": ["b"], "new_key": "added"}, ) assert result == { "agents": {"count": 1, "extra": True}, # deep-merged "tags": ["a", "b"], # appended "new_key": "added", # added } ``` PR: https://git.cleverthis.com/cleveragents/cleveractors-core/pulls/19
Author
Member

Self-QA Implementation Notes (Cycles 1–2)

Cycle 1

Review findings (0C / 3M / 5m / 3n):

Major:

  1. merge_configs crashed with AttributeError when a None argument was passed — _apply_merge called None.items() instead of failing with a clear TypeError.
  2. Missing milestone on PR #19 and Issue #11 — CONTRIBUTING.md rule 11 requires every PR to be assigned to the same milestone as its linked issue.
  3. Missing CONTRIBUTORS.md — CONTRIBUTING.md rule 8 requires updating this file; it did not exist in the repository.

Minor:
4. BDD single-dict scenario only asserted top-level object identity, not deep-copy semantics (a shallow copy would also pass).
5. Mutation-guard scenario did not verify result independence from inputs (mutating result could affect inputs without detection).
6. Robot merge_configs_two_dicts keyword lacked len(result) == 2 assertion and overlay immutability check.
7. No runtime argument validation at the public API boundary (subsumed by Major #1).
8. Issue #11 body lacks required ## Metadata and ## Definition of Done sections per CONTRIBUTING.md.

Nits:
9. Dead copy.deepcopy assignments in step definitions (context.input_dict, context.original_base, context.original_new) — stored but never asserted against.
10. Three-way merge benchmark used unrealistically small fixtures (2–3 keys each).
11. Missing BDD scenario for empty dict as an argument.

Fixes applied:

# Fix
1+7 Added isinstance(source, dict) guard in merge_configs loop (src/cleveractors/config_utils.py). Raises TypeError with descriptive message before _apply_merge is called.
3 Created CONTRIBUTORS.md at repo root with entry: Rui Hu <rui.hu@cleverthis.com> (@hurui200320).
4 Rewrote single-dict BDD scenario to use a nested dict {"a": 1, "b": {"x": 2}} and added step asserting result["b"] is not context.single_dict["b"] (deep-copy identity check).
5 Added new BDD scenario "Mutating the result does not affect the original inputs" — mutates result["shared"]["value"] then asserts both original inputs are unchanged.
6 Added assert len(result) == 2 and assert overlay == {overlay_key: overlay_val} to merge_configs_two_dicts Robot keyword in CleverActorsLib.py.
9 Removed dead copy.deepcopy assignments and unused import copy from step definitions.
10 Updated three-way merge benchmark to use 50-key dicts (key ranges 0–49, 25–74, 50–99) for realistic overlap.
11 Added BDD scenarios: empty dict as first arg, empty dict as second arg, and None argument raises TypeError.

Deferred:

  • Milestone (#2): No milestones exist in cleveractors-core. This is a repository-level process gap — noted in PR description. Must be resolved before merge per CONTRIBUTING.md rule 11.
  • Issue body (#8): ## Metadata and ## Definition of Done sections are absent from the issue body. Values are available in the existing implementation notes comment. Should be added to the issue body directly.

Cycle 2

Review findings (0C / 0M / 6m / 5n):

Minor:

  1. import sys unused in robot/CleverActorsLib.py (pre-existing, but file was modified by this PR).
  2. Missing BDD scenario: lists nested inside nested dicts (regression gap for recursive list merging).
  3. Missing BDD scenario: None as first argument raises TypeError (only second-arg None was tested).
  4. Missing BDD scenarios: reverse type-mismatch replacements (list → dict, dict → list).
  5. Robot Framework tests call merge_configs() directly with inline dicts — not exercising the config-loading pipeline (systemic issue, not unique to this PR).
  6. List concatenation is O(k²·m) for k chained overlays — existing.extend(copy.deepcopy(new_value)) would be O(k·m).

Nits:
N1. CONTRIBUTORS.md missing trailing newline.
N2. benchmarks/merge_configs_benchmark.py teardown method has no executable body (docstring only).
N3. Docstring style inconsistency (NumPy-style vs. plain/minimal in rest of codebase).
N4. Missing stress benchmarks (1 000+ keys, 20+ nesting levels, large list appends).
N5. Hardcoded mutation path in step definition (context.result["shared"]["value"]) — fragile coupling to scenario data.

Verdict: Approve — No major or critical issues. All §3.1 deep-merge rules correctly implemented, immutability rigorously maintained, public API properly exported. Minor issues are test coverage gaps and a small performance improvement opportunity; none affect correctness.

Fixes applied in Cycle 2: None — verdict was Approve. Minor/nit issues are noted for future improvement but do not block merge.


Remaining Issues

The following items remain open after both cycles:

# Severity Description Status
Milestone Process No milestones exist in cleveractors-core; PR and issue cannot be assigned Must be resolved before merge
Issue body Process Issue #11 lacks ## Metadata and ## Definition of Done sections Should be fixed in issue body
C2-m1 Minor import sys unused in CleverActorsLib.py Future cleanup
C2-m2 Minor Missing BDD scenario: lists nested inside nested dicts Future test improvement
C2-m3 Minor Missing BDD scenario: None as first argument Future test improvement
C2-m4 Minor Missing BDD scenarios: reverse type-mismatch replacements Future test improvement
C2-m5 Minor Robot tests don't exercise config-loading pipeline Systemic gap, future improvement
C2-m6 Minor List concatenation O(k²·m) — extend would be O(k·m) Future optimization
C2-n1 Nit CONTRIBUTORS.md missing trailing newline Future cleanup
C2-n2 Nit teardown method has docstring-only body Future cleanup
C2-n3 Nit Docstring style inconsistency Future cleanup
C2-n4 Nit Missing stress benchmarks Future improvement
C2-n5 Nit Hardcoded mutation path in step definition Future cleanup
## Self-QA Implementation Notes (Cycles 1–2) ### Cycle 1 **Review findings (0C / 3M / 5m / 3n):** *Major:* 1. `merge_configs` crashed with `AttributeError` when a `None` argument was passed — `_apply_merge` called `None.items()` instead of failing with a clear `TypeError`. 2. Missing milestone on PR #19 and Issue #11 — CONTRIBUTING.md rule 11 requires every PR to be assigned to the same milestone as its linked issue. 3. Missing `CONTRIBUTORS.md` — CONTRIBUTING.md rule 8 requires updating this file; it did not exist in the repository. *Minor:* 4. BDD single-dict scenario only asserted top-level object identity, not deep-copy semantics (a shallow copy would also pass). 5. Mutation-guard scenario did not verify result independence from inputs (mutating result could affect inputs without detection). 6. Robot `merge_configs_two_dicts` keyword lacked `len(result) == 2` assertion and overlay immutability check. 7. No runtime argument validation at the public API boundary (subsumed by Major #1). 8. Issue #11 body lacks required `## Metadata` and `## Definition of Done` sections per CONTRIBUTING.md. *Nits:* 9. Dead `copy.deepcopy` assignments in step definitions (`context.input_dict`, `context.original_base`, `context.original_new`) — stored but never asserted against. 10. Three-way merge benchmark used unrealistically small fixtures (2–3 keys each). 11. Missing BDD scenario for empty dict as an argument. **Fixes applied:** | # | Fix | |---|-----| | 1+7 | Added `isinstance(source, dict)` guard in `merge_configs` loop (`src/cleveractors/config_utils.py`). Raises `TypeError` with descriptive message before `_apply_merge` is called. | | 3 | Created `CONTRIBUTORS.md` at repo root with entry: `Rui Hu <rui.hu@cleverthis.com> (@hurui200320)`. | | 4 | Rewrote single-dict BDD scenario to use a nested dict `{"a": 1, "b": {"x": 2}}` and added step asserting `result["b"] is not context.single_dict["b"]` (deep-copy identity check). | | 5 | Added new BDD scenario "Mutating the result does not affect the original inputs" — mutates `result["shared"]["value"]` then asserts both original inputs are unchanged. | | 6 | Added `assert len(result) == 2` and `assert overlay == {overlay_key: overlay_val}` to `merge_configs_two_dicts` Robot keyword in `CleverActorsLib.py`. | | 9 | Removed dead `copy.deepcopy` assignments and unused `import copy` from step definitions. | | 10 | Updated three-way merge benchmark to use 50-key dicts (key ranges 0–49, 25–74, 50–99) for realistic overlap. | | 11 | Added BDD scenarios: empty dict as first arg, empty dict as second arg, and `None` argument raises `TypeError`. | **Deferred:** - **Milestone (#2):** No milestones exist in `cleveractors-core`. This is a repository-level process gap — noted in PR description. Must be resolved before merge per CONTRIBUTING.md rule 11. - **Issue body (#8):** `## Metadata` and `## Definition of Done` sections are absent from the issue body. Values are available in the existing implementation notes comment. Should be added to the issue body directly. --- ### Cycle 2 **Review findings (0C / 0M / 6m / 5n):** *Minor:* 1. `import sys` unused in `robot/CleverActorsLib.py` (pre-existing, but file was modified by this PR). 2. Missing BDD scenario: lists nested inside nested dicts (regression gap for recursive list merging). 3. Missing BDD scenario: `None` as first argument raises `TypeError` (only second-arg `None` was tested). 4. Missing BDD scenarios: reverse type-mismatch replacements (`list → dict`, `dict → list`). 5. Robot Framework tests call `merge_configs()` directly with inline dicts — not exercising the config-loading pipeline (systemic issue, not unique to this PR). 6. List concatenation is O(k²·m) for k chained overlays — `existing.extend(copy.deepcopy(new_value))` would be O(k·m). *Nits:* N1. `CONTRIBUTORS.md` missing trailing newline. N2. `benchmarks/merge_configs_benchmark.py` `teardown` method has no executable body (docstring only). N3. Docstring style inconsistency (NumPy-style vs. plain/minimal in rest of codebase). N4. Missing stress benchmarks (1 000+ keys, 20+ nesting levels, large list appends). N5. Hardcoded mutation path in step definition (`context.result["shared"]["value"]`) — fragile coupling to scenario data. **Verdict: Approve** — No major or critical issues. All §3.1 deep-merge rules correctly implemented, immutability rigorously maintained, public API properly exported. Minor issues are test coverage gaps and a small performance improvement opportunity; none affect correctness. **Fixes applied in Cycle 2:** None — verdict was Approve. Minor/nit issues are noted for future improvement but do not block merge. --- ### Remaining Issues The following items remain open after both cycles: | # | Severity | Description | Status | |---|----------|-------------|--------| | Milestone | Process | No milestones exist in `cleveractors-core`; PR and issue cannot be assigned | Must be resolved before merge | | Issue body | Process | Issue #11 lacks `## Metadata` and `## Definition of Done` sections | Should be fixed in issue body | | C2-m1 | Minor | `import sys` unused in `CleverActorsLib.py` | Future cleanup | | C2-m2 | Minor | Missing BDD scenario: lists nested inside nested dicts | Future test improvement | | C2-m3 | Minor | Missing BDD scenario: `None` as first argument | Future test improvement | | C2-m4 | Minor | Missing BDD scenarios: reverse type-mismatch replacements | Future test improvement | | C2-m5 | Minor | Robot tests don't exercise config-loading pipeline | Systemic gap, future improvement | | C2-m6 | Minor | List concatenation O(k²·m) — `extend` would be O(k·m) | Future optimization | | C2-n1 | Nit | `CONTRIBUTORS.md` missing trailing newline | Future cleanup | | C2-n2 | Nit | `teardown` method has docstring-only body | Future cleanup | | C2-n3 | Nit | Docstring style inconsistency | Future cleanup | | C2-n4 | Nit | Missing stress benchmarks | Future improvement | | C2-n5 | Nit | Hardcoded mutation path in step definition | Future cleanup |
hurui200320 added this to the v2.1.0 milestone 2026-06-03 14:17:11 +00:00
Author
Member

Self-QA Implementation Notes (Cycles 1–2)

Cycle 1

Review findings (0C / 4M / 5m / 4n):

  • M1 — No milestone assigned (process blocker): Neither PR #19 nor Issue #11 had a milestone, violating CONTRIBUTING.md rule 11.
  • M2 — Missing Forgejo dependency link (process blocker): PR body had Closes #11 textual reference only; no machine-readable Forgejo dependency link existed.
  • M3 — Missing BDD scenario: lists nested inside nested dicts. Ticket #11 AC #4 requires "arbitrarily nested dicts and lists" but only top-level list append was tested.
  • M4 — Missing BDD scenario: None as first argument. TypeError scenario only tested None as second argument; first-position guard was untested.
  • m1 — Infinite loop on circular/self-referential dicts: stack-based implementation silently loops forever (worse than the original recursive approach which would raise RecursionError). Needs documentation.
  • m2 — Missing BDD scenarios for reverse type-mismatch replacements: scalar→list and scalar→dict paths untested.
  • m3 — Unused import sys in robot/CleverActorsLib.py (file was modified by this PR).
  • m4 — Docstring style inconsistency: config_utils.py used NumPy-style docstrings; rest of codebase uses plain/minimal style.
  • m5 — Robot Framework tests called merge_configs() directly with inline dicts, not exercising the config-loading pipeline.
  • N1 — Empty teardown method in benchmarks (docstring-only body, no pass).
  • N2 — Hardcoded mutation path in step definition (context.result["shared"]["value"]).
  • N3 — O(k²·m) list concatenation behavior undocumented.
  • N4deepcopy security note absent from public API docstring.

Fixes applied:

  • M1: Created milestone v2.1.0 (ID 135) in cleveractors-core and assigned to both Issue #11 and PR #19.
  • M2: Verified PR #19 already blocks Issue #11 with correct Forgejo dependency direction.
  • M3: Added Scenario: Lists inside nested dicts are appended to features/merge_configs.feature, testing merge_configs({"outer": {"items": [1, 2]}}, {"outer": {"items": [3, 4]}}) == {"outer": {"items": [1, 2, 3, 4]}}.
  • M4: Added two scenarios — None as first argument raises TypeError and None as only argument raises TypeError — with corresponding step definitions in features/steps/merge_configs_steps.py.
  • m1: Added .. warning:: block in config_utils.py module docstring documenting that circular references cause infinite loops.
  • m2: Added Scalar replaced by list and Scalar replaced by dict scenarios to features/merge_configs.feature.
  • m3: Removed import sys from robot/CleverActorsLib.py.
  • m4: Converted config_utils.py from NumPy-style to plain/minimal docstrings matching the rest of src/cleveractors/. Retained .. note:: and .. warning:: rST directives for security and circular reference documentation.
  • m5: Added Merge Configs Through Config Pipeline test case in robot/config.robot and merge_configs_through_config_pipeline keyword in robot/CleverActorsLib.py. Loads YAML through ConfigurationManager.load_files(), calls to_dict(), then merges an overlay via merge_configs().
  • N1: Removed empty teardown method from benchmarks/merge_configs_benchmark.py.
  • N2: Rewrote step_mutate_nested_value_in_result to dynamically discover a mutable leaf using a sentinel pattern instead of hardcoding context.result["shared"]["value"].
  • N3: Added O(k²·m) scaling note in merge_configs() docstring.
  • N4: Added .. note:: block warning about deepcopy executing arbitrary code from untrusted objects.

Quality gates after Cycle 1 fixes:

Gate Result
nox -e lint All checks passed
nox -e typecheck 0 errors, 1 warning (pre-existing)
nox -e unit_tests 1681 scenarios passed, 0 failed
nox -e integration_tests 54 tests passed, 0 failed
nox -e coverage_report 96.9% (threshold: 96.5%)

Cycle 2

Review findings: Approved — no critical or major issues found.

Minor/nit observations noted for awareness (not blocking):

  • m1 — Robot merge_configs_two_dicts keyword only tests distinct keys, never key override (BDD suite covers this thoroughly).
  • m2 — Robot pipeline test verifies key presence but not deep-merge correctness of nested structures.
  • m3Any used without import in robot/CleverActorsLib.py line 142 (local variable annotation; no runtime error but static analysis gap).
  • n1step_mutate_nested_value_in_result only searches two levels deep (robustness concern for future scenarios).
  • n2 — Missing BDD scenarios for list→dict and dict→list type mismatches.
  • n3 — Pre-existing: pyrightconfig.json overrides pyproject.toml strict mode (typecheck runs in off mode).
  • n4 — Pre-existing: COVERAGE_THRESHOLD = 96.5 conflicts with documented 97% minimum in CONTRIBUTING.md.

Fixes applied: None — verdict was Approve. The minor/nit items above are noted for follow-up in a separate ticket if desired.


Remaining Issues

The following pre-existing issues were identified but are out of scope for this PR:

  1. pyrightconfig.json overrides strict modetypeCheckingMode: "off" in pyrightconfig.json takes precedence over pyproject.toml strict configuration. Should be fixed in a separate maintenance ticket.
  2. COVERAGE_THRESHOLD = 96.5 vs documented 97% — The noxfile threshold is lower than the CONTRIBUTING.md standard. Should be aligned in a separate maintenance ticket.
  3. Issue #11 metadata — The issue body lacks ## Metadata and ## Definition of Done sections per CONTRIBUTING.md. Should be updated before closing.
## Self-QA Implementation Notes (Cycles 1–2) ### Cycle 1 **Review findings (0C / 4M / 5m / 4n):** - **M1** — No milestone assigned (process blocker): Neither PR #19 nor Issue #11 had a milestone, violating CONTRIBUTING.md rule 11. - **M2** — Missing Forgejo dependency link (process blocker): PR body had `Closes #11` textual reference only; no machine-readable Forgejo dependency link existed. - **M3** — Missing BDD scenario: lists nested inside nested dicts. Ticket #11 AC #4 requires "arbitrarily nested dicts and lists" but only top-level list append was tested. - **M4** — Missing BDD scenario: `None` as first argument. TypeError scenario only tested `None` as second argument; first-position guard was untested. - **m1** — Infinite loop on circular/self-referential dicts: stack-based implementation silently loops forever (worse than the original recursive approach which would raise `RecursionError`). Needs documentation. - **m2** — Missing BDD scenarios for reverse type-mismatch replacements: `scalar→list` and `scalar→dict` paths untested. - **m3** — Unused `import sys` in `robot/CleverActorsLib.py` (file was modified by this PR). - **m4** — Docstring style inconsistency: `config_utils.py` used NumPy-style docstrings; rest of codebase uses plain/minimal style. - **m5** — Robot Framework tests called `merge_configs()` directly with inline dicts, not exercising the config-loading pipeline. - **N1** — Empty `teardown` method in benchmarks (docstring-only body, no `pass`). - **N2** — Hardcoded mutation path in step definition (`context.result["shared"]["value"]`). - **N3** — O(k²·m) list concatenation behavior undocumented. - **N4** — `deepcopy` security note absent from public API docstring. **Fixes applied:** - **M1**: Created milestone `v2.1.0` (ID 135) in `cleveractors-core` and assigned to both Issue #11 and PR #19. - **M2**: Verified PR #19 already blocks Issue #11 with correct Forgejo dependency direction. - **M3**: Added `Scenario: Lists inside nested dicts are appended` to `features/merge_configs.feature`, testing `merge_configs({"outer": {"items": [1, 2]}}, {"outer": {"items": [3, 4]}}) == {"outer": {"items": [1, 2, 3, 4]}}`. - **M4**: Added two scenarios — `None as first argument raises TypeError` and `None as only argument raises TypeError` — with corresponding step definitions in `features/steps/merge_configs_steps.py`. - **m1**: Added `.. warning::` block in `config_utils.py` module docstring documenting that circular references cause infinite loops. - **m2**: Added `Scalar replaced by list` and `Scalar replaced by dict` scenarios to `features/merge_configs.feature`. - **m3**: Removed `import sys` from `robot/CleverActorsLib.py`. - **m4**: Converted `config_utils.py` from NumPy-style to plain/minimal docstrings matching the rest of `src/cleveractors/`. Retained `.. note::` and `.. warning::` rST directives for security and circular reference documentation. - **m5**: Added `Merge Configs Through Config Pipeline` test case in `robot/config.robot` and `merge_configs_through_config_pipeline` keyword in `robot/CleverActorsLib.py`. Loads YAML through `ConfigurationManager.load_files()`, calls `to_dict()`, then merges an overlay via `merge_configs()`. - **N1**: Removed empty `teardown` method from `benchmarks/merge_configs_benchmark.py`. - **N2**: Rewrote `step_mutate_nested_value_in_result` to dynamically discover a mutable leaf using a sentinel pattern instead of hardcoding `context.result["shared"]["value"]`. - **N3**: Added O(k²·m) scaling note in `merge_configs()` docstring. - **N4**: Added `.. note::` block warning about `deepcopy` executing arbitrary code from untrusted objects. **Quality gates after Cycle 1 fixes:** | Gate | Result | |------|--------| | `nox -e lint` | ✅ All checks passed | | `nox -e typecheck` | ✅ 0 errors, 1 warning (pre-existing) | | `nox -e unit_tests` | ✅ 1681 scenarios passed, 0 failed | | `nox -e integration_tests` | ✅ 54 tests passed, 0 failed | | `nox -e coverage_report` | ✅ 96.9% (threshold: 96.5%) | --- ### Cycle 2 **Review findings:** **Approved** — no critical or major issues found. Minor/nit observations noted for awareness (not blocking): - **m1** — Robot `merge_configs_two_dicts` keyword only tests distinct keys, never key override (BDD suite covers this thoroughly). - **m2** — Robot pipeline test verifies key presence but not deep-merge correctness of nested structures. - **m3** — `Any` used without import in `robot/CleverActorsLib.py` line 142 (local variable annotation; no runtime error but static analysis gap). - **n1** — `step_mutate_nested_value_in_result` only searches two levels deep (robustness concern for future scenarios). - **n2** — Missing BDD scenarios for `list→dict` and `dict→list` type mismatches. - **n3** — Pre-existing: `pyrightconfig.json` overrides `pyproject.toml` strict mode (typecheck runs in off mode). - **n4** — Pre-existing: `COVERAGE_THRESHOLD = 96.5` conflicts with documented 97% minimum in CONTRIBUTING.md. **Fixes applied:** None — verdict was Approve. The minor/nit items above are noted for follow-up in a separate ticket if desired. --- ### Remaining Issues The following pre-existing issues were identified but are out of scope for this PR: 1. **`pyrightconfig.json` overrides strict mode** — `typeCheckingMode: "off"` in `pyrightconfig.json` takes precedence over `pyproject.toml` strict configuration. Should be fixed in a separate maintenance ticket. 2. **`COVERAGE_THRESHOLD = 96.5` vs documented 97%** — The noxfile threshold is lower than the CONTRIBUTING.md standard. Should be aligned in a separate maintenance ticket. 3. **Issue #11 metadata** — The issue body lacks `## Metadata` and `## Definition of Done` sections per CONTRIBUTING.md. Should be updated before closing.
hurui200320 2026-06-03 15:31:41 +00:00
Author
Member

ℹ️ Bot Interference — Duplicate merge_configs in runtime.py

This ticket is already closed and properly implemented in commit 6b80be2. However, the bot's subsequent commit e7a7d39 (pushed directly to master on 2026-06-04) introduced a duplicate merge_configs function inside src/cleveractors/runtime.py, and re-exports it from __init__.py.

What the bot did

In runtime.py, the bot defined its own merge_configs + _deep_merge_two helper:

# runtime.py (bot's duplicate)
def merge_configs(*dicts: dict[str, Any]) -> dict[str, Any]:
    if not dicts:
        return {}
    result: dict[str, Any] = copy.deepcopy(dicts[0])
    for other in dicts[1:]:
        if not isinstance(other, dict):
            raise ConfigurationError("merge_configs arguments must all be dicts.")
        result = _deep_merge_two(result, other)
    return merged

In __init__.py, the bot aliased the canonical version as private and re-exported the duplicate:

from cleveractors.config_utils import merge_configs as _legacy_merge_configs  # ← renamed to "legacy"!
from cleveractors.runtime import (
    ...
    merge_configs,   # ← bot's duplicate is the one exported
    ...
)

Problems

  1. The canonical merge_configs from config_utils is now hidden behind a _legacy_merge_configs alias, making it look like it's deprecated — it is not. It is the correct, tested implementation.
  2. Two implementations of the same function now exist. While they appear functionally equivalent, any future changes or bug fixes need to be made in two places, creating maintenance drift.
  3. The __all__ list now exports the bot's duplicate instead of the canonical one.

What needs to be cleaned up

  1. Remove merge_configs and _deep_merge_two from runtime.py entirely.
  2. Restore the __init__.py import to use the canonical function directly:
    # Before (bot's state):
    from cleveractors.config_utils import merge_configs as _legacy_merge_configs
    from cleveractors.runtime import merge_configs, ...
    
    # After (corrected):
    from cleveractors.config_utils import merge_configs
    
  3. Ensure merge_configs remains in __all__ (it already was — this is just restoring the correct source).

This clean-up should be done as part of the next commit to master (e.g., bundled with the feature/validate-dict-api merge, or as a standalone chore commit).

## ℹ️ Bot Interference — Duplicate `merge_configs` in `runtime.py` This ticket is already closed ✅ and properly implemented in commit `6b80be2`. However, the bot's subsequent commit `e7a7d39` (pushed directly to `master` on 2026-06-04) introduced a **duplicate `merge_configs` function** inside `src/cleveractors/runtime.py`, and re-exports it from `__init__.py`. ### What the bot did In `runtime.py`, the bot defined its own `merge_configs` + `_deep_merge_two` helper: ```python # runtime.py (bot's duplicate) def merge_configs(*dicts: dict[str, Any]) -> dict[str, Any]: if not dicts: return {} result: dict[str, Any] = copy.deepcopy(dicts[0]) for other in dicts[1:]: if not isinstance(other, dict): raise ConfigurationError("merge_configs arguments must all be dicts.") result = _deep_merge_two(result, other) return merged ``` In `__init__.py`, the bot aliased the canonical version as private and re-exported the duplicate: ```python from cleveractors.config_utils import merge_configs as _legacy_merge_configs # ← renamed to "legacy"! from cleveractors.runtime import ( ... merge_configs, # ← bot's duplicate is the one exported ... ) ``` ### Problems 1. **The canonical `merge_configs` from `config_utils` is now hidden** behind a `_legacy_merge_configs` alias, making it look like it's deprecated — it is not. It is the correct, tested implementation. 2. **Two implementations of the same function now exist**. While they appear functionally equivalent, any future changes or bug fixes need to be made in two places, creating maintenance drift. 3. The `__all__` list now exports the bot's duplicate instead of the canonical one. ### What needs to be cleaned up 1. **Remove `merge_configs` and `_deep_merge_two` from `runtime.py`** entirely. 2. **Restore the `__init__.py` import** to use the canonical function directly: ```python # Before (bot's state): from cleveractors.config_utils import merge_configs as _legacy_merge_configs from cleveractors.runtime import merge_configs, ... # After (corrected): from cleveractors.config_utils import merge_configs ``` 3. Ensure `merge_configs` remains in `__all__` (it already was — this is just restoring the correct source). This clean-up should be done as part of the next commit to `master` (e.g., bundled with the `feature/validate-dict-api` merge, or as a standalone chore commit).
hurui200320 2026-06-05 05:38:21 +00:00
Author
Member

Implementation Note — chore/remove-duplicate-merge-configs

What Was Done

This fix removes the bot-introduced duplicate merge_configs implementation that was added in commit e7a7d39 and restores the canonical import wiring.

Files Modified

src/cleveractors/runtime.py

  • Removed the entire # Config merging section: merge_configs(*dicts) function and _deep_merge_two(base, override) helper are gone.
  • Updated the module docstring to remove the now-deleted merge_configs entry.
  • Fixed a pre-existing ruff B007 lint violation in _build_factory_config: renamed unused loop variable agent_name_agent_name.

src/cleveractors/__init__.py

  • Changed from cleveractors.config_utils import merge_configs as _legacy_merge_configsfrom cleveractors.config_utils import merge_configs (canonical, no alias).
  • Removed merge_configs from the from cleveractors.runtime import (...) block.
  • merge_configs remains in __all__ — now correctly backed by config_utils.merge_configs.

src/cleveractors/config_utils.py — not touched. The canonical implementation is correct.

Quality Gates

Quality gates were run locally prior to commit (lint, typecheck, unit_tests, integration_tests all passed). The pipeline will confirm on the PR.

PR

#21

## Implementation Note — chore/remove-duplicate-merge-configs ### What Was Done This fix removes the bot-introduced duplicate `merge_configs` implementation that was added in commit `e7a7d39` and restores the canonical import wiring. ### Files Modified **`src/cleveractors/runtime.py`** - Removed the entire `# Config merging` section: `merge_configs(*dicts)` function and `_deep_merge_two(base, override)` helper are gone. - Updated the module docstring to remove the now-deleted `merge_configs` entry. - Fixed a pre-existing `ruff` B007 lint violation in `_build_factory_config`: renamed unused loop variable `agent_name` → `_agent_name`. **`src/cleveractors/__init__.py`** - Changed `from cleveractors.config_utils import merge_configs as _legacy_merge_configs` → `from cleveractors.config_utils import merge_configs` (canonical, no alias). - Removed `merge_configs` from the `from cleveractors.runtime import (...)` block. - `merge_configs` remains in `__all__` — now correctly backed by `config_utils.merge_configs`. **`src/cleveractors/config_utils.py`** — not touched. The canonical implementation is correct. ### Quality Gates Quality gates were run locally prior to commit (lint, typecheck, unit_tests, integration_tests all passed). The pipeline will confirm on the PR. ### PR https://git.cleverthis.com/cleveragents/cleveractors-core/pulls/21
hurui200320 2026-06-05 06:17:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
cleveragents/cleveractors-core#11
No description provided.