feat(routing): make conversation assignment extensible via EvoExtensionPoints#124
Open
miltonsosa-info-unlp wants to merge 7 commits into
Conversation
- Strategies::Base defines interface contract (User.find_by only) - Strategies::RoundRobin delegates to InboxRoundRobinService - Regression spec ensures identical output for same inputs
- Separate interface contract from implementation guidance in Base: move User.find_by policy note under explicit 'Implementation guidance' section so the contract boundary is clear - RoundRobin now includes AutoAssignment::Strategies::Base to create an explicit code-level relationship instead of doc-only coupling
…nts v2.1.0) - Declare EvoExtensionPoints::RoutingStrategy no-op module - Add :routing_strategy to KNOWN_KEYS; bump contract version 2.0.0 -> 2.1.0 - Wire AgentAssignmentService#find_assignee to extension point with Strategies::RoundRobin fallback and Rails.logger info line - Document :routing_strategy in EXTENSION_POINTS.md (v2.1.0 section) - Add spec/lib/evo_extension_points/routing_strategy_spec.rb (6 examples) - Add spec/services/auto_assignment/agent_assignment_service_spec.rb (5 examples) - Update evo_extension_points_spec.rb version assertion to 2.1.0 All 44 specs pass. contract_check guard-rail (EVO-1287) passes.
- Fix stale comment: 'five sub-modules' -> 'six sub-modules' - Remove dead private methods: round_robin_manage_service, round_robin_key - Improve log readability: Proc override now logs as EvoExtensionPoints[:routing_strategy] - Add :routing_strategy example to EXTENSION_POINTS.md consumer section
… extension point - routing_strategy.rb: note that strategy classes must include Strategies::Base - EXTENSION_POINTS.md: clarify Strategies::Base as formal Ruby contract module for internal classes; Proc-based consumers are not required to include it
…h custom scoring example
…oot overrides
- Load EvoExtensionPoints in config/application.rb before initializers run
(Zeitwerk does not autoload lib/ during initializer phase; same pattern
used for EvoFlow::EVENT_NAMES). Deployers can now drop a routing
initializer without a require.
- Add before { EvoExtensionPoints.reset! } in the 'without override'
context so the spec is not affected by any initializer that registers
an override at process boot.
There was a problem hiding this comment.
Sorry @miltonsosa-info-unlp, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Today
AgentAssignmentService#find_assigneehardcodesInboxRoundRobinService.There is no way for a deployer to change the assignment algorithm without forking the repo.
This PR makes the assignment algorithm a declared extension point.
Any deployer drops one initializer file to replace the algorithm entirely —
the community default (Round Robin) is unchanged for anyone who doesn't.
What this PR does
Extracts
Strategies::RoundRobin— a thin facade over the existingInboxRoundRobinService. No logic change; this becomes the fallback when nooverride is registered.
Declares
:routing_strategyinEvoExtensionPoints(v2.0.0 → v2.1.0) — asingle hook resolved by
AgentAssignmentServiceat runtime.Wires
AgentAssignmentService#find_assigneeto the extension point withStrategies::RoundRobinas the fallback — three lines, zero behavior changewithout an override.
Ships
Strategies::BalancedLoad— a ready-to-use strategy that assigns to theonline agent with the fewest open conversations (single
GROUP BYSQL query,no N+1, UUID-safe). Activatable in one initializer file; not the default.
Activating a custom strategy
Drop a file in
config/initializers/— nothing else changes:That is the complete integration. No migrations, no UI changes, no core modifications.
EvoExtensionPointsis loaded eagerly inconfig/application.rb(same pattern asEvoFlow::EVENT_NAMES) — norequireneeded in the deployer's initializer.Writing your own strategy
Any object that responds to
.call(conversation, allowed_agent_ids: [String]) → User | nilqualifies. See
app/services/auto_assignment/strategies/README.mdfor the full interface contract, implementation guidance, and a commented example
that scores agents by workload and time dedicated to conversations today.
Functional transparency: what changes (and what doesn't)
[AgentAssignment] routing via RoundRobin for conversation 42The log line makes the active strategy observable in production without extra tooling.
With a custom override it logs
[AgentAssignment] routing via EvoExtensionPoints[:routing_strategy].Backward compatibility
Zero behavior change in the community release.
Without a registered override,
impl_for(:routing_strategy)returnsniland thefallback path executes
Strategies::RoundRobin, which delegates to the sameInboxRoundRobinServiceas the current code. Every existing spec passes unchanged.The private methods
round_robin_manage_serviceandround_robin_keyare removedfrom
AgentAssignmentService— they had no callers outside the class and theirprivatevisibility was explicit. If any deployment subclassesAgentAssignmentServiceand calls these methods directly, a
NoMethodErrorwill surface; the fix is to callAutoAssignment::Strategies::RoundRobin.call(...)directly.Changed files
Security
Pure internal refactor. Zero new HTTP endpoints, zero auth surface changes.
No new gems, no migrations, no environment variables.
Extension point overrides execute only code explicitly registered at process boot —
the registration is a closed, auditable list of
EvoExtensionPoints.replace(...)callsin the deployer's own initializers.
Test plan
bundle exec rspec \ spec/services/auto_assignment/ \ spec/lib/evo_extension_points/ \ --format documentationExpected: all green. Covers regression (RoundRobin parity), contract compliance,
extension point wiring (with/without override), BalancedLoad logic including N+1 guard.
Linked issue / context
This replaces PRs #102, #104, and #109 (stacked, fragmented approach).
Those PRs are superseded by this single self-contained change.