Skip to content

feat: scenario routing architecture redesign (020)#25

Open
john-zhh wants to merge 8 commits intofeat/v3.0.1from
020-scenario-routing-redesign
Open

feat: scenario routing architecture redesign (020)#25
john-zhh wants to merge 8 commits intofeat/v3.0.1from
020-scenario-routing-redesign

Conversation

@john-zhh
Copy link
Contributor

Overview

Redesign scenario routing to be protocol-agnostic, middleware-extensible, and support open scenario namespaces.

Specification

This PR adds the specification for redesigning the scenario routing system to address current architectural limitations:

Current Problems

  • Scenario detection is protocol-specific (Anthropic-only)
  • Middleware cannot drive routing decisions
  • Scenario namespace is closed and hardcoded
  • Per-scenario routing policies are too limited
  • Config validation is weak

Target Architecture

  • Protocol-agnostic routing: Normalize Anthropic/OpenAI Chat/OpenAI Responses into common semantic model
  • Middleware extensibility: First-class RoutingDecision and RoutingHints API
  • Open scenario namespace: Support custom route keys (e.g., spec-kit stages: specify, clarify, plan, tasks, analyse, implement)
  • Rich per-scenario policies: Each route can define strategy, weights, thresholds, fallback behavior
  • Strong validation: Fail-fast on invalid routing config
  • Comprehensive observability: Structured logs for all routing decisions

Files Added

  • specs/020-scenario-routing-redesign/spec.md - Complete feature specification
  • specs/020-scenario-routing-redesign/checklists/requirements.md - Quality validation checklist
  • docs/scenario-routing-architecture.md - Detailed architecture design document

Next Steps

  1. Review and approve specification
  2. Run /speckit.plan to generate implementation plan
  3. Run /speckit.tasks to generate task breakdown
  4. Implement according to plan

Related

  • Builds on 019-profile-strategy-routing
  • Enables spec-kit middleware routing
  • Part of v3.0.1 release

Checklist

  • Specification complete and validated
  • All mandatory sections filled
  • No implementation details in spec
  • Success criteria are measurable and technology-agnostic
  • Edge cases identified
  • Dependencies and assumptions documented

john-zhh and others added 8 commits March 10, 2026 13:29
Add comprehensive specification for redesigning scenario routing to be:
- Protocol-agnostic (Anthropic, OpenAI Chat, OpenAI Responses)
- Middleware-extensible (explicit routing decisions)
- Open scenario namespace (custom route keys)
- Per-scenario routing policies (strategy, weights, thresholds)

Key requirements:
- Normalized request layer for protocol-agnostic detection
- First-class middleware routing hooks (RoutingDecision, RoutingHints)
- Open scenario keys supporting custom workflows (spec-kit stages)
- Strong config validation with fail-fast behavior
- Comprehensive routing observability

Includes quality checklist confirming specification readiness.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Completed foundational infrastructure for protocol-agnostic scenario routing:

Phase 1 (Setup):
- T001-T003: Created routing file structure and types
- Added RoutingDecision and RoutingHints types in routing_decision.go
- Extended RequestContext with routing fields (using interface{} to avoid circular deps)

Phase 2 (Foundational):
- T004: Bumped config version 14 → 15
- T005: Added RoutePolicy type replacing ScenarioRoute
  - Supports per-scenario strategy, weights, threshold, fallback
  - Updated ProfileConfig.Routing to use string keys and RoutePolicy values
  - Updated Clone() method for deep copying
- T006: Implemented NormalizeScenarioKey function
  - Supports camelCase, kebab-case, snake_case normalization
  - Examples: web-search → webSearch, long_context → longContext
- T007: Implemented ValidateRoutingConfig function
  - Validates provider existence, weights, strategies, scenario keys
  - Comprehensive error messages for config issues
- T008: Added structured logging functions for routing
  - LogRoutingDecision, LogRoutingFallback, LogProtocolDetection
  - LogRequestFeatures, LogProviderSelection

Phase 3 (User Story 1 - Tests):
- T009-T013: Wrote comprehensive tests for protocol normalization
  - TestNormalizeAnthropicMessages (7 test cases)
  - TestNormalizeOpenAIChat (7 test cases)
  - TestNormalizeOpenAIResponses (5 test cases)
  - TestMalformedRequestHandling (5 test cases)
  - TestExtractFeatures (5 test cases)
- Tests follow TDD approach (written before implementation)

Files modified:
- internal/config/config.go: RoutePolicy type, version bump
- internal/config/store.go: ValidateRoutingConfig function
- internal/middleware/interface.go: Routing fields in RequestContext
- internal/daemon/logger.go: Routing-specific logging functions

Files created:
- internal/proxy/routing_decision.go: RoutingDecision and RoutingHints types
- internal/proxy/routing_classifier.go: NormalizeScenarioKey function
- internal/proxy/routing_normalize_test.go: Comprehensive test suite (29 tests)

Next: Implement User Story 1 (protocol normalization functions)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implemented core normalization functions for protocol-agnostic routing:

Types & Infrastructure:
- Created NormalizedRequest and NormalizedMessage types
- Created RequestFeatures type for routing classification
- Updated config types: Scenario remains type alias, but routing uses string keys
- Changed ProfileConfig.Routing from map[Scenario]*ScenarioRoute to map[string]*RoutePolicy
- Updated RoutingConfig.ScenarioRoutes to use string keys

Normalization Functions (T015-T021):
- NormalizeAnthropicMessages: Handles Anthropic Messages API format
  - Extracts model, system prompt, messages
  - Supports both string and array content (text + images)
  - Detects image content and tool usage
