feat(validate_dict): expose public validate_dict(config_dict, platform_limits) API #10
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 milestone
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
#18 feat(validate_dict): expose public validate_dict(config_dict, platform_limits) API
cleveragents/cleveractors-core
Reference
cleveragents/cleveractors-core#10
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
cleveractors-corehas no public validation function. The internalSchemaValidator.validate()only checks for the presence of top-level keys (agents,routes,default_router). It does not validate agent types, provider values, route structure, or enforce platform-level structural limits.The CleverThis router must validate actor YAML at upload time — before storing it in the database — using a self-contained library call. The function must be callable without reading files, touching environment variables, or constructing
ReactiveCleverAgentsApp.Spec references: ADR-2024 (Router-Facing API), ADR-2025 (Format Validation), ADR-2029 (Upload-time Validation)
What Is Currently Missing
validate_dictfunction exists anywhere in the package.SchemaValidator.validate()only checks for top-level key presence — no structural or semantic validation.platform_limits["allowed_providers"]).max_graph_depth,max_subgraph_depth,max_total_nodes).Acceptance Criteria
Implement
validate_dict(config_dict: dict[str, Any], platform_limits: dict[str, Any]) -> dict[str, Any]and export it fromcleveractors/__init__.py:ConfigurationErrorifagentsorrouteskeys are absent fromconfig_dict.llm,tool,composite,chain,template_instance; unknown types raiseConfigurationError.providervalues: ifplatform_limits["allowed_providers"]is present, the declared provider must be in that list; if absent, defaults to{"openai", "anthropic", "google"}. Unknown providers raiseConfigurationError.source/target; node and operator types must be within the spec-defined vocabulary.platform_limits:max_graph_depth,max_subgraph_depth,max_total_nodes.cleveractors/__init__.pyand listed in__all__.Subtasks
validate_dict(config_dict, platform_limits)incleveractors/validation.py(or equivalent module)providervalidation usingplatform_limits["allowed_providers"](default{openai, anthropic, google}if key absent)source/targetfields, node types, operator types)platform_limitscleveractors/__init__.pyand__all__platform_limitsvariantsDefinition of Done
from cleveractors import validate_dictworks without error.Implementation Notes — Pre-Implementation Analysis
Branch and Commit Message
Since the issue body has no Metadata section, I derived these from context:
feature/validate-dict-apifeat(validate_dict): expose public validate_dict(config_dict, platform_limits) API(from issue title)Reference
Architecture Context (from ADR-2024, ADR-2025, ADR-2029)
The
validate_dict()function is the first of four router-facing APIs established by ADR-2024. The router calls it at actor-upload time to validate the spec-conformant Actor Configuration Standard format before storing the dict as JSON.ADR-2025 confirms that only the spec-conformant format is accepted (not the legacy flat format). Key field differences to enforce:
source/target(notfrom/to)entry_point:scalar (notentry_node:)id:fields)agentsandroutestop-level sections are requiredADR-2029 defines the structural limits passed via
platform_limits:max_graph_depthmax_subgraph_depthmax_total_nodesThe library's own internal ceiling for each is very large (implementation-defined). The platform constants passed by the router are the binding limits.
Spec-Conformant Vocabulary (from actor-standard.md v1.0.0)
Agent types (§4.3):
llm,tool,composite,chain,template_instanceDefault allowed providers (§4.4.1):
openai,anthropic,googleRoute types (§5.1):
stream,graph,bridgeNode types (§6.2) — case-insensitive:
start,end,agent,function,tool,conditional,subgraph,message_routerGraph edge fields:
source,target— edges usingfrom/tomust be rejected.Operator types (§5.3.2):
map,filter,transform,debounce,throttle,delay,buffer,scan,reduce,switch,conditional_route,catch,retry,distinct,take,skip,sample,graph_execute,state_update,state_checkpoint,graph_nodeImplementation Design
Module:
cleveractors/validation.py(new file)Function signature:
Validation order:
agents,routes)platform_limits["allowed_providers"](default{"openai", "anthropic", "google"})source/targetfield names, node types, operator typesplatform_limits(max_graph_depth,max_subgraph_depth,max_total_nodes)config_dictunchangedGraph depth calculation: DFS from
entry_point, tracking visited nodes to avoid cycles, finding the longest path toendnode.No side effects: The function does NOT read files, env vars, or instantiate any app objects. It is a pure static validator.
Testing Plan
BDD (Behave) scenarios in
features/validate_dict.feature:agentskey →ConfigurationErrorrouteskey →ConfigurationErrorConfigurationErrorConfigurationErroropenai/anthropic/google)allowed_providersin platform_limits →ConfigurationErrorsource/target→ validfrom/to→ConfigurationErrorConfigurationErrorConfigurationErrormax_graph_depth→ConfigurationErrormax_total_nodes→ConfigurationErrormax_subgraph_depth→ConfigurationErrorRobot Framework integration tests in
robot/— verify the public API is importable and callable.Implementation Results
What was implemented
cleveractors/validation.py(new module, 100% covered):validate_dict(config_dict, platform_limits)— the public entry point_validate_top_level_keys()— checks for requiredagentsandrouteskeys_validate_agents()— validates each agent's type; delegates to_validate_llm_provider()forllmagents_validate_llm_provider()— checks provider against allowlist (platform-configurable or default{openai, anthropic, google})_validate_routes()— validates route types; delegates to_validate_graph_route()and_validate_stream_route()_validate_graph_route()— rejects legacyfrom/toedge fields; validates node types_validate_stream_route()— validates operator types against the 20 spec-defined operator names_validate_structural_limits()— enforcesmax_graph_depth,max_subgraph_depth,max_total_nodes_compute_graph_depth()— DFS longest-path traversal with cycle guard (frozenset visited set)_compute_subgraph_depth()— recursive subgraph-reference depth with cycle guardcleveractors/__init__.py(updated):from cleveractors.validation import validate_dict"validate_dict"to__all__robot/CleverActorsLib.py(updated):validate_dict_succeeds,validate_dict_raises_configuration_error,validate_dict_returns_same_object,validate_dict_is_exportedTest results
features/validate_dict.feature)robot/validate_dict.robot)Quality gates
nox -s lintnox -s typechecknox -s unit_testsnox -s integration_testsnox -s coverage_reportvalidation.pycoverageDesign decisions
No side effects:
validate_dictis a pure static validator. It does not read files, touch env vars, or construct any app object. The function can be imported and called from the router in isolation.Identity return contract: The function returns
config_dictitself (the same object, not a copy) when valid. This matches the ADR-2024 contract and allows the router to chain calls without extra dict copies.Removed dead defensive guards: Two guards (
if not isinstance(route_def, dict): continuein_validate_structural_limitsand_count_total_nodes) were removed because_validate_routes()already ensures all route values are dicts before structural limits are checked. Removing them improves code clarity and coverage.Provider comparison is case-insensitive:
provider.lower()is compared against lowercase allowlist entries, following the spec's §4.4.1 note that "provider name comparison is case-insensitive."LLM agent with
agent_template/templatekey: Agents using template references may omit thetypefield. These are silently skipped for type validation (treated astemplate_instanceimplicitly), consistent with §4.8.Cyclic graph handling:
_compute_graph_depthuses afrozensetvisited set during DFS to avoid infinite recursion on cyclic edge graphs. Cycles are broken by returning 0 when a node is revisited.Branch and commit
feature/validate-dict-apib9f61ba—feat(validate_dict): expose public validate_dict(config_dict, platform_limits) APISelf-QA Implementation Notes (Cycles 1–5)
Cycle 1
Review findings (0C/5M/4m/5n):
type→AttributeError; non-stringprovider→AttributeError; bare-stringallowed_providers→ silently wrong allowlist; non-numericplatform_limitsvalues →TypeError; DFS overcounts depth for cyclic graphs (off-by-one)agent_templatekey test; missingbridgeroute test; missing case-insensitive comparison tests; missing boundary-exact structural limit tests; missing Robotmax_subgraph_depthtestentry_pointsilently returns depth 0;bridgeroutes receive no content validation; inconsistent operator type handlingimport copyplacement, docstring line number reference, dead code, PR description countFixes applied:
isinstance(node_type, str)guard in_validate_graph_routeisinstance(provider, str)guard in_validate_llm_providerallowed_providers_validate_limit_typehelper rejecting non-int limit values"unknown agent type","unknown node type","unknown operator type","max_total_nodes")agent_template,bridgeroute, case-insensitive comparisons, boundary-exact limitsmax_subgraph_depthtestsource/targetpresence checks; addednodes/edgespresence checks; addedentry_pointexistence check; added comment for bridge routes; changed operator type to fail-fastCycle 2
Review findings (0C/9M/4m/3n):
agent_typeas list/dict →TypeError: unhashable type;route_typeas list/dict →TypeError;entry_pointas list →TypeErrorduring structural limit enforcement;allowed_providersas dict → silently iterates over keysallowed_providers; loose assertions for depth, subgraph depth, entry_point; boundary-exactmax_subgraph_depthtest used wrong limit value;# type: ignoresuppressions violating CONTRIBUTING.mdstep_error_mentions_providertoo looseFixes applied:
isinstance(agent_type, str)guard in_validate_agentsisinstance(route_type, str)guard in_validate_routesisinstance(entry_point, str)guard in_compute_graph_depthallowed_providersguard with explicitisinstance(raw_allowed, (list, tuple, set, frozenset))check"max_graph_depth","max_subgraph_depth","entry_point"in messagesmax_subgraph_depthtest to usemax_subgraph_depth=3(not 4)# type: ignoresuppressions; replaced baredictannotations withdict[str, Any]; configured pyright to not check behave imports_MAX_DEPTH_GUARD = 1000with depth_guard parameter to both DFS functionsCycle 3
Review findings (3C/3M/7m/5n):
_MAX_DEPTH_GUARD=1000equals Python's recursion limit →RecursionErrorat ~498 edges;subgraphfield as list →TypeErrorin_compute_subgraph_depth; edgesourceas list →TypeErrorin_compute_graph_depthpretty.outputartifact committed (672 KB);entry_pointnot validated in_validate_graph_route; emptysubgraphreference silently accepted: AnyannotationsFixes applied:
_MAX_DEPTH_GUARDto 500isinstance(sub_ref, str)guard in_compute_subgraph_depthbeforeroutes.get(sub_ref)isinstance(source, str)andisinstance(target, str)type checks in_validate_graph_routepretty.outputviagit rm; added to.gitignoreentry_pointvalidation in_validate_graph_route(non-empty string, must match declared node)_validate_graph_routenodes/edgesto raiseConfigurationErrorstep_error_mentions_entry_pointto require both "entry_point" and "does not match any declared node": AnyannotationsCycle 4
Review findings (0C/2M/7m/7n):
_MAX_DEPTH_GUARD=500still unreachable (Python recursion limit fires first at ~498 edges); empty-stringsource/target/entry_pointpass validation silently_count_total_nodes; subgraph references to non-graph routes silently return depth 0; unreachable depth-guard branches; missing zero-value boundary tests; missing empty-collection tests; unusedimport sys;_compute_graph_depthdefaultsentry_pointto"start"route_def.get("nodes")lookups; negative platform limits not rejected;noqa: BLE001commentsFixes applied:
_compute_graph_depthand_compute_subgraph_depthto iterative DFS using explicit stacks; removed_MAX_DEPTH_GUARDentirelynot entry_point,not src_val,not tgt_valchecks for empty-string rejectioncontinuetoraise ConfigurationError_compute_subgraph_depth(subgraph must reference a graph route)import sysfromCleverActorsLib.pyentry_pointdefault"start"from_compute_graph_depth; replaced withassertnodeslookup; addedvalue >= 0check in_validate_limit_type; extracted_call_and_capturehelper to eliminatenoqacommentsCycle 5
Review findings (0C/2M/5m/6n):
validate_dict(None, {})andvalidate_dict({}, None)raiseTypeError/AttributeErrorinstead ofConfigurationError; iterative DFS for longest-path is exponential in graph density (12-node complete graph hangs indefinitely) — practical DoS vector for router-facing APIassertstatements in depth functions raiseAssertionErrornotConfigurationError; O(N²) frozenset allocations; non-dict edges/operators silently skipped; graph node missingtypekey produces confusing error;ConfigurationErrornot exported from__init__.pynodes_rawassigned before presence check;is Nonecheck unreachable; non-specific step assertions; file size guideline violations; British spelling in docstringRemaining Issues (after Cycle 5 — not yet fixed)
validate_dict(None, {})crashes withTypeErrorinstead ofConfigurationError— needsisinstance(config_dict, dict)guard at function entryMAX_DFS_STEPS = 1_000_000)assertstatements in_compute_graph_depth/_compute_subgraph_depthraiseAssertionErrornotConfigurationErrortypekey in graph node produces confusing"unknown node type ''"errorConfigurationErrornot exported from__init__.pySelf-QA Implementation Notes (Cycles 6–9)
Cycle 6
Review findings (0C/2M/5m/6n):
validate_dict(None, {})crashes withTypeErrorinstead ofConfigurationError; iterative DFS for longest-path is exponential in graph density (12-node complete graph hangs indefinitely)assertstatements raiseAssertionErrornotConfigurationError; O(N²) frozenset allocations; non-dict edges/operators silently skipped; graph node missingtypekey produces confusing error;ConfigurationErrornot exported from__init__.pynodes_rawassignment order,is Nonecheck comment, non-specific step assertions, file size violations, British spellingFixes applied:
isinstance(config_dict, dict)andisinstance(platform_limits, dict)guards at top ofvalidate_dictMAX_DFS_STEPS = 1_000_000step-counter bailout in_compute_graph_depthassertstatements with explicitConfigurationErrorchecks_compute_graph_depthfrom frozenset-per-step to mutable-set backtracking (O(1) per step)continuetoraise ConfigurationErrorNonecheck for missing nodetypekeyConfigurationErrorto__init__.pyexports and__all__Cycle 7
Review findings (0C/6M/5m/5n):
_compute_subgraph_depthlacks step limit (DoS);_compute_subgraph_depthretains O(D²) frozenset allocations (claimed fix not applied);# type: ignoresuppressions in step files; three files exceed 500-line limit;_validate_stream_routesilently skips non-list operators; imports buried inside functionsMAX_DFS_STEPSinside function body; unused_labelin DFS stack tuple; misleadingvisitingparameter type; dangling subgraph references silently accepted; misleading error message for initial routevalidate_dictimport not groupedFixes applied:
_MAX_SUBGRAPH_STEPS = 1_000_000step counter in_compute_subgraph_depth_compute_subgraph_depthto mutable-set backtracking (removed frozenset unions)# type: ignoresuppressions; changed_call_and_captureto acceptAnyvalidation.pyinto sub-package:validation/__init__.py,_vocabulary.py,_agents.py,_routes.py,_limits.pyvalidate_dict_steps.pyinto 6 focused files:validate_dict_helpers.py,validate_dict_steps.py,validate_dict_agents_steps.py,validate_dict_routes_steps.py,validate_dict_limits_steps.py,validate_dict_export_steps.pyoperatorsfrom silent return toraise ConfigurationErrorMAX_DFS_STEPSto module-level constant_MAX_DFS_STEPS_labelfrom DFS stack tuplevisitingparameter from_compute_subgraph_depth(manages its own set)ConfigurationErrorfor dangling subgraph referencesCycle 8
Review findings (0C/2M/4m/4n):
import cleveractorsinside Robot keyword method bodies;validate_limit_typemissing_private prefix_MAX_DFS_STEPSguard branch untested;_MAX_SUBGRAPH_STEPSguard branch untested; unusednodesvariable in_compute_graph_depth; non-dictconfig_dict/platform_limitsonly tested withNonevalidate_dict as _validate_dictalias unnecessary; misleading scenario title; weak platform_limits error assertion; Robot test count mismatchFixes applied:
import cleveractorsto top ofCleverActorsLib.pyvalidate_limit_type→_validate_limit_typewith 3 call-site updates_MAX_DFS_STEPSand_MAX_SUBGRAPH_STEPSguardsfeatures/environment.pyafter_scenariocleanup to restore monkey-patched constantsnodesvariable from_compute_graph_depthconfig_dictand stringplatform_limitsas _validate_dictalias; renamed misleading scenario; strengthened platform_limits assertionsCycle 9
Review findings (0C/1M/7m/5n):
max_subgraph_depth— invalid configs silently pass when limit is absentimport shutilinCleverActorsLib.py; redundant buriedimport shutilinenvironment.py; redundant buriedimport osinenvironment.py; buried imports in monkey-patch step functions; missing milestone on PRvalidate_dictimport not grouped; stale docstring in helpers; duplicate step definitions; no rationale comment for_MAX_DFS_STEPS; defensiveisinstanceguard in_count_total_nodesFixes applied:
_validate_graph_route— now runs unconditionally regardless ofmax_subgraph_depthprovider.strip().lower()for whitespace-tolerant provider comparison_validate_graph_routev2.1.0milestoneisinstancewithassertFinal Verdict (Cycle 9): APPROVED
All 8 acceptance criteria from ticket #10 are fully implemented and tested. Quality gates pass at 96.99% coverage (threshold 96.5%). The PR is ready to merge.
Remaining minor items (not blocking merge):
features/environment.pymonkey-patch teardown (minor CONTRIBUTING.md violation)route_type = Noneandallowed_providersasset/frozensetv2.1.0)CleverActorsLib.py(not introduced by this PR)Self-QA Implementation Notes (Cycles 10–12)
Cycle 10
Review findings (0C/2M/7m/4n):
__were silently accepted.setattr(_vocab, "_MAX_DFS_STEPS", ...)had no effect on_limits.py's local binding; teardown saved/restored from the wrong module._validate_llm_providersilently skipped non-dictagent_configinstead of raisingConfigurationError._count_total_nodesusedassertthat can be disabled with-O.route_type = Noneexplicit branch untested.allowed_providersassetorfrozensetuntested.validate_dict_agents_steps.py,validate_dict_export_steps.py).validate_dict_routes_steps.pyexceeded 500-line file size limit (581 lines).environment.pymonkey-patch teardown.Finalannotation semantically incompatible with monkey-patching.noqa: B010suppressions instead of direct attribute assignment._vocabpatch calls in monkey-patch steps.Fixes applied:
agent_name.startswith("__")check in_agents.pyandroute_name.startswith("__")check in_routes.py; added 2 BDD scenarios.setattr(_vocab, ...)calls fromvalidate_dict_limits_steps.pyandenvironment.py; changed to direct attribute assignment on_limitsonly; moved imports to top-level; consolidated teardown blocks._validate_llm_providerto raiseConfigurationErrorfor non-dictagent_config; updated BDD scenario.assert isinstance(nodes, dict)in_count_total_nodeswith explicitisinstancecheck raisingConfigurationError.route_type = None.allowed_providersassetandfrozenset.validate_dict_agents_steps.pyandvalidate_dict_export_steps.py.validate_dict_routes_steps.pyintovalidate_dict_stream_steps.py(86 lines) andvalidate_dict_subgraph_steps.py(105 lines); routes file reduced to 431 lines.import cleveractors.validation._limitsto top ofenvironment.py; consolidated twoifblocks.Finalannotation from_MAX_DFS_STEPSand_MAX_SUBGRAPH_STEPS.setattrcalls with direct attribute assignment; eliminated allnoqa: B010comments.Cycle 11
Review findings (0C/1M/6m/5n):
max_total_nodesaggregation across multiple graph routes:_count_total_nodessums across all routes but every existing scenario used only a single route.agentsdict causedAttributeErrorinstead ofConfigurationError.routesdict causedAttributeErrorinstead ofConfigurationError.robot/CleverActorsLib.pyexceeded 500-line limit (555 lines) after PR additions._MAX_DFS_STEPSceiling of 1,000,000 too high for a web service upload endpoint (realistic DoS vector).allowed_providersastuple.validate_dict Can Be Used Without Any App Construction.import json,import asyncio,from cleveractors.agents.tool import ToolAgentinCleverActorsLib.py(pre-existing)._subgraph_childrendefensive branches not annotated with# pragma: no branch.operatorslist on stream route.Fixes applied:
max_total_nodesaggregation across two graph routes (exceeding limit + at-exact-limit).isinstance(agent_name, str)guard in_agents.pyraisingConfigurationError; added BDD scenario.isinstance(route_name, str)guard in_routes.pyraisingConfigurationError; added BDD scenario.validate_dictkeywords fromCleverActorsLib.pyinto newrobot/ValidateDictLib.py; updatedrobot/validate_dict.robotto import both libraries.CleverActorsLib.pynow at 492 lines._MAX_DFS_STEPSand_MAX_SUBGRAPH_STEPSfrom 1,000,000 to 50,000.allowed_providersastuple.validate_dict Accepts Minimal Valid Config.import json,import asyncio,from cleveractors.agents.tool import ToolAgentto top-level imports inCleverActorsLib.py.# pragma: no branchto defensive guards in_subgraph_children.operatorslist on stream route.Cycle 12
Review findings: Approved (0C/0M/3m/4n)
The reviewer noted three minor inconsistencies and four nits, none blocking merge:
isinstance(node_name, str)guard for graph node keys (inconsistency with agent/route key guards).# pragma: no branchon_count_total_nodesdefensive guard (inconsistent with_subgraph_children).steps > _MAX_DFS_STEPSfires at 50,001 but message says "exceeded 50,000").build_subgraph_chain(0)produces a dangling subgraph reference (latent helper bug, no current scenario triggers it).environment.py.step_error_mentions_limits_typeassertion is broad._compute_subgraph_depthcalled once per graph route (minor redundancy, not a practical concern).Verdict: APPROVED — PR is ready to merge.
Remaining Issues (not blocking merge)
isinstance(node_name, str)guard missing for graph node keys (minor inconsistency).# pragma: no branchmissing on_count_total_nodesguard (minor coverage annotation inconsistency).build_subgraph_chain(0)latent helper bug.environment.py.⚠️ Bot Interference — Schema Conflict with
runtime.pyThe bot pushed commit
e7a7d39directly tomasteron 2026-06-04. Among other things, it added avalidate_dict()function insidesrc/cleveractors/runtime.pythat is incompatible with this ticket's spec and with our proper implementation onfeature/validate-dict-api(37f8453).What the bot's
validate_dictdoes (wrong)typefield with vocabulary{llm, graph, tool, multi_actor}llmactors: expectsproviderandmodelat top level or underconfigblockgraphactors: expectsroute.nodes/route.edges/route.entry_nodeWhat this ticket requires (correct — our
feature/validate-dict-api)agentsandrouteskeys (raisesConfigurationErrorif either is absent){llm, tool, composite, chain, template_instance}providervalidated againstplatform_limits["allowed_providers"]source/target(notfrom/to)max_graph_depth(DFS longest-path),max_subgraph_depth,max_total_nodesThese two schemas are mutually incompatible. A config dict that passes the bot's version will fail ours, and vice versa.
Current state of
__init__.py(after bot's commit)The bot's
__init__.pyimportsvalidate_dictfromruntime, which means the bot's version is currently the one exported at the package level:What needs to happen
feature/validate-dict-apion top of the currentmasteras-is — thevalidate_dictinruntime.pywill conflict and will silently take precedence if not removed.feature/validate-dict-apiis ready to merge, remove thevalidate_dictfunction fromruntime.pyentirely and update the import in__init__.pyto use our implementation:runtime.py'svalidate_dictmay still be referenced by the bot'sExecutorcode — confirm those call sites are updated to use the canonicalvalidation.validate_dictas well.validate_dictalso does not enforce themax_graph_depthDFS check, themax_subgraph_depthrecursion guard, or thesource/targetedge field requirement — none of the structural validation from this ticket is present in the bot's version.Action items for this ticket
feature/validate-dict-api→master: removevalidate_dictfromruntime.py__init__.pyimport to usecleveractors.validation.validate_dictExecutorinruntime.pycalls through to the canonicalvalidate_dict(or none at all —Executor.execute()should not re-validate; validation is caller's responsibility)✅ Bot Interference Resolved —
validate_dictRemoved fromruntime.pyAddressing the action items raised in the previous comment:
What was done
validate_dictfromruntime.py— The entire# Validationsection (the bot's incorrect implementation expecting atypefield with vocabulary{llm, graph, tool, multi_actor}) has been deleted fromruntime.py.runtime.py's docstring no longer listsvalidate_dict(d, platform_limits)as part of its public API surface.__init__.pywas already correct — The feature branch's commit already importedvalidate_dictfromcleveractors.validation, not fromcleveractors.runtime. No change needed there.Executorconfirmed clean —Executor.execute()inruntime.pydoes not callvalidate_dictinternally. Validation is the caller's responsibility, as intended.Amended commit
The fix was folded into the existing
feat(validate_dict)commit via--amendand force-pushed tofeature/validate-dict-api(91172ae).Action item status
validate_dictfromruntime.py__init__.pyimport to usecleveractors.validation.validate_dict(was already done in the original commit)Executorinruntime.pydoes not callvalidate_dictinternally (confirmed clean)cleveractors.validation.validate_dictexported)Self-QA Implementation Notes (Cycles 13–15)
Cycle 13
Review findings (0C/3M/4m/4n):
entry_point(reviewer claimed spec defines it as optional with default"start") — verified against spec and found NOT VALID:entry_pointis required per the authoritative spec. No code change.start/endnodes (reviewer claimed spec mandates implicit injection) — verified against spec and found NOT VALID:start/endmust be explicitly declared. No code change.isinstance(node_name, str)guard for graph node keys (Cycle 12 m1 carry-over).# pragma: no branchon_count_total_nodesdefensive guard (Cycle 12 m2 carry-over).continuebranches in_validate_graph_routelack# pragma: no branch.src not in nodes_rawbranch (dangling source node) never exercised by any test.environment.py(Cycle 12 n2 carry-over).build_subgraph_chain(0)produces dangling subgraph reference.steps > _MAX_DFS_STEPSallows one extra iteration beyond documented ceiling.Fixes applied:
isinstance(node_name, str)guard in_validate_graph_routebefore the node loop; added BDD scenario.# pragma: no branchto_count_total_nodesdefensive guard in_limits.py.# pragma: no branchto both deadcontinueguards in_validate_graph_route.environment.py.if depth <= 0: raise ValueError(...)guard tobuild_subgraph_chainin helpers.steps > _MAX_DFS_STEPStosteps >= _MAX_DFS_STEPS(and same for_MAX_SUBGRAPH_STEPS).list[str]toset[str]for O(1) deduplication.Cycle 14
Review findings (0C/2M/6m/2n) — ADR-2029 items excluded (not applicable to this library):
_subgraph_childrenyields duplicate route names, causing false "too complex" errors on legitimate configs with multiple nodes referencing the same subgraph."openai"is excluded from customallowed_providers."agent"and"end"."map"._compute_subgraph_depthcalled once per graph route with no memoization.environment.pyimports_limitsinternals at module level, coupling all BDD tests to internal implementation detail.validate_dict_routes_steps.pyat 473 lines, approaching 500-line limit._vocabulary.pymodule comment overstates DFS step capacity.Fixes applied:
seen: set[str]deduplication in_subgraph_children()— each unique subgraph ref yielded at most once per route; added BDD scenario.set[str]in_compute_graph_depth()— O(1) deduplication, O(N²) worst case for dense graphs.allowed_providersexcludes"openai".start,tool,conditional,message_router,function.filter,transform,reduce.depth_cache: dict[str, int]parameter to_compute_subgraph_depth()and memoize results within a singlevalidate_structural_limitscall.import cleveractors.validation._limits as _limitsfrom module level intoafter_scenario, guarded byhasattr(context, '_original_max_dfs').validate_dict_routes_steps.py(473 lines) intovalidate_dict_routes_steps.py(83 lines, generic route steps) andvalidate_dict_graph_route_steps.py(447 lines, graph-specific steps)._vocabulary.pycomment to accurately state O(N + E) complexity and note dense-graph ceiling exhaustion.Cycle 15
Review findings: Approved (0C/0M/3m/4n)
The reviewer confirmed all acceptance criteria are met and all Cycle 13–14 fixes were applied correctly. Remaining non-blocking items:
depth_cachememoization in_compute_subgraph_depthis incomplete: only top-level routes are cached, not intermediate children. Correctness is unaffected; the 50k step ceiling bounds worst-case behavior.validate_dict.featureat 877 lines exceeds the 500-line file size limit.>=)._limitsinafter_scenario.CHANGELOG.mdentry is 842 characters on a single line.visitedvariable name in_compute_graph_depthis misleading (tracks current path, not all visited nodes).Verdict: APPROVED — PR is ready to merge.
Remaining Issues (not blocking merge)
depth_cachememoization incomplete for intermediate child routes (performance only, correctness unaffected).validate_dict.featureexceeds 500-line limit (877 lines).