feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm #11
Labels
No labels
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
1 participant
Notifications
Due date
No due date set.
Blocks
Depends on
#17 feat(public-api): expose all router-facing APIs at cleveractors package level; update README
cleveragents/cleveractors-core
You do not have permission to read 1 dependency
#19 feat(merge_configs): expose public merge_configs(*dicts) API per §3.1 deep-merge algorithm
cleveragents/cleveractors-core
#21 chore(merge_configs): remove bot-introduced duplicate from runtime.py and restore canonical import
cleveragents/cleveractors-core
Reference
cleveragents/cleveractors-core#11
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Background
Only an internal
_merge_configs(base, new)instance method exists onReactiveConfigParser. It is not publicly importable and mutates its input. The CleverThis router needs a publicmerge_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
merge_configsfunction exists._merge_configsmutatesbasein place.Acceptance Criteria
Implement
merge_configs(*dicts: dict[str, Any]) -> dict[str, Any]and export it fromcleveractors/__init__.py:{}.cleveractors/__init__.pyand listed in__all__.Subtasks
merge_configs(*dicts)as a module-level function{}cleveractors/__init__.pyand__all__Definition of Done
from cleveractors import merge_configsworks without error.Implementation Notes & Inferred Metadata
Inferred Metadata (missing from issue body)
The issue body lacks a
## Metadatasection as required by CONTRIBUTING.md. The following values have been inferred and are recorded here for traceability: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)
feature/merge-configs-apiReference
Spec Reference — §3.1 Deep-Merge Algorithm
Sourced from
docs/actor-standard.mdincleveragents/cleveragents-webapp@develop:Design Decisions
Module placement:
merge_configs()will be implemented as a module-level function in a new filesrc/cleveractors/config_utils.py. This follows the Single Responsibility Principle and keepsreactive/config_parser.pyfocused on parsing.Immutability: Unlike the existing
_merge_configsinstance method (which mutatesbasein place), the publicmerge_configs()function will usecopy.deepcopy()to build a new accumulator at each step, ensuring input dicts are never modified.Variadic design: Instead of
merge_configs(base, new), the public API accepts*dictsto allow merging an arbitrary number of configs in a single call (including zero args → returns{}).Existing
_merge_configsunchanged: The internal method onReactiveConfigParserwill remain as-is to avoid breaking the existing parse path. The new public function is independent.Export:
merge_configswill be added tocleveractors/__init__.pyand__all__so consumers can dofrom cleveractors import merge_configs.Test Plan (BDD scenarios)
Feature file:
features/merge_configs.featureSteps file:
features/steps/merge_configs_steps.pyScenarios:
{}Implementation locations
src/cleveractors/config_utils.py— new module withmerge_configs()functionsrc/cleveractors/__init__.py— add import and__all__entryfeatures/merge_configs.feature— BDD scenariosfeatures/steps/merge_configs_steps.py— step definitionsCHANGELOG.md— new entry for this featureImplementation Complete — PR Submitted
All 6 subtasks are checked off. Pull request #19 has been submitted for review.
Files Changed
src/cleveractors/config_utils.pymerge_configs()and_apply_merge()src/cleveractors/__init__.pymerge_configsimport and__all__entryfeatures/merge_configs.featurefeatures/steps/merge_configs_steps.pyrobot/config.robotrobot/CleverActorsLib.pybenchmarks/merge_configs_benchmark.pyCHANGELOG.md[Unreleased] > AddedQuality Gate Summary
(1 pre-existing warning for optional
langchain_google_genaidep — not introduced by this PR)config_utils.pyat 100%Verification
PR: #19
Self-QA Implementation Notes (Cycles 1–2)
Cycle 1
Review findings (0C / 3M / 5m / 3n):
Major:
merge_configscrashed withAttributeErrorwhen aNoneargument was passed —_apply_mergecalledNone.items()instead of failing with a clearTypeError.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_dictskeyword lackedlen(result) == 2assertion and overlay immutability check.7. No runtime argument validation at the public API boundary (subsumed by Major #1).
8. Issue #11 body lacks required
## Metadataand## Definition of Donesections per CONTRIBUTING.md.Nits:
9. Dead
copy.deepcopyassignments 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:
isinstance(source, dict)guard inmerge_configsloop (src/cleveractors/config_utils.py). RaisesTypeErrorwith descriptive message before_apply_mergeis called.CONTRIBUTORS.mdat repo root with entry:Rui Hu <rui.hu@cleverthis.com> (@hurui200320).{"a": 1, "b": {"x": 2}}and added step assertingresult["b"] is not context.single_dict["b"](deep-copy identity check).result["shared"]["value"]then asserts both original inputs are unchanged.assert len(result) == 2andassert overlay == {overlay_key: overlay_val}tomerge_configs_two_dictsRobot keyword inCleverActorsLib.py.copy.deepcopyassignments and unusedimport copyfrom step definitions.Noneargument raisesTypeError.Deferred:
cleveractors-core. This is a repository-level process gap — noted in PR description. Must be resolved before merge per CONTRIBUTING.md rule 11.## Metadataand## Definition of Donesections 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:
import sysunused inrobot/CleverActorsLib.py(pre-existing, but file was modified by this PR).Noneas first argument raisesTypeError(only second-argNonewas tested).list → dict,dict → list).merge_configs()directly with inline dicts — not exercising the config-loading pipeline (systemic issue, not unique to this PR).existing.extend(copy.deepcopy(new_value))would be O(k·m).Nits:
N1.
CONTRIBUTORS.mdmissing trailing newline.N2.
benchmarks/merge_configs_benchmark.pyteardownmethod 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:
cleveractors-core; PR and issue cannot be assigned## Metadataand## Definition of Donesectionsimport sysunused inCleverActorsLib.pyNoneas first argumentextendwould be O(k·m)CONTRIBUTORS.mdmissing trailing newlineteardownmethod has docstring-only bodySelf-QA Implementation Notes (Cycles 1–2)
Cycle 1
Review findings (0C / 4M / 5m / 4n):
Closes #11textual reference only; no machine-readable Forgejo dependency link existed.Noneas first argument. TypeError scenario only testedNoneas second argument; first-position guard was untested.RecursionError). Needs documentation.scalar→listandscalar→dictpaths untested.import sysinrobot/CleverActorsLib.py(file was modified by this PR).config_utils.pyused NumPy-style docstrings; rest of codebase uses plain/minimal style.merge_configs()directly with inline dicts, not exercising the config-loading pipeline.teardownmethod in benchmarks (docstring-only body, nopass).context.result["shared"]["value"]).deepcopysecurity note absent from public API docstring.Fixes applied:
v2.1.0(ID 135) incleveractors-coreand assigned to both Issue #11 and PR #19.Scenario: Lists inside nested dicts are appendedtofeatures/merge_configs.feature, testingmerge_configs({"outer": {"items": [1, 2]}}, {"outer": {"items": [3, 4]}}) == {"outer": {"items": [1, 2, 3, 4]}}.None as first argument raises TypeErrorandNone as only argument raises TypeError— with corresponding step definitions infeatures/steps/merge_configs_steps.py... warning::block inconfig_utils.pymodule docstring documenting that circular references cause infinite loops.Scalar replaced by listandScalar replaced by dictscenarios tofeatures/merge_configs.feature.import sysfromrobot/CleverActorsLib.py.config_utils.pyfrom NumPy-style to plain/minimal docstrings matching the rest ofsrc/cleveractors/. Retained.. note::and.. warning::rST directives for security and circular reference documentation.Merge Configs Through Config Pipelinetest case inrobot/config.robotandmerge_configs_through_config_pipelinekeyword inrobot/CleverActorsLib.py. Loads YAML throughConfigurationManager.load_files(), callsto_dict(), then merges an overlay viamerge_configs().teardownmethod frombenchmarks/merge_configs_benchmark.py.step_mutate_nested_value_in_resultto dynamically discover a mutable leaf using a sentinel pattern instead of hardcodingcontext.result["shared"]["value"].merge_configs()docstring... note::block warning aboutdeepcopyexecuting arbitrary code from untrusted objects.Quality gates after Cycle 1 fixes:
nox -e lintnox -e typechecknox -e unit_testsnox -e integration_testsnox -e coverage_reportCycle 2
Review findings: Approved — no critical or major issues found.
Minor/nit observations noted for awareness (not blocking):
merge_configs_two_dictskeyword only tests distinct keys, never key override (BDD suite covers this thoroughly).Anyused without import inrobot/CleverActorsLib.pyline 142 (local variable annotation; no runtime error but static analysis gap).step_mutate_nested_value_in_resultonly searches two levels deep (robustness concern for future scenarios).list→dictanddict→listtype mismatches.pyrightconfig.jsonoverridespyproject.tomlstrict mode (typecheck runs in off mode).COVERAGE_THRESHOLD = 96.5conflicts 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:
pyrightconfig.jsonoverrides strict mode —typeCheckingMode: "off"inpyrightconfig.jsontakes precedence overpyproject.tomlstrict configuration. Should be fixed in a separate maintenance ticket.COVERAGE_THRESHOLD = 96.5vs documented 97% — The noxfile threshold is lower than the CONTRIBUTING.md standard. Should be aligned in a separate maintenance ticket.## Metadataand## Definition of Donesections per CONTRIBUTING.md. Should be updated before closing.ℹ️ Bot Interference — Duplicate
merge_configsinruntime.pyThis ticket is already closed ✅ and properly implemented in commit
6b80be2. However, the bot's subsequent commite7a7d39(pushed directly tomasteron 2026-06-04) introduced a duplicatemerge_configsfunction insidesrc/cleveractors/runtime.py, and re-exports it from__init__.py.What the bot did
In
runtime.py, the bot defined its ownmerge_configs+_deep_merge_twohelper:In
__init__.py, the bot aliased the canonical version as private and re-exported the duplicate:Problems
merge_configsfromconfig_utilsis now hidden behind a_legacy_merge_configsalias, making it look like it's deprecated — it is not. It is the correct, tested implementation.__all__list now exports the bot's duplicate instead of the canonical one.What needs to be cleaned up
merge_configsand_deep_merge_twofromruntime.pyentirely.__init__.pyimport to use the canonical function directly:merge_configsremains 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 thefeature/validate-dict-apimerge, or as a standalone chore commit).Implementation Note — chore/remove-duplicate-merge-configs
What Was Done
This fix removes the bot-introduced duplicate
merge_configsimplementation that was added in commite7a7d39and restores the canonical import wiring.Files Modified
src/cleveractors/runtime.py# Config mergingsection:merge_configs(*dicts)function and_deep_merge_two(base, override)helper are gone.merge_configsentry.ruffB007 lint violation in_build_factory_config: renamed unused loop variableagent_name→_agent_name.src/cleveractors/__init__.pyfrom cleveractors.config_utils import merge_configs as _legacy_merge_configs→from cleveractors.config_utils import merge_configs(canonical, no alias).merge_configsfrom thefrom cleveractors.runtime import (...)block.merge_configsremains in__all__— now correctly backed byconfig_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