- NormalizeOpenAIChat: Handles OpenAI Chat Completions API format
  - Extracts system message from messages array
  - Supports vision content (image_url type)
  - Detects functions and tools
- NormalizeOpenAIResponses: Handles OpenAI Responses API format
  - Supports both string and array input formats
  - Converts to user messages
- ExtractFeatures: Extracts routing-relevant features
  - Detects images, tools, long context, message count

Type Migration:
- Updated DetectScenario and DetectScenarioFromJSON to return string
- Updated all test files to use string keys instead of config.Scenario
- Fixed profileInfo struct to use map[string]*RoutePolicy
- Updated scenario detection in server.go to use string type

Test Results:
- All 29 normalization tests passing
- TestNormalizeAnthropicMessages: 7/7 passing
- TestNormalizeOpenAIChat: 7/7 passing
- TestNormalizeOpenAIResponses: 5/5 passing
- TestMalformedRequestHandling: 5/5 passing
- TestExtractFeatures: 5/5 passing

Files modified:
- internal/proxy/routing_normalize.go: Core normalization implementation
- internal/proxy/scenario.go: Return string instead of config.Scenario
- internal/proxy/server.go: Use string for scenario routing
- internal/proxy/profile_proxy.go: Use map[string]*RoutePolicy
- internal/proxy/*_test.go: Updated all tests to use string keys

Next: T017 (DetectProtocol), T022 (token counting), T023-T025 (server integration)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…T022-T025)

Completed all remaining tasks for User Story 1:

T014 - Integration Tests:
- Created tests/integration/routing_protocol_test.go
- TestProtocolAgnosticRouting: Verifies equivalent requests via different protocols
- TestProtocolDetectionPriority: Tests priority order (URL → header → body → default)
- All 7 integration test cases passing

T017 - DetectProtocol Function:
- Implements 4-level priority detection:
  1. URL path (/v1/messages → anthropic, /v1/chat/completions → openai_chat)
  2. X-Zen-Client header (anthropic/claude/openai/openai_responses)
  3. Body structure (claude model → anthropic, input field → openai_responses)
  4. Default to openai_chat (most common)
- Handles ambiguous /completions path (checks for input field)

T022 - Token Counting:
- Added estimateTokens() helper using tiktoken
- Falls back to character-based estimation (~4 chars/token)
- Integrated into all normalization functions
- TokenCount field populated for all NormalizedMessage instances
- Accurate long-context detection via ExtractFeatures

T023-T025 - Server Integration:
- Updated ProxyServer.ServeHTTP to detect protocol and normalize requests
- Populates RequestContext.RequestFormat with detected protocol
- Populates RequestContext.NormalizedRequest with normalized data
- Error handling: logs normalization errors, continues with default routing
- Middleware receives normalized request for routing decisions

Type Migration (Web API):
- Updated internal/web/api_profiles.go to use map[string]*RoutePolicy
- Fixed profileResponse, createProfileRequest, updateProfileRequest types
- Updated routingResponseToConfig to return RoutePolicy map

Test Results:
- Unit tests: 29/29 passing (normalization, malformed, features)
- Integration tests: 7/7 passing (protocol detection, routing)
- All existing tests still passing

Files modified:
- internal/proxy/routing_normalize.go: Added estimateTokens, DetectProtocol
- internal/proxy/server.go: Integrated normalization in ServeHTTP
- internal/web/api_profiles.go: Updated types for string-keyed routing
- tests/integration/routing_protocol_test.go: Comprehensive integration tests

User Story 1 Status: ✅ COMPLETE
- Protocol-agnostic normalization working across all 3 protocols
- Token counting accurate with tiktoken integration
- Server integration complete with error handling
- All tests passing (36 total test cases)

Next: User Story 2 (Middleware-Driven Custom Routing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Changed all TUI code to use string keys for routing maps
- Updated switchToScenarioEditMsg.scenario from config.Scenario to string
- Updated scenarioEditModel.scenario from config.Scenario to string
- Updated scenarioEntry.scenario from config.Scenario to string
- Updated knownScenarios to use string(config.Scenario) conversions
- Fixed cmd/root.go scenarioRoutes map type to map[string]*proxy.ScenarioProviders
- Fixed all test files to use string() conversions for scenario keys
- Updated test data format from v14 to v15 (ProviderRoute array structure)
- Updated TestConfigMigrationV11ToV12 to expect version 15

All tests passing (36 total).
- Fix identical expressions bug in fbmessenger.go (len(payload) - len(payload))
- Remove unnecessary nil checks for map length (S1009)
- Fix error string punctuation (ST1005)
- Use type conversion instead of struct literal (S1016)
- Remove unnecessary nil check around range (S1031)
- Fix possible nil pointer dereference in nlu_test.go (SA5011)
- Fix unused value assignment in store_test.go (SA4006)
- Remove ineffective assignment in form.go (SA4005)
- Run go mod tidy to clean up dependencies

All tests passing (36 total). All staticcheck warnings resolved.
…ecks)

- Add Principle IX requiring staticcheck for Go and eslint for TypeScript
- Code quality checks MUST be run after tests and before PR submission
- All staticcheck warnings MUST be addressed (except intentional U1000)
- All eslint errors MUST be fixed, warnings SHOULD be addressed
- Update Development Workflow section to include quality check steps
- Update release checklist to include quality checks

Version: 1.4.0 → 1.5.0 (MINOR)
Rationale: New principle added for code quality enforcement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant