Framework & Vertical Integration Architecture¶
Target Audience: Framework developers extending Victor with new verticals or capabilities.
Overview¶
Victor's architecture follows SOLID principles with a protocol-first design that separates concerns between the framework core and domain-specific verticals.
---
title: Framework integration of vertical capabilities
---
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#E8EFF7","primaryTextColor":"#17324D","primaryBorderColor":"#456987","lineColor":"#456987","fontFamily":"Arial"}}}%%
flowchart TB
C["Client"]
F["Framework factory"]
P["VerticalIntegrationPipeline"]
H["StepHandlerRegistry"]
V["VerticalBase contract"]
R["Configured runtime"]
C -->|"request configured agent"| F
F -->|"apply selected vertical"| P
V -->|"supply capabilities"| P
P -->|"dispatch integration steps"| H
H -->|"bind tools, prompts and extensions"| R
Core Components¶
1. VerticalIntegrationPipeline¶
Location: victor/framework/vertical_integration.py
Facade that applies vertical configurations to orchestrators via step handlers.
from victor.framework.vertical_integration import VerticalIntegrationPipeline
pipeline = VerticalIntegrationPipeline()
result = pipeline.apply(orchestrator, CodingAssistant)
Key Features:
- Protocol-first capability invocation (_check_capability, _invoke_capability)
- Two-level caching (definition + execution)
- Result tracking with IntegrationResult
2. StepHandler System¶
Location: victor/framework/step_handlers.py
Single Responsibility Principle: each handler manages one integration concern.
| Handler | Order | Responsibility |
|---|---|---|
CapabilityConfigStepHandler |
5 | Centralized capability config storage |
ToolStepHandler |
10 | Tool filter canonicalization |
TieredConfigStepHandler |
15 | Tiered tool configuration |
PromptStepHandler |
20 | System prompt application |
ConfigStepHandler |
40 | Stage definitions |
MiddlewareStepHandler |
50 | Middleware chain |
SafetyStepHandler |
30 | Safety patterns |
ExtensionsStepHandler |
45 | Extension coordination |
FrameworkStepHandler |
60 | Workflows, RL, teams, handlers |
ContextStepHandler |
100 | Final context attachment |
Protocol-First Design:
def _check_capability(obj: Any, capability_name: str) -> bool:
"""Check capability via protocol (DIP compliant)."""
if isinstance(obj, CapabilityRegistryProtocol):
return obj.has_capability(capability_name)
# Fallback to public method only (no private attributes)
3. VerticalBase¶
Location: victor/core/verticals/base.py
Abstract base class for all verticals with built-in caching.
class MyVertical(VerticalBase):
name = "my_vertical"
@classmethod
def get_tools(cls) -> List[str]:
return ["read", "write", "grep"]
@classmethod
def get_system_prompt(cls) -> str:
return "You are a specialized assistant..."
Extension Caching:
@classmethod
def get_safety_extension(cls):
def _create():
from victor.myvertical.safety import MySafetyExtension
return MySafetyExtension()
return cls._get_cached_extension("safety_extension", _create)
Protocol-First Design¶
Capability Registry Protocol¶
Location: victor/framework/protocols.py
Replaces hasattr duck-typing with explicit capability contracts.
@runtime_checkable
class CapabilityRegistryProtocol(Protocol):
def has_capability(self, name: str, min_version: Optional[str] = None) -> bool: ...
def invoke_capability(self, name: str, *args, **kwargs) -> Any: ...
def get_capability(self, name: str) -> Optional[OrchestratorCapability]: ...
OrchestratorVerticalProtocol¶
@runtime_checkable
class OrchestratorVerticalProtocol(Protocol):
def set_vertical_context(self, context: VerticalContext) -> None: ...
def set_enabled_tools(self, tools: Set[str]) -> None: ...
def apply_vertical_middleware(self, middleware: List[Any]) -> None: ...
def apply_vertical_safety_patterns(self, patterns: List[Any]) -> None: ...
Capability System¶
Framework Capabilities¶
Location: victor/framework/capabilities/
Reusable capabilities across verticals following DRY:
from victor.framework.capabilities import (
FileOperationsCapability, # read, write, edit, grep
PromptContributionCapability, # common prompt hints
PrivacyCapabilityProvider, # PII management
)
# Use in vertical
class MyVertical(VerticalBase):
_file_ops = FileOperationsCapability()
def get_tools(cls):
return cls._file_ops.get_tool_list()
Vertical-Specific Capabilities¶
Each vertical can define capabilities via BaseCapabilityProvider:
# victor/coding/capabilities.py
class CodingCapabilityProvider(BaseCapabilityProvider):
def get_capabilities(self) -> Dict[str, Callable]:
return {
"git_safety": configure_git_safety,
"code_style": configure_code_style,
}
SOLID Compliance Summary¶
| Principle | Implementation | File |
|---|---|---|
| SRP | Each StepHandler handles one concern | step_handlers.py |
| OCP | ExtensionsStepHandler extension registry for pluggable handlers | step_handlers.py |
| LSP | StepHandlerProtocol ensures substitutability | step_handlers.py:278 |
| ISP | Focused protocols (CapabilityRegistry, SubAgentContext) | protocols.py |
| DIP | _check_capability/_invoke_capability use protocols |
step_handlers.py:120 |
Extension Points¶
Adding a New Step Handler¶
from victor.framework.step_handlers import BaseStepHandler, StepHandlerRegistry
class MyCustomStepHandler(BaseStepHandler):
@property
def name(self) -> str:
return "my_custom"
@property
def order(self) -> int:
return 55 # Between extensions (45) and framework (60)
def _do_apply(self, orchestrator, vertical, context, result):
# Your logic here
pass
# Register
registry = StepHandlerRegistry.default()
registry.add_handler(MyCustomStepHandler())
Adding Extension Handlers (Active Path)¶
# Note: _ExtensionHandler is internal to step_handlers (FEP-0015) — extension
# registration happens through the public ExtensionHandlerRegistry surface.
from victor.framework.step_handlers import ExtensionsStepHandler, StepHandlerRegistry
from victor.framework.step_handlers import _ExtensionHandler
def handle_my_extension(orchestrator, ext_value, extensions, context, result):
# Handle extension type from vertical.get_extensions()
result.add_info("Handled my_extension")
registry = StepHandlerRegistry.default()
extensions = registry.get_handler("extensions")
assert isinstance(extensions, ExtensionsStepHandler)
extensions.extension_registry.register(
_ExtensionHandler("my_extension_provider", handle_my_extension, priority=60)
)
Data Flow Summary¶
See the integration diagram: framework construction applies a vertical
through the integration pipeline and registered handlers before runtime execution.
The runtime service map describes the owning services.
Cancellation-Aware Tool Discovery¶
VerticalLoader's tool-discovery path accepts an optional cooperative
cancellation token (threading.Event) so callers can abort an in-flight
entry-point scan without forcing a blocking operation to run to completion.
The token is threaded down the full call chain:
---
title: Cooperative cancellation during tool discovery
---
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#E8EFF7","primaryTextColor":"#17324D","primaryBorderColor":"#456987","lineColor":"#456987","fontFamily":"Arial"}}}%%
flowchart TB
S["discover_tool_plugins(cancel_event)"]
A["discover_tool_plugins_async(cancel_event)"]
DS["VerticalLoader.discover_tools"]
DA["VerticalLoader.discover_tools_async"]
I["_discover_tools_internal"]
E["threading.Event"]
S -->|"delegate synchronous discovery"| DS
A -->|"delegate asynchronous discovery"| DA
DS -->|"scan in caller thread"| I
DA -->|"asyncio.to_thread"| I
E -.->|"check before scan and class loading"| I
Cancellation semantics:
- The token is optional everywhere (
Noneby default), preserving full
backward compatibility for existing callers. - When
cancel_event.is_set()is observed before the entry-point scan,
the scan is skipped and the current (possibly empty) cached result is
returned without performing any entry-point I/O. - When the token is set after the entry-point scan completes but before
tool classes are loaded, loading is aborted and the partial result is
returned. This bounds the worst case to one entry-point scan. - Cancellation does not raise; it returns whatever is available so far.
Callers that need a hard-failure signal must inspectcancel_event
themselves after the call returns.
This is especially relevant for the async path, which runs the scan in a
worker thread via asyncio.to_thread — a blocking entry-point scan cannot
be interrupted by asyncio cancellation, so the cooperative token is the
only way to short-circuit it.
Key Files Reference¶
| Component | File |
|---|---|
| Pipeline | victor/framework/vertical_integration.py |
| Step Handlers | victor/framework/step_handlers.py |
| Protocols | victor/framework/protocols.py |
| Vertical Base | victor/core/verticals/base.py |
| Vertical Loader | victor/core/verticals/vertical_loader.py |
| Framework Capabilities | victor/framework/capabilities/ |
| WorkflowEngine | victor/framework/workflow_engine.py |