mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-14 13:06:30 +03:00
Instead of the compose phase fabricating personas with fake backgrounds, the council now draws from 39 real professional profiles via a git submodule (https://github.com/magnus919/hermes-profiles). Key changes: - New select phase reads SOUL.md + profile.yaml from profiles submodule - Auto-updates submodule before selection via git submodule update --remote - --profiles flag for explicit selection (comma-separated names) - Auto-selection by keyword overlap with profile descriptions when omitted - Each agent's identity is their real SOUL.md — actual methodology, values, and operating principles, not invented backgrounds - Falls back to composed personas if profile library is unavailable - Real profiles produce genuine methodological disagreement (debugger said 'unanswerable without a verified process' to naming question) Signed-off-by: Magnus Hedemark <magnus919@pm.me>
187 lines
6.2 KiB
Python
187 lines
6.2 KiB
Python
"""Typed state model and phase output schemas."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# ── Profile info (from submoduled hermes-profiles) ──
|
|
|
|
|
|
@dataclass
|
|
class ProfileInfo:
|
|
"""A real profile drawn from the hermes-profiles library.
|
|
|
|
Used in place of fabricated AgentPersona when profiles are available.
|
|
The name is the profile directory name (e.g. 'debugger').
|
|
The soul_content is the full identity document.
|
|
"""
|
|
name: str
|
|
description: str
|
|
soul_content: str
|
|
|
|
|
|
# ── Phase output schemas (validated Pydantic models) ──
|
|
|
|
|
|
class AgentPersona(BaseModel):
|
|
"""Profile for a single debate agent."""
|
|
|
|
name: str
|
|
background: str = Field(description="One-paragraph career background")
|
|
expertise: str = Field(description="Specific domain expertise")
|
|
approach: str = Field(description="Analytical approach they bring")
|
|
bias: str = Field(description="What experience or bias they bring to THIS question")
|
|
|
|
|
|
class Premortem(BaseModel):
|
|
"""Pre-mortem: agent envisions how the decision already failed."""
|
|
|
|
agent_name: str
|
|
failure_scenario: str = Field(description="Narrative of how the decision failed")
|
|
root_causes: list[str] = Field(description="What went wrong")
|
|
early_warning_signals: list[str] = Field(description="What to watch for")
|
|
|
|
|
|
class Position(BaseModel):
|
|
"""Agent's initial position on the question."""
|
|
|
|
agent_name: str
|
|
stance: str = Field(description="Position on the question")
|
|
reasoning: list[str] = Field(description="Chain of reasoning")
|
|
confidence: float = Field(ge=0, le=1, description="Confidence in this position")
|
|
key_assumptions: list[str] = Field(description="Assumptions that must hold")
|
|
|
|
|
|
class CrossExamination(BaseModel):
|
|
"""Agent's response after reading all other positions."""
|
|
|
|
agent_name: str
|
|
concessions: list[str] = Field(description="Points where the agent conceded or shifted")
|
|
remaining_disagreements: list[str] = Field(description="Points still in dispute")
|
|
updated_position: str | None = Field(
|
|
default=None, description="Revised position, if changed"
|
|
)
|
|
updated_confidence: float | None = Field(
|
|
default=None, ge=0, le=1, description="Updated confidence, if changed"
|
|
)
|
|
reflection: str = Field(
|
|
description="What the agent learned from other perspectives"
|
|
)
|
|
new_evidence_needed: list[str] = Field(
|
|
default_factory=list,
|
|
description="What evidence would close remaining gaps",
|
|
)
|
|
|
|
|
|
class RiskVector(BaseModel):
|
|
"""A risk identified during the debate, with position-relative context."""
|
|
|
|
description: str
|
|
agents_who_flagged: list[str]
|
|
severity: Literal["low", "medium", "high"]
|
|
phase_discovered: Literal["premortem", "position", "cross_examine"] = Field(
|
|
description="Which phase first surfaced this risk. "
|
|
"Premortem risks are seen BEFORE positional commitment."
|
|
)
|
|
|
|
|
|
class RoundMetrics(BaseModel):
|
|
"""Convergence metrics for a single cross-examination round."""
|
|
|
|
round: int
|
|
mean_confidence: float
|
|
dispersion: float = Field(description="Standard deviation of agent confidences")
|
|
new_arguments: int = Field(description="Arguments not seen in prior rounds")
|
|
concessions_made: int
|
|
stopped_early: bool = Field(
|
|
default=False,
|
|
description="True if this round was cut short by convergence detection",
|
|
)
|
|
|
|
|
|
class Disagreement(BaseModel):
|
|
"""A point of genuine disagreement that survived cross-examination."""
|
|
|
|
topic: str
|
|
positions: dict[str, str] = Field(
|
|
description="Agent name -> summary of their position on this topic"
|
|
)
|
|
unresolved: bool = Field(
|
|
default=True,
|
|
description="Whether this disagreement persisted after all rounds",
|
|
)
|
|
|
|
|
|
class Synthesis(BaseModel):
|
|
"""Structured output of a completed council debate."""
|
|
|
|
# Metadata
|
|
question: str
|
|
mode: str
|
|
num_agents: int
|
|
rounds_completed: int
|
|
stopped_reason: Literal[
|
|
"converged",
|
|
"max_rounds",
|
|
"diminishing_returns",
|
|
"genuine_disagreement",
|
|
] = Field(description="Why the debate stopped")
|
|
|
|
# Convergence diagnostics
|
|
confidence_history: list[RoundMetrics] = Field(
|
|
description="One entry per cross-examination round"
|
|
)
|
|
final_dispersion: float
|
|
mean_confidence_delta: float = Field(
|
|
description="Change in mean confidence from first to last round"
|
|
)
|
|
|
|
# Content: premortem phase (pre-positional)
|
|
shared_risks: list[RiskVector] = Field(
|
|
description="Risks identified during pre-mortem before any agent "
|
|
"formed a position. Compare with shared_concerns to see which "
|
|
"worries survived cross-examination."
|
|
)
|
|
|
|
# Content: cross-examination phase (post-positional)
|
|
shared_concerns: list[str] = Field(
|
|
description="Concerns that survived cross-examination and are shared "
|
|
"across agents. A risk in shared_risks that also appears here was "
|
|
"confirmed by debate. A risk in shared_risks absent here is either "
|
|
"resolved or buried by positional commitment."
|
|
)
|
|
disagreements: list[Disagreement]
|
|
assumptions_per_position: dict[str, list[str]] = Field(
|
|
description="Agent name -> assumptions that would need to hold "
|
|
"for their position to be correct"
|
|
)
|
|
risk_vectors: list[RiskVector]
|
|
principal_path: str = Field(description="Narrative synthesis of the decision landscape")
|
|
|
|
|
|
# ── Orchestration state (mutable dataclass) ──
|
|
|
|
|
|
@dataclass
|
|
class CouncilState:
|
|
"""Mutable state that flows through the debate graph."""
|
|
|
|
question: str
|
|
mode: str = "medium"
|
|
max_rounds: int = 4
|
|
convergence_threshold: float = 0.10
|
|
|
|
personas: list[AgentPersona] = field(default_factory=list)
|
|
profiles: list[ProfileInfo] = field(default_factory=list)
|
|
"""Real profiles from the hermes-profiles library, if available.
|
|
Takes priority over fabricated personas when present."""
|
|
premortems: dict[str, Premortem] = field(default_factory=dict)
|
|
positions: dict[str, Position] = field(default_factory=dict)
|
|
cross_examination_rounds: list[dict[str, CrossExamination]] = field(
|
|
default_factory=list
|
|
)
|
|
synthesis: Synthesis | None = None
|
|
round_number: int = 0
|