226 lines
13 KiB
Python
226 lines
13 KiB
Python
"""Specialist agent capability registry for PMFlow AI operating runs."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Iterable
|
||
|
||
from ...schemas.pmflow_ai_operating_runtime import (
|
||
PMFlowAIAgentAssignment,
|
||
PMFlowAIAgentCapabilityContract,
|
||
PMFlowAIOperatingMission,
|
||
)
|
||
|
||
|
||
class PMFlowAIOperatingAgentRegistry:
|
||
"""Registry of specialist operating agents.
|
||
|
||
The registry is a capability contract surface. It is intentionally not a
|
||
keyword router: selection is driven by mission contract and project scope,
|
||
not by matching user text.
|
||
"""
|
||
|
||
def __init__(self, contracts: Iterable[PMFlowAIAgentCapabilityContract] | None = None) -> None:
|
||
self._contracts = {contract.agent_id: contract for contract in (contracts or _default_contracts())}
|
||
|
||
def list_contracts(self) -> list[PMFlowAIAgentCapabilityContract]:
|
||
return list(self._contracts.values())
|
||
|
||
def assignments_for_mission(self, mission: PMFlowAIOperatingMission) -> list[PMFlowAIAgentAssignment]:
|
||
requested = [agent_id for agent_id in mission.required_agents if agent_id in self._contracts]
|
||
agent_ids = requested or [contract.agent_id for contract in self.list_contracts() if contract.agent_id in _CORE_STEP1_AGENT_IDS]
|
||
input_refs = ["operating_mission", "project_world_state_packet", "evidence_ledger", "learning_ledger"]
|
||
assignments: list[PMFlowAIAgentAssignment] = []
|
||
for agent_id in agent_ids:
|
||
contract = self._contracts.get(agent_id)
|
||
if contract is None:
|
||
continue
|
||
assignments.append(
|
||
PMFlowAIAgentAssignment(
|
||
agent_id=contract.agent_id,
|
||
role_goal=_role_goal(contract=contract, mission=mission),
|
||
input_refs=input_refs,
|
||
expected_work_product=_expected_work_product(contract.agent_id),
|
||
autonomy_limit=contract.autonomy_limit,
|
||
)
|
||
)
|
||
return assignments
|
||
|
||
def contract_for(self, agent_id: str) -> PMFlowAIAgentCapabilityContract | None:
|
||
return self._contracts.get(str(agent_id or "").strip())
|
||
|
||
|
||
_CORE_STEP1_AGENT_IDS = {
|
||
"idea_incubation_agent",
|
||
"market_research_agent",
|
||
"competitor_agent",
|
||
"product_strategy_agent",
|
||
"mvp_scope_agent",
|
||
"tech_architect_agent",
|
||
"pm_breakdown_agent",
|
||
"qa_risk_agent",
|
||
"growth_agent",
|
||
"monetization_agent",
|
||
"learning_agent",
|
||
}
|
||
|
||
def _default_contracts() -> list[PMFlowAIAgentCapabilityContract]:
|
||
return [
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="idea_incubation_agent",
|
||
display_name="Idea Incubation Agent",
|
||
responsibility="Compress raw ideas into target users, problem hypotheses, value hypotheses and first validation cuts.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "LearningLedger"],
|
||
output_contract=["IdeaSeed", "problem_hypotheses", "value_hypotheses", "first_validation_cut"],
|
||
allowed_tools=["read_learning_ledger", "read_research_workspace"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["hypotheses must be marked as assumptions unless evidence-backed", "must identify first falsifiable test"],
|
||
customer_safe_projection={"idea_seed": True, "hypotheses": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="market_research_agent",
|
||
display_name="Market Research Agent",
|
||
responsibility="Collect public market, trend, demand and timing evidence and turn it into a bounded research note.",
|
||
input_contract=["OperatingMission", "external evidence ledger", "LearningLedger"],
|
||
output_contract=["ResearchNote", "market_claims", "source_boundaries", "research_gaps"],
|
||
allowed_tools=["external_search", "inspect_source_detail", "read_learning_ledger"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["market claims require evidence refs or explicit boundary", "must propose recovery when sources are weak"],
|
||
customer_safe_projection={"research_note": True, "source_boundaries": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="competitor_agent",
|
||
display_name="Competitor Agent",
|
||
responsibility="Identify comparable products, substitutes and positioning pressure without inventing competitive facts.",
|
||
input_contract=["OperatingMission", "external evidence ledger", "ProjectWorldStatePacket"],
|
||
output_contract=["CompetitorMap", "substitutes", "differentiation_gaps"],
|
||
allowed_tools=["external_search", "inspect_source_detail"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["competitor facts require source refs", "must separate substitutes from direct competitors"],
|
||
customer_safe_projection={"competitor_map": True, "positioning_gaps": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="product_strategy_agent",
|
||
display_name="Product Strategy Agent",
|
||
responsibility="Convert evidence and project state into positioning, MVP direction, tradeoffs and opportunity decision.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "ResearchNote", "LearningLedger"],
|
||
output_contract=["OpportunityBrief", "ProductHypothesis", "positioning_options", "highest_leverage_move"],
|
||
allowed_tools=["read_project_world_state", "read_learning_ledger", "read_research_workspace"],
|
||
write_permissions=["prepare_object_only"],
|
||
autonomy_limit="prepare_changes",
|
||
critic_checks=["strategy must tie to mission outcome", "must separate assumptions from verified evidence"],
|
||
customer_safe_projection={"opportunity_brief": True, "product_hypothesis": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="mvp_scope_agent",
|
||
display_name="MVP Scope Agent",
|
||
responsibility="Turn strategy into MVP options, requirement boundaries and out-of-scope decisions.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "OpportunityBrief"],
|
||
output_contract=["MVPOption", "RequirementDraft candidates", "out_of_scope_now"],
|
||
allowed_tools=["read_project_world_state", "prepare_requirement_draft"],
|
||
write_permissions=["prepare_change_only"],
|
||
autonomy_limit="prepare_changes",
|
||
critic_checks=["requirement drafts require confirmation", "scope must include acceptance criteria"],
|
||
customer_safe_projection={"mvp_options": True, "requirement_candidates": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="tech_architect_agent",
|
||
display_name="Tech Architect Agent",
|
||
responsibility="Review technical path, architecture risk, dependencies, cost and handoff readiness.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "ExecutionContext"],
|
||
output_contract=["TechnicalArchitectureAssessment", "technical_bottlenecks", "handoff_readiness"],
|
||
allowed_tools=["read_project_world_state", "read_execution_summary"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["must separate readiness from execution", "must not claim code execution"],
|
||
customer_safe_projection={"technical_risks": True, "handoff_readiness": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="pm_breakdown_agent",
|
||
display_name="PM Breakdown Agent",
|
||
responsibility="Compile next-cycle tasks, priority changes, dependencies, blockers and execution package candidates.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "specialist work products"],
|
||
output_contract=["TaskDraft", "ExecutionPlan", "next_cycle_plan", "prepared_task_changes"],
|
||
allowed_tools=["read_project_world_state", "prepare_task_change", "prepare_execution_package"],
|
||
write_permissions=["prepare_change_only", "prepare_handoff_only"],
|
||
autonomy_limit="prepare_changes",
|
||
critic_checks=["task changes require confirmation", "blocked dependencies must be explicit"],
|
||
customer_safe_projection={"next_cycle_plan": True, "prepared_actions": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="qa_risk_agent",
|
||
display_name="QA Risk Agent",
|
||
responsibility="Review privacy, compliance, safety, authorization and execution risks; veto unsafe prepared actions.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "OperatingPlan draft"],
|
||
output_contract=["QAPlan", "RiskReview", "blocked_actions", "confirmation_requirements"],
|
||
allowed_tools=["read_project_world_state", "read_policy_boundary"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["high-risk action must be blocked or require confirmation", "risk veto must be visible"],
|
||
customer_safe_projection={"blocked_actions": True, "confirmation_requirements": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="growth_agent",
|
||
display_name="Growth Agent",
|
||
responsibility="Translate opportunity or project state into channel, launch, feedback and growth experiment options.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "ResearchNote", "ReleaseSignals"],
|
||
output_contract=["GrowthExperiment", "ReleaseSignal candidates", "feedback_plan"],
|
||
allowed_tools=["read_project_world_state", "read_release_growth_signals", "external_search"],
|
||
write_permissions=["prepare_experiment_only"],
|
||
autonomy_limit="prepare_changes",
|
||
critic_checks=["growth experiments need success criteria", "must not claim launch execution"],
|
||
customer_safe_projection={"growth_experiments": True, "feedback_plan": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="monetization_agent",
|
||
display_name="Monetization Agent",
|
||
responsibility="Assess pricing, cost, revenue path and commercial viability signals.",
|
||
input_contract=["OperatingMission", "ProjectWorldStatePacket", "market evidence", "LearningLedger"],
|
||
output_contract=["RevenueSignal", "pricing_hypotheses", "unit_economics_gaps"],
|
||
allowed_tools=["external_search", "read_project_world_state", "read_learning_ledger"],
|
||
write_permissions=[],
|
||
autonomy_limit="propose_plan",
|
||
critic_checks=["commercial claims require evidence or assumption marker", "must identify cost/revenue gaps"],
|
||
customer_safe_projection={"revenue_signals": True, "pricing_hypotheses": True, "internal_reasoning": False},
|
||
),
|
||
PMFlowAIAgentCapabilityContract(
|
||
agent_id="learning_agent",
|
||
display_name="Learning Agent",
|
||
responsibility="Extract accepted/rejected decisions, useful source patterns, user preference signals and next-cycle triggers.",
|
||
input_contract=["OperatingMission", "OutcomeObservations", "LearningLedger"],
|
||
output_contract=["LearningUpdate", "next_cycle_policy", "memory_candidates"],
|
||
allowed_tools=["read_learning_ledger", "observe_outcome"],
|
||
write_permissions=["prepare_learning_only"],
|
||
autonomy_limit="prepare_changes",
|
||
critic_checks=["learning memory writeback requires explicit policy", "must not store raw transcript as preference"],
|
||
customer_safe_projection={"learning_updates": True, "next_cycle": True, "internal_reasoning": False},
|
||
),
|
||
]
|
||
|
||
|
||
def _role_goal(*, contract: PMFlowAIAgentCapabilityContract, mission: PMFlowAIOperatingMission) -> str:
|
||
goal = mission.mission_goal or "推进当前 PMFlow operating mission"
|
||
return f"{contract.display_name} 围绕本轮 mission 产出结构化 { _expected_work_product(contract.agent_id) }:{goal}"
|
||
|
||
|
||
def _expected_work_product(agent_id: str) -> str:
|
||
mapping = {
|
||
"idea_incubation_agent": "IdeaSeed",
|
||
"market_research_agent": "ResearchNote",
|
||
"competitor_agent": "CompetitorMap",
|
||
"product_strategy_agent": "OpportunityBrief",
|
||
"mvp_scope_agent": "MVPOption",
|
||
"tech_architect_agent": "TechnicalArchitectureAssessment",
|
||
"pm_breakdown_agent": "TaskDraft",
|
||
"qa_risk_agent": "QAPlan",
|
||
"growth_agent": "GrowthExperiment",
|
||
"monetization_agent": "RevenueSignal",
|
||
"learning_agent": "LearningUpdate",
|
||
}
|
||
return mapping.get(agent_id, "AgentWorkProduct")
|
||
|
||
|
||
__all__ = ["PMFlowAIOperatingAgentRegistry"]
|