Mechanical only, and separated from the judgment calls that follow so the reviewable changes are not buried in a 98-file whitespace diff. 227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller modernisations. Then `ruff format` over src and tests: 98 files reformatted, 35 already conforming. No file among the unused-import findings defines __all__ or is an __init__.py, so nothing here removes a re-export. `make test`: 658 passed, unchanged from HEAD. Two things observed while verifying, neither addressed here: `pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an `e2e` marker that is not registered, and the config is strict about markers. This fails identically at HEAD, so it predates this change; `make test` passes because it ignores tests/e2e, tests/integration and tests/contracts. test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run with these changes and passed on the next, passes in isolation with them, and fails in isolation at HEAD. It is order- or timing-dependent, not a regression from this commit — established by running the full suite both ways rather than by reasoning about which change could have caused it. Co-Authored-By: Claude <noreply@anthropic.com>
130 lines
4.5 KiB
Python
130 lines
4.5 KiB
Python
"""
|
|
Steward agent schemas.
|
|
|
|
Defines the structured output models for Steward's request analysis
|
|
and capability recommendations.
|
|
"""
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class ConversationContext(BaseModel):
|
|
"""
|
|
Contextual information extracted from conversation history.
|
|
|
|
The Steward analyzes the full conversation to identify references
|
|
to previous topics, helping the Butler maintain context.
|
|
"""
|
|
|
|
has_previous_context: bool = Field(
|
|
description="Whether the current request references previous conversation turns"
|
|
)
|
|
relevant_turns: list[int] = Field(
|
|
default_factory=list,
|
|
description="0-indexed turn numbers that are relevant to the current request",
|
|
)
|
|
context_summary: str = Field(
|
|
default="", description="Brief summary of relevant context for the Butler"
|
|
)
|
|
|
|
|
|
class StewardRecommendation(BaseModel):
|
|
"""
|
|
Structured recommendation from Steward's request analysis.
|
|
|
|
This is the output format for the Steward agent, providing:
|
|
- Which household capabilities are needed
|
|
- Why those capabilities were chosen
|
|
- Complexity assessment
|
|
- Conversation context
|
|
- Missing capabilities (if any)
|
|
"""
|
|
|
|
recommended_capabilities: list[str] = Field(
|
|
description="List of household member names to include (e.g., ['tatlock_core'])"
|
|
)
|
|
reasoning: str = Field(description="Explanation of why these capabilities were recommended")
|
|
estimated_complexity: Literal["simple", "moderate", "complex"] = Field(
|
|
description="Complexity assessment: simple (1 tool), moderate (2-3 tools), complex (multiple tools/steps)"
|
|
)
|
|
conversation_context: ConversationContext = Field(
|
|
description="Contextual information from conversation history"
|
|
)
|
|
missing_capabilities: str | None = Field(
|
|
default=None,
|
|
description="Description of capabilities that would be helpful but aren't available",
|
|
)
|
|
memory_context: dict[str, Any] = Field(
|
|
default_factory=dict,
|
|
description="Pre-fetched user context from memory (profile, preferences)",
|
|
)
|
|
enriched_query: str = Field(
|
|
default="",
|
|
description="User query with auto-filled context (location, timezone) when not specified",
|
|
)
|
|
|
|
def format_for_butler(self) -> str:
|
|
"""
|
|
Format recommendation as a note for the Butler.
|
|
|
|
Returns:
|
|
Formatted string suitable for prepending to user request
|
|
"""
|
|
lines = []
|
|
|
|
# Header
|
|
lines.append("📋 Steward's Analysis")
|
|
lines.append("=" * 40)
|
|
|
|
# Complexity
|
|
lines.append(f"Complexity: {self.estimated_complexity.upper()}")
|
|
|
|
# Recommended capabilities
|
|
if self.recommended_capabilities:
|
|
caps = ", ".join(self.recommended_capabilities)
|
|
lines.append(f"Recommended tools: {caps}")
|
|
else:
|
|
lines.append("Recommended tools: None (conversational response)")
|
|
|
|
# Context summary
|
|
if self.conversation_context.has_previous_context:
|
|
lines.append(f"Context: {self.conversation_context.context_summary}")
|
|
|
|
# Missing capabilities warning
|
|
if self.missing_capabilities:
|
|
lines.append(f"⚠️ Missing: {self.missing_capabilities}")
|
|
|
|
# Memory context (user profile and preferences)
|
|
if self.memory_context:
|
|
profile = self.memory_context.get("profile", {})
|
|
preferences = self.memory_context.get("preferences", {})
|
|
|
|
if profile or preferences:
|
|
lines.append("-" * 40)
|
|
lines.append("User Context:")
|
|
|
|
if profile:
|
|
for key, value in profile.items():
|
|
lines.append(f" • {key}: {value}")
|
|
|
|
if preferences:
|
|
prefs_str = ", ".join(f"{k}={v}" for k, v in preferences.items())
|
|
lines.append(f" • preferences: {prefs_str}")
|
|
|
|
# Add delegation instructions when expert agents are recommended
|
|
delegation_agents = [
|
|
c for c in self.recommended_capabilities if c in ("biographer", "librarian")
|
|
]
|
|
if delegation_agents:
|
|
lines.append("-" * 40)
|
|
lines.append("DELEGATION REQUIRED:")
|
|
for agent in delegation_agents:
|
|
lines.append(f' Call: delegate_to_{agent}(task="[user request]")')
|
|
lines.append(f' Or output: [DELEGATE:{agent}] task="[user request]"')
|
|
|
|
lines.append("=" * 40)
|
|
|
|
return "\n".join(lines)
|