Complete second-pass refinement integrating all wizard house contributions: **Three-House Architecture:** - Ezra (Archivist): Read-before-write, evidence over vibes, citation discipline - Bezalel (Artificer): Build-from-plans, proof over speculation, test discipline - Timmy (Sovereign): Final judgment, telemetry, sovereignty preservation **Core Components:** - harness.py: House-aware execution with policy enforcement - router.py: Intelligent task routing to appropriate house - task_router_daemon.py: Full three-house Gitea workflow - tests/test_v2.py: Comprehensive test suite **Key Features:** - Provenance tracking with content hashing - House-specific policy enforcement - Sovereignty telemetry logging - Cross-house workflow orchestration - Evidence-level tracking per execution Honors canon from specs/timmy-ezra-bezalel-canon-sheet.md: - Distinct house identities - No authority blending - Artifact-flow unidirectional - Full provenance and telemetry
385 lines
13 KiB
Python
385 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Uni-Wizard Router v2 — Intelligent delegation across the three houses
|
|
|
|
Routes tasks to the appropriate house based on task characteristics:
|
|
- READ/ARCHIVE tasks → Ezra (archivist)
|
|
- BUILD/TEST tasks → Bezalel (artificer)
|
|
- JUDGE/REVIEW tasks → Timmy (sovereign)
|
|
|
|
Usage:
|
|
router = HouseRouter()
|
|
result = router.route("read_and_summarize", {"repo": "timmy-home"})
|
|
"""
|
|
|
|
import json
|
|
from typing import Dict, Any, Optional, List
|
|
from pathlib import Path
|
|
from dataclasses import dataclass
|
|
from enum import Enum
|
|
|
|
from harness import UniWizardHarness, House, ExecutionResult
|
|
|
|
|
|
class TaskType(Enum):
|
|
"""Categories of work for routing decisions"""
|
|
READ = "read" # Read, analyze, summarize
|
|
ARCHIVE = "archive" # Store, catalog, preserve
|
|
SYNTHESIZE = "synthesize" # Combine, reconcile, interpret
|
|
BUILD = "build" # Implement, create, construct
|
|
TEST = "test" # Verify, validate, benchmark
|
|
OPTIMIZE = "optimize" # Tune, improve, harden
|
|
JUDGE = "judge" # Review, decide, approve
|
|
ROUTE = "route" # Delegate, coordinate, dispatch
|
|
|
|
|
|
@dataclass
|
|
class RoutingDecision:
|
|
"""Record of why a task was routed to a house"""
|
|
task_type: str
|
|
primary_house: str
|
|
confidence: float
|
|
reasoning: str
|
|
fallback_houses: List[str]
|
|
|
|
|
|
class HouseRouter:
|
|
"""
|
|
Routes tasks to the appropriate wizard house.
|
|
|
|
The router understands the canon:
|
|
- Ezra reads and orders the pattern
|
|
- Bezalel builds and unfolds the pattern
|
|
- Timmy judges and preserves sovereignty
|
|
"""
|
|
|
|
# Task → House mapping
|
|
ROUTING_TABLE = {
|
|
# Read/Archive tasks → Ezra
|
|
TaskType.READ: {
|
|
"house": House.EZRA,
|
|
"confidence": 0.95,
|
|
"reasoning": "Archivist house: reading is Ezra's domain"
|
|
},
|
|
TaskType.ARCHIVE: {
|
|
"house": House.EZRA,
|
|
"confidence": 0.95,
|
|
"reasoning": "Archivist house: preservation is Ezra's domain"
|
|
},
|
|
TaskType.SYNTHESIZE: {
|
|
"house": House.EZRA,
|
|
"confidence": 0.85,
|
|
"reasoning": "Archivist house: synthesis requires reading first"
|
|
},
|
|
|
|
# Build/Test tasks → Bezalel
|
|
TaskType.BUILD: {
|
|
"house": House.BEZALEL,
|
|
"confidence": 0.95,
|
|
"reasoning": "Artificer house: building is Bezalel's domain"
|
|
},
|
|
TaskType.TEST: {
|
|
"house": House.BEZALEL,
|
|
"confidence": 0.95,
|
|
"reasoning": "Artificer house: verification is Bezalel's domain"
|
|
},
|
|
TaskType.OPTIMIZE: {
|
|
"house": House.BEZALEL,
|
|
"confidence": 0.90,
|
|
"reasoning": "Artificer house: optimization is Bezalel's domain"
|
|
},
|
|
|
|
# Judge/Route tasks → Timmy
|
|
TaskType.JUDGE: {
|
|
"house": House.TIMMY,
|
|
"confidence": 1.0,
|
|
"reasoning": "Sovereign house: judgment is Timmy's domain"
|
|
},
|
|
TaskType.ROUTE: {
|
|
"house": House.TIMMY,
|
|
"confidence": 0.95,
|
|
"reasoning": "Sovereign house: routing is Timmy's domain"
|
|
},
|
|
}
|
|
|
|
# Tool → TaskType mapping
|
|
TOOL_TASK_MAP = {
|
|
# System tools
|
|
"system_info": TaskType.READ,
|
|
"process_list": TaskType.READ,
|
|
"service_status": TaskType.READ,
|
|
"service_control": TaskType.BUILD,
|
|
"health_check": TaskType.TEST,
|
|
"disk_usage": TaskType.READ,
|
|
|
|
# Git tools
|
|
"git_status": TaskType.READ,
|
|
"git_log": TaskType.ARCHIVE,
|
|
"git_pull": TaskType.BUILD,
|
|
"git_commit": TaskType.ARCHIVE,
|
|
"git_push": TaskType.BUILD,
|
|
"git_checkout": TaskType.BUILD,
|
|
"git_branch_list": TaskType.READ,
|
|
|
|
# Network tools
|
|
"http_get": TaskType.READ,
|
|
"http_post": TaskType.BUILD,
|
|
"gitea_list_issues": TaskType.READ,
|
|
"gitea_get_issue": TaskType.READ,
|
|
"gitea_create_issue": TaskType.BUILD,
|
|
"gitea_comment": TaskType.BUILD,
|
|
}
|
|
|
|
def __init__(self):
|
|
self.harnesses: Dict[House, UniWizardHarness] = {
|
|
House.TIMMY: UniWizardHarness("timmy"),
|
|
House.EZRA: UniWizardHarness("ezra"),
|
|
House.BEZALEL: UniWizardHarness("bezalel")
|
|
}
|
|
self.decision_log: List[RoutingDecision] = []
|
|
|
|
def classify_task(self, tool_name: str, params: Dict) -> TaskType:
|
|
"""Classify a task based on tool and parameters"""
|
|
# Direct tool mapping
|
|
if tool_name in self.TOOL_TASK_MAP:
|
|
return self.TOOL_TASK_MAP[tool_name]
|
|
|
|
# Heuristic classification
|
|
if any(kw in tool_name for kw in ["read", "get", "list", "status", "info", "log"]):
|
|
return TaskType.READ
|
|
if any(kw in tool_name for kw in ["write", "create", "commit", "push", "post"]):
|
|
return TaskType.BUILD
|
|
if any(kw in tool_name for kw in ["test", "check", "verify", "validate"]):
|
|
return TaskType.TEST
|
|
|
|
# Default to Timmy for safety
|
|
return TaskType.ROUTE
|
|
|
|
def route(self, tool_name: str, **params) -> ExecutionResult:
|
|
"""
|
|
Route a task to the appropriate house and execute.
|
|
|
|
Returns execution result with routing metadata attached.
|
|
"""
|
|
# Classify the task
|
|
task_type = self.classify_task(tool_name, params)
|
|
|
|
# Get routing decision
|
|
routing = self.ROUTING_TABLE.get(task_type, {
|
|
"house": House.TIMMY,
|
|
"confidence": 0.5,
|
|
"reasoning": "Default to sovereign house"
|
|
})
|
|
|
|
house = routing["house"]
|
|
|
|
# Record decision
|
|
decision = RoutingDecision(
|
|
task_type=task_type.value,
|
|
primary_house=house.value,
|
|
confidence=routing["confidence"],
|
|
reasoning=routing["reasoning"],
|
|
fallback_houses=[h.value for h in [House.TIMMY] if h != house]
|
|
)
|
|
self.decision_log.append(decision)
|
|
|
|
# Execute via the chosen harness
|
|
harness = self.harnesses[house]
|
|
result = harness.execute(tool_name, **params)
|
|
|
|
# Attach routing metadata
|
|
result.data = {
|
|
"result": result.data,
|
|
"routing": {
|
|
"task_type": task_type.value,
|
|
"house": house.value,
|
|
"confidence": routing["confidence"],
|
|
"reasoning": routing["reasoning"]
|
|
}
|
|
}
|
|
|
|
return result
|
|
|
|
def execute_multi_house_plan(
|
|
self,
|
|
plan: List[Dict],
|
|
require_timmy_approval: bool = False
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Execute a plan that may span multiple houses.
|
|
|
|
Example plan:
|
|
[
|
|
{"tool": "git_status", "params": {}, "house": "ezra"},
|
|
{"tool": "git_commit", "params": {"message": "Update"}, "house": "ezra"},
|
|
{"tool": "git_push", "params": {}, "house": "bezalel"}
|
|
]
|
|
"""
|
|
results = {}
|
|
ezra_review = None
|
|
bezalel_proof = None
|
|
|
|
for step in plan:
|
|
tool_name = step.get("tool")
|
|
params = step.get("params", {})
|
|
specified_house = step.get("house")
|
|
|
|
# Use specified house or auto-route
|
|
if specified_house:
|
|
harness = self.harnesses[House(specified_house)]
|
|
result = harness.execute(tool_name, **params)
|
|
else:
|
|
result = self.route(tool_name, **params)
|
|
|
|
results[tool_name] = result
|
|
|
|
# Collect review/proof for Timmy
|
|
if specified_house == "ezra":
|
|
ezra_review = result
|
|
elif specified_house == "bezalel":
|
|
bezalel_proof = result
|
|
|
|
# If required, get Timmy's approval
|
|
if require_timmy_approval:
|
|
timmy_harness = self.harnesses[House.TIMMY]
|
|
|
|
# Build review package
|
|
review_input = {
|
|
"ezra_work": {
|
|
"success": ezra_review.success if ezra_review else None,
|
|
"evidence_level": ezra_review.provenance.evidence_level if ezra_review else None,
|
|
"sources": ezra_review.provenance.sources_read if ezra_review else []
|
|
},
|
|
"bezalel_work": {
|
|
"success": bezalel_proof.success if bezalel_proof else None,
|
|
"proof_verified": bezalel_proof.success if bezalel_proof else None
|
|
} if bezalel_proof else None
|
|
}
|
|
|
|
# Timmy judges
|
|
timmy_result = timmy_harness.execute(
|
|
"review_proposal",
|
|
proposal=json.dumps(review_input)
|
|
)
|
|
|
|
results["timmy_judgment"] = timmy_result
|
|
|
|
return results
|
|
|
|
def get_routing_stats(self) -> Dict:
|
|
"""Get statistics on routing decisions"""
|
|
if not self.decision_log:
|
|
return {"total": 0}
|
|
|
|
by_house = {}
|
|
by_task = {}
|
|
total_confidence = 0
|
|
|
|
for d in self.decision_log:
|
|
by_house[d.primary_house] = by_house.get(d.primary_house, 0) + 1
|
|
by_task[d.task_type] = by_task.get(d.task_type, 0) + 1
|
|
total_confidence += d.confidence
|
|
|
|
return {
|
|
"total": len(self.decision_log),
|
|
"by_house": by_house,
|
|
"by_task_type": by_task,
|
|
"avg_confidence": round(total_confidence / len(self.decision_log), 2)
|
|
}
|
|
|
|
|
|
class CrossHouseWorkflow:
|
|
"""
|
|
Pre-defined workflows that coordinate across houses.
|
|
|
|
Implements the canonical flow:
|
|
1. Ezra reads and shapes
|
|
2. Bezalel builds and proves
|
|
3. Timmy reviews and approves
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.router = HouseRouter()
|
|
|
|
def issue_to_pr_workflow(self, issue_number: int, repo: str) -> Dict:
|
|
"""
|
|
Full workflow: Issue → Ezra analysis → Bezalel implementation → Timmy review
|
|
"""
|
|
workflow_id = f"issue_{issue_number}"
|
|
|
|
# Phase 1: Ezra reads and shapes the issue
|
|
ezra_harness = self.router.harnesses[House.EZRA]
|
|
issue_data = ezra_harness.execute("gitea_get_issue", repo=repo, number=issue_number)
|
|
|
|
if not issue_data.success:
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"phase": "ezra_read",
|
|
"status": "failed",
|
|
"error": issue_data.error
|
|
}
|
|
|
|
# Phase 2: Ezra synthesizes approach
|
|
# (Would call LLM here in real implementation)
|
|
approach = {
|
|
"files_to_modify": ["file1.py", "file2.py"],
|
|
"tests_needed": True
|
|
}
|
|
|
|
# Phase 3: Bezalel implements
|
|
bezalel_harness = self.router.harnesses[House.BEZALEL]
|
|
# Execute implementation plan
|
|
|
|
# Phase 4: Bezalel proves with tests
|
|
test_result = bezalel_harness.execute("run_tests", repo_path=repo)
|
|
|
|
# Phase 5: Timmy reviews
|
|
timmy_harness = self.router.harnesses[House.TIMMY]
|
|
review = timmy_harness.review_for_timmy({
|
|
"ezra_analysis": issue_data,
|
|
"bezalel_implementation": test_result
|
|
})
|
|
|
|
return {
|
|
"workflow_id": workflow_id,
|
|
"status": "complete",
|
|
"phases": {
|
|
"ezra_read": issue_data.success,
|
|
"bezalel_implement": test_result.success,
|
|
"timmy_review": review
|
|
},
|
|
"recommendation": review.get("recommendation", "PENDING")
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("HOUSE ROUTER — Three-House Delegation Demo")
|
|
print("=" * 60)
|
|
|
|
router = HouseRouter()
|
|
|
|
# Demo routing decisions
|
|
demo_tasks = [
|
|
("git_status", {"repo_path": "/tmp/timmy-home"}),
|
|
("git_commit", {"repo_path": "/tmp/timmy-home", "message": "Test"}),
|
|
("system_info", {}),
|
|
("health_check", {}),
|
|
]
|
|
|
|
print("\n📋 Task Routing Decisions:")
|
|
print("-" * 60)
|
|
|
|
for tool, params in demo_tasks:
|
|
task_type = router.classify_task(tool, params)
|
|
routing = router.ROUTING_TABLE.get(task_type, {})
|
|
|
|
print(f"\n Tool: {tool}")
|
|
print(f" Task Type: {task_type.value}")
|
|
print(f" Routed To: {routing.get('house', House.TIMMY).value}")
|
|
print(f" Confidence: {routing.get('confidence', 0.5)}")
|
|
print(f" Reasoning: {routing.get('reasoning', 'Default')}")
|
|
|
|
print("\n" + "=" * 60)
|
|
print("Routing complete.")
|