2026-08-16 18:12:50 +07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from agents.crypto_venture import CryptoVentureService
|
|
|
|
|
from control_plane.ventures.models import VentureCohort
|
|
|
|
|
from graph.native_runtime import GraphExecutionContext
|
|
|
|
|
from graph.registry import NodeHandlerRegistry, NodeResult
|
|
|
|
|
from graph.spec import ExecutionGraphSpec, GraphEdgeSpec, GraphNodeSpec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def crypto_venture_cohort_graph_v1() -> ExecutionGraphSpec:
|
|
|
|
|
nodes = [
|
|
|
|
|
"prepare_crypto_mandate",
|
|
|
|
|
"portfolio_crypto_thesis_review",
|
|
|
|
|
"generate_independent_protocols",
|
2026-08-16 21:00:15 +07:00
|
|
|
"onchain_necessity_gate",
|
2026-08-16 18:12:50 +07:00
|
|
|
"token_necessity_gate",
|
|
|
|
|
"novelty_gate",
|
|
|
|
|
"regenerate_rejected_slots",
|
|
|
|
|
"light_market_research",
|
|
|
|
|
"protocol_research",
|
|
|
|
|
"protocol_security_gate",
|
|
|
|
|
"token_red_team",
|
|
|
|
|
"tokenomics_simulation",
|
|
|
|
|
"crypto_ic_first_pass",
|
|
|
|
|
"top5_deep_research",
|
|
|
|
|
"rescore",
|
|
|
|
|
"portfolio_crypto_ic",
|
|
|
|
|
"regulatory_gate",
|
|
|
|
|
"capability_analysis",
|
|
|
|
|
"update_crypto_thesis_registry",
|
|
|
|
|
"produce_crypto_cohort_report",
|
|
|
|
|
"complete",
|
|
|
|
|
]
|
2026-08-16 21:56:09 +07:00
|
|
|
spec = ExecutionGraphSpec(name="crypto_venture_cohort", version=4, graph_type="CRYPTO_VENTURE_COHORT", entry="prepare_crypto_mandate", nodes={node: GraphNodeSpec(node, node if node == "complete" else f"crypto_venture_{node}") for node in nodes}, edges=[GraphEdgeSpec(nodes[index], nodes[index + 1], "success") for index in range(len(nodes) - 1)], terminal_nodes=["complete"], metadata={"description": "Crypto / Protocol Venture Cohort V0.3.1: Sol-native problem-first ideation, repaired stage ordering, structural native-token utility judge, primary-source-first research."})
|
2026-08-16 18:12:50 +07:00
|
|
|
spec.validate()
|
|
|
|
|
return spec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CryptoCohortNode:
|
|
|
|
|
idempotent = True
|
|
|
|
|
replay_safe = True
|
|
|
|
|
destructive = False
|
|
|
|
|
|
|
|
|
|
def __init__(self, service: CryptoVentureService, node_type: str, *, cohort_size: int = 10, concurrency: int = 2) -> None:
|
|
|
|
|
self.service = service
|
|
|
|
|
self.node_type = node_type
|
|
|
|
|
self.cohort_size = cohort_size
|
|
|
|
|
self.concurrency = concurrency
|
|
|
|
|
|
|
|
|
|
def cohort(self, context: GraphExecutionContext) -> VentureCohort:
|
|
|
|
|
return VentureCohort.objects.get(id=context.graph_run.metadata["cohort_id"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PrepareCryptoMandateNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
cohort = self.service.prepare_cohort(size=self.cohort_size, graph_run=context.graph_run, concurrency=self.concurrency)
|
|
|
|
|
context.graph_run.metadata = {**context.graph_run.metadata, "cohort_id": str(cohort.id), "cohort_stable_id": cohort.cohort_id, "no_real_spend": True, "no_user_contact": True, "no_token_sale": True, "no_fundraising": True, "no_mainnet_issuance": True}
|
|
|
|
|
context.graph_run.save(update_fields=["metadata", "updated_at"])
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"cohort_id": cohort.cohort_id, "size": cohort.cohort_size})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GenerateProtocolsNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
proposals = self.service.generate_protocols(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"proposal_count": len(proposals)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TokenNecessityNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
assessments = self.service.token_necessity_gate(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"assessment_count": len(assessments)})
|
|
|
|
|
|
|
|
|
|
|
2026-08-16 21:00:15 +07:00
|
|
|
class OnchainNecessityNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
assessments = self.service.onchain_necessity_gate(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"assessment_count": len(assessments)})
|
|
|
|
|
|
|
|
|
|
|
2026-08-16 18:12:50 +07:00
|
|
|
class NoveltyGateNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
return NodeResult("COMPLETE", "success", self.service.novelty_gate(self.cohort(context)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegenerateRejectedSlotsNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
created = self.service.regenerate_rejected_slots(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"regenerated": len(created)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LightResearchNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
cohort = self.cohort(context)
|
|
|
|
|
self.service.light_market_research(cohort)
|
|
|
|
|
return NodeResult("COMPLETE", "success", cohort.metrics)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProtocolResearchNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
self.service.protocol_research(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProtocolSecurityGateNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
return NodeResult("COMPLETE", "success", self.service.protocol_security_gate(self.cohort(context)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TokenRedTeamNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
assessments = self.service.token_red_team(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"red_team_count": len(assessments)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TokenomicsSimulationNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
simulations = self.service.tokenomics_simulation(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"simulation_count": len(simulations)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CryptoICFirstPassNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
self.service.crypto_ic_first_pass(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Top5DeepResearchNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
self.service.top5_deep_research(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PortfolioCryptoICNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
review = self.service.portfolio_crypto_ic(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"ranked_count": len(review.rankings), "top_3": review.top_3})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegulatoryGateNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
self.service.regulatory_gate(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CapabilityAnalysisNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
gaps = self.service.capability_analysis(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"capability_gap_count": len(gaps)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RegistryUpdateNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
updates = self.service.update_crypto_thesis_registry(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"update_count": len(updates)})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReportNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
artifact = self.service.produce_crypto_cohort_report(self.cohort(context))
|
|
|
|
|
return NodeResult("COMPLETE", "success", {"artifact_id": str(artifact.id), "artifact_type": artifact.artifact_type})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NoopNode(CryptoCohortNode):
|
|
|
|
|
def run(self, context: GraphExecutionContext) -> NodeResult:
|
|
|
|
|
return NodeResult("COMPLETE", "success")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def crypto_venture_cohort_registry(service: CryptoVentureService, *, cohort_size: int = 10, concurrency: int = 2) -> NodeHandlerRegistry:
|
|
|
|
|
registry = NodeHandlerRegistry()
|
|
|
|
|
handlers = [
|
|
|
|
|
PrepareCryptoMandateNode(service, "crypto_venture_prepare_crypto_mandate", cohort_size=cohort_size, concurrency=concurrency),
|
|
|
|
|
NoopNode(service, "crypto_venture_portfolio_crypto_thesis_review"),
|
|
|
|
|
GenerateProtocolsNode(service, "crypto_venture_generate_independent_protocols"),
|
2026-08-16 21:00:15 +07:00
|
|
|
OnchainNecessityNode(service, "crypto_venture_onchain_necessity_gate"),
|
2026-08-16 18:12:50 +07:00
|
|
|
TokenNecessityNode(service, "crypto_venture_token_necessity_gate"),
|
|
|
|
|
NoveltyGateNode(service, "crypto_venture_novelty_gate"),
|
|
|
|
|
RegenerateRejectedSlotsNode(service, "crypto_venture_regenerate_rejected_slots"),
|
|
|
|
|
LightResearchNode(service, "crypto_venture_light_market_research"),
|
|
|
|
|
ProtocolResearchNode(service, "crypto_venture_protocol_research"),
|
|
|
|
|
ProtocolSecurityGateNode(service, "crypto_venture_protocol_security_gate"),
|
|
|
|
|
TokenRedTeamNode(service, "crypto_venture_token_red_team"),
|
|
|
|
|
TokenomicsSimulationNode(service, "crypto_venture_tokenomics_simulation"),
|
|
|
|
|
CryptoICFirstPassNode(service, "crypto_venture_crypto_ic_first_pass"),
|
|
|
|
|
Top5DeepResearchNode(service, "crypto_venture_top5_deep_research"),
|
|
|
|
|
CryptoICFirstPassNode(service, "crypto_venture_rescore"),
|
|
|
|
|
PortfolioCryptoICNode(service, "crypto_venture_portfolio_crypto_ic"),
|
|
|
|
|
RegulatoryGateNode(service, "crypto_venture_regulatory_gate"),
|
|
|
|
|
CapabilityAnalysisNode(service, "crypto_venture_capability_analysis"),
|
|
|
|
|
RegistryUpdateNode(service, "crypto_venture_update_crypto_thesis_registry"),
|
|
|
|
|
ReportNode(service, "crypto_venture_produce_crypto_cohort_report"),
|
|
|
|
|
]
|
|
|
|
|
for handler in handlers:
|
|
|
|
|
registry.register(handler)
|
|
|
|
|
return registry
|