cybertron-agents 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cybertron/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """Cybertron multi-agent configuration and orchestration primitives."""
2
+ from .schema import Message, ToolCallRequest, function_to_json_schema
3
+ from .compaction import AlphaTrionHeadTailCompaction, CompactionStrategy
4
+ from .policy import MegatronusCircuitBreaker, PermissionEffect, PermissionRule
5
+ from .feishu import FeishuWikiGovernanceConfig, FeishuWikiKnowledgeClient
6
+ from .registry import AgentMode, PrimeAgentSpec, THIRTEEN_PRIMES_REGISTRY
7
+ from .ledger import CybertronTaskLedger, LedgerStep, PrimaPlanOrchestrator
8
+ from .exporters import compile_deepseek_harness_assets, compile_opencode_v2_assets
9
+ from .selfcheck import run_self_verification
10
+ from .execution import AgentExecutor, AgentResult, AgentTask, QualityEvidence, QualityKind, ResultStatus
11
+ from .dispatch import PrimaDispatcher
@@ -0,0 +1,107 @@
1
+ """在模型上下文超预算时保留早期约束与近期消息,并保持工具调用和结果成组。"""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ from abc import abstractmethod
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Protocol, Set, Tuple, runtime_checkable
8
+ from .schema import Message
9
+
10
+ # MODULE 2: Alpha Trion Context Compaction Engine (Ref: picoagents/compaction.py)
11
+ # ============================================================================
12
+
13
+ @runtime_checkable
14
+ class CompactionStrategy(Protocol):
15
+ """Protocol for context compaction executed INSIDE the agent tool loop."""
16
+
17
+ @abstractmethod
18
+ def compact(self, messages: List[Message]) -> List[Message]:
19
+ ...
20
+
21
+
22
+ @dataclass
23
+ class AlphaTrionHeadTailCompaction:
24
+ """
25
+ Token-aware Head+Tail Compaction (Alpha Trion's Quill).
26
+ Preserves:
27
+ - Head (25% budget): System prompt, initial user requirement, and Feishu Wiki architectural/hardware constraints.
28
+ - Tail (75% budget): Most recent tool calls and results (working memory).
29
+ Crucially preserves 'atomic groups': an assistant message containing `tool_calls`
30
+ is NEVER separated from its corresponding `ToolMessage` results.
31
+ """
32
+ token_budget: int = 64_000
33
+ head_ratio: float = 0.25
34
+ compaction_count: int = field(default=0, repr=False)
35
+ total_tokens_saved: int = field(default=0, repr=False)
36
+
37
+ @staticmethod
38
+ def estimate_tokens(messages: List[Message]) -> int:
39
+ total = 0
40
+ for msg in messages:
41
+ total += 4 + len(msg.content or "") // 4
42
+ for tc in msg.tool_calls:
43
+ total += 8 + len(tc.tool_name) + len(json.dumps(tc.parameters, ensure_ascii=False)) // 4
44
+ return total
45
+
46
+ @staticmethod
47
+ def find_atomic_groups(messages: List[Message]) -> List[Tuple[int, ...]]:
48
+ groups: List[Tuple[int, ...]] = []
49
+ i = 0
50
+ while i < len(messages):
51
+ msg = messages[i]
52
+ if msg.role == "assistant" and msg.tool_calls:
53
+ pending_ids: Set[str] = {tc.call_id for tc in msg.tool_calls}
54
+ group_indices = [i]
55
+ j = i + 1
56
+ while j < len(messages) and pending_ids:
57
+ if messages[j].role == "tool" and messages[j].tool_call_id in pending_ids:
58
+ group_indices.append(j)
59
+ pending_ids.remove(messages[j].tool_call_id) # type: ignore[arg-type]
60
+ j += 1
61
+ groups.append(tuple(group_indices))
62
+ i = max(group_indices) + 1
63
+ else:
64
+ groups.append((i,))
65
+ i += 1
66
+ return groups
67
+
68
+ def compact(self, messages: List[Message]) -> List[Message]:
69
+ if not messages:
70
+ return messages
71
+ current_tokens = self.estimate_tokens(messages)
72
+ if current_tokens <= self.token_budget:
73
+ return messages
74
+
75
+ self.compaction_count += 1
76
+ groups = self.find_atomic_groups(messages)
77
+ head_budget = int(self.token_budget * self.head_ratio)
78
+ tail_budget = self.token_budget - head_budget
79
+
80
+ head_groups: List[Tuple[int, ...]] = []
81
+ head_tokens = 0
82
+ for grp in groups:
83
+ grp_tokens = self.estimate_tokens([messages[idx] for idx in grp])
84
+ if head_tokens + grp_tokens <= head_budget:
85
+ head_groups.append(grp)
86
+ head_tokens += grp_tokens
87
+ else:
88
+ break
89
+
90
+ remaining = groups[len(head_groups):]
91
+ tail_groups: List[Tuple[int, ...]] = []
92
+ tail_tokens = 0
93
+ for grp in reversed(remaining):
94
+ grp_tokens = self.estimate_tokens([messages[idx] for idx in grp])
95
+ if tail_tokens + grp_tokens <= tail_budget:
96
+ tail_groups.insert(0, grp)
97
+ tail_tokens += grp_tokens
98
+ else:
99
+ break
100
+
101
+ kept_indices: Set[int] = set()
102
+ for grp in head_groups + tail_groups:
103
+ kept_indices.update(grp)
104
+
105
+ compacted = [messages[idx] for idx in sorted(kept_indices)]
106
+ self.total_tokens_saved += current_tokens - self.estimate_tokens(compacted)
107
+ return compacted
@@ -0,0 +1,141 @@
1
+ """Offline stage 7 deployment contracts; no device or shell operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from dataclasses import asdict, dataclass
8
+ from pathlib import Path
9
+
10
+
11
+ _FIRMWARE_SUFFIXES = {".bin", ".elf", ".hex", ".uf2"}
12
+
13
+
14
+ def _required(value: str, label: str) -> str:
15
+ if not isinstance(value, str) or not value.strip():
16
+ raise ValueError(f"{label} is required")
17
+ return value.strip()
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class FirmwareArtifact:
22
+ path: str
23
+ sha256: str
24
+ size_bytes: int
25
+
26
+ @classmethod
27
+ def inspect(cls, path: Path) -> FirmwareArtifact:
28
+ if path.is_symlink() or path.suffix.lower() not in _FIRMWARE_SUFFIXES:
29
+ raise ValueError("Firmware must be a regular .bin/.elf/.hex/.uf2 file")
30
+ if not path.is_file():
31
+ raise FileNotFoundError("Firmware file is missing")
32
+ digest = hashlib.sha256()
33
+ size = 0
34
+ with path.open("rb") as stream:
35
+ for chunk in iter(lambda: stream.read(1024 * 1024), b""):
36
+ digest.update(chunk)
37
+ size += len(chunk)
38
+ if size == 0:
39
+ raise ValueError("Firmware file is empty")
40
+ return cls(str(path.resolve()), digest.hexdigest(), size)
41
+
42
+ def verify(self) -> None:
43
+ current = type(self).inspect(Path(self.path))
44
+ if current.sha256 != self.sha256 or current.size_bytes != self.size_bytes:
45
+ raise ValueError("Firmware changed after inspection")
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class DeviceTarget:
50
+ device_id: str
51
+ board: str
52
+ connection: str
53
+ flash_start: int
54
+ flash_end: int # exclusive
55
+
56
+ def validate(self) -> None:
57
+ for label in ("device_id", "board", "connection"):
58
+ _required(getattr(self, label), label)
59
+ if not (isinstance(self.flash_start, int) and isinstance(self.flash_end, int)
60
+ and 0 <= self.flash_start < self.flash_end):
61
+ raise ValueError("Target flash range must be a nonempty address interval")
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class FlashPlan:
66
+ target: DeviceTarget
67
+ firmware: FirmwareArtifact
68
+ rollback: FirmwareArtifact
69
+ erase_start: int
70
+ erase_end: int # exclusive
71
+ rollback_procedure: str
72
+ heartbeat_marker: str
73
+ plan_digest: str
74
+
75
+ @classmethod
76
+ def prepare(cls, target: DeviceTarget, firmware: FirmwareArtifact,
77
+ rollback: FirmwareArtifact, *, erase_start: int, erase_end: int,
78
+ rollback_procedure: str, heartbeat_marker: str) -> FlashPlan:
79
+ target.validate()
80
+ firmware.verify()
81
+ rollback.verify()
82
+ if not (isinstance(erase_start, int) and isinstance(erase_end, int)
83
+ and target.flash_start <= erase_start < erase_end <= target.flash_end):
84
+ raise ValueError("Erase range is outside the target flash region")
85
+ if Path(firmware.path).suffix.lower() == ".bin" and firmware.size_bytes > erase_end - erase_start:
86
+ raise ValueError("Raw firmware does not fit the erase range")
87
+ if Path(rollback.path).suffix.lower() == ".bin" and rollback.size_bytes > target.flash_end - target.flash_start:
88
+ raise ValueError("Rollback firmware does not fit target flash")
89
+ procedure = _required(rollback_procedure, "rollback_procedure")
90
+ marker = _required(heartbeat_marker, "heartbeat_marker")
91
+ if firmware.path == rollback.path or firmware.sha256 == rollback.sha256:
92
+ raise ValueError("Rollback firmware must be a distinct verified image")
93
+ values = {
94
+ "target": asdict(target), "firmware": asdict(firmware),
95
+ "rollback": asdict(rollback), "erase_start": erase_start,
96
+ "erase_end": erase_end, "rollback_procedure": procedure,
97
+ "heartbeat_marker": marker,
98
+ }
99
+ digest = hashlib.sha256(json.dumps(values, sort_keys=True).encode()).hexdigest()
100
+ return cls(target, firmware, rollback, erase_start, erase_end,
101
+ procedure, marker, digest)
102
+
103
+ def verify(self) -> None:
104
+ rebuilt = type(self).prepare(
105
+ self.target, self.firmware, self.rollback,
106
+ erase_start=self.erase_start, erase_end=self.erase_end,
107
+ rollback_procedure=self.rollback_procedure,
108
+ heartbeat_marker=self.heartbeat_marker,
109
+ )
110
+ if rebuilt.plan_digest != self.plan_digest:
111
+ raise ValueError("Flash plan changed after preparation")
112
+
113
+ def assessment(self) -> dict:
114
+ """Reviewable blast-radius data; contains no command or approval claim."""
115
+ self.verify()
116
+ return {
117
+ "plan_digest": self.plan_digest,
118
+ "target": asdict(self.target),
119
+ "firmware": asdict(self.firmware),
120
+ "erase_range": {"start": self.erase_start, "end_exclusive": self.erase_end},
121
+ "rollback": asdict(self.rollback),
122
+ "rollback_procedure": self.rollback_procedure,
123
+ "heartbeat_marker": self.heartbeat_marker,
124
+ "requires_human_approval": True,
125
+ }
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class SimulationReceipt:
130
+ plan_digest: str
131
+ device_id: str
132
+ heartbeat_observed: bool
133
+ simulated: bool = True
134
+
135
+
136
+ def simulate_flash(plan: FlashPlan, boot_log: str) -> SimulationReceipt:
137
+ """Check a supplied fake boot log without opening ports or writing hardware."""
138
+ plan.verify()
139
+ if not isinstance(boot_log, str) or plan.heartbeat_marker not in boot_log:
140
+ raise ValueError("Simulated boot log does not contain the required heartbeat")
141
+ return SimulationReceipt(plan.plan_digest, plan.target.device_id, True)
cybertron/dispatch.py ADDED
@@ -0,0 +1,92 @@
1
+ """Move the ledger one step after validating an executor result."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .execution import AgentExecutor, AgentResult, AgentTask, QualityKind, ResultStatus
6
+ from .ledger import CybertronTaskLedger
7
+ from .registry import THIRTEEN_PRIMES_REGISTRY
8
+
9
+
10
+ class PrimaDispatcher:
11
+ def __init__(self, executor: AgentExecutor) -> None:
12
+ self.executor = executor
13
+ self.agent_ids = {agent.agent_id for agent in THIRTEEN_PRIMES_REGISTRY}
14
+
15
+ def run_next(self, ledger: CybertronTaskLedger) -> AgentResult | None:
16
+ step = ledger.next_step()
17
+ if step is None:
18
+ return None
19
+ agent_id = step.next_agent()
20
+ if agent_id is None:
21
+ raise ValueError(f"Step {step.step_id} has no remaining agent")
22
+ if agent_id not in self.agent_ids:
23
+ raise ValueError(f"Unknown agent: {agent_id}")
24
+ if ledger.turn_count >= ledger.max_turns:
25
+ raise RuntimeError("Ledger turn limit reached")
26
+ if step.status == "blocked":
27
+ raise ValueError(f"Step {step.step_id} is blocked; resume it first")
28
+ if step.status == "pending":
29
+ ledger.start_step(step.step_id)
30
+
31
+ task = AgentTask(
32
+ step_id=step.step_id,
33
+ agent_id=agent_id,
34
+ user_request=ledger.user_request,
35
+ objective=step.participant_objectives.get(agent_id, step.objective),
36
+ input_artifact=step.input_artifact,
37
+ expected_output=step.expected_output,
38
+ feedback=step.feedback,
39
+ prior_evidence=tuple(
40
+ item for completed in ledger.plan if completed.status == "completed"
41
+ for item in completed.evidence
42
+ ) + tuple(step.evidence),
43
+ )
44
+ ledger.turn_count += 1
45
+ try:
46
+ result = self.executor.execute(task)
47
+ except Exception as exc:
48
+ ledger.block_step(step.step_id, f"Executor error: {exc}")
49
+ raise
50
+
51
+ if result.step_id != task.step_id or result.agent_id != task.agent_id:
52
+ ledger.block_step(step.step_id, "Executor returned a result for a different task")
53
+ raise ValueError("Executor returned a result for a different task")
54
+ if not result.summary.strip():
55
+ ledger.block_step(step.step_id, "Executor returned an empty summary")
56
+ raise ValueError("Executor returned an empty summary")
57
+ if result.status == ResultStatus.COMPLETED:
58
+ if not result.evidence or not all(item.strip() for item in result.evidence):
59
+ ledger.block_step(step.step_id, "Completion has no evidence")
60
+ raise ValueError("Completion requires nonempty evidence")
61
+ gate_kind = {
62
+ "micronus-prime": QualityKind.REVIEW_L1,
63
+ "liege-maximo": QualityKind.REVIEW_L2,
64
+ "alchemist": QualityKind.TEST,
65
+ }.get(agent_id)
66
+ if gate_kind is not None and not any(
67
+ item.kind == gate_kind and item.passed and item.source.strip()
68
+ for item in result.quality_evidence
69
+ ):
70
+ ledger.block_step(step.step_id, f"Missing passing {gate_kind.value} evidence")
71
+ raise ValueError(f"Missing passing {gate_kind.value} evidence")
72
+ ledger.record_agent_completion(step.step_id, agent_id, result.summary,
73
+ list(result.evidence), list(result.artifacts),
74
+ list(result.quality_evidence))
75
+ elif result.status in (ResultStatus.BLOCKED, ResultStatus.NEEDS_APPROVAL):
76
+ ledger.block_step(step.step_id, result.summary)
77
+ elif result.status == ResultStatus.REJECTED:
78
+ ledger.reject_review(agent_id, result.summary)
79
+ else:
80
+ ledger.block_step(step.step_id, "Unknown executor status")
81
+ raise ValueError("Unknown executor status")
82
+ return result
83
+
84
+ def run_until_blocked(self, ledger: CybertronTaskLedger) -> list[AgentResult]:
85
+ results: list[AgentResult] = []
86
+ while ledger.next_step() is not None:
87
+ result = self.run_next(ledger)
88
+ assert result is not None
89
+ results.append(result)
90
+ if result.status != ResultStatus.COMPLETED:
91
+ break
92
+ return results
cybertron/execution.py ADDED
@@ -0,0 +1,58 @@
1
+ """Host-neutral task and result contracts for one agent dispatch."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Protocol, Tuple
8
+
9
+
10
+ class ResultStatus(str, Enum):
11
+ COMPLETED = "completed"
12
+ BLOCKED = "blocked"
13
+ NEEDS_APPROVAL = "needs_approval"
14
+ REJECTED = "rejected"
15
+
16
+
17
+ class QualityKind(str, Enum):
18
+ REVIEW_L1 = "review_l1"
19
+ REVIEW_L2 = "review_l2"
20
+ TEST = "test"
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class QualityEvidence:
25
+ """A typed gate result; source identifies the report or run that produced it."""
26
+ kind: QualityKind
27
+ passed: bool
28
+ source: str
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class AgentTask:
33
+ step_id: int
34
+ agent_id: str
35
+ user_request: str
36
+ objective: str
37
+ input_artifact: str
38
+ expected_output: str
39
+ prior_evidence: Tuple[str, ...] = ()
40
+ feedback: str = ""
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class AgentResult:
45
+ step_id: int
46
+ agent_id: str
47
+ status: ResultStatus
48
+ summary: str
49
+ evidence: Tuple[str, ...] = ()
50
+ artifacts: Tuple[str, ...] = ()
51
+ quality_evidence: Tuple[QualityEvidence, ...] = ()
52
+
53
+
54
+ class AgentExecutor(Protocol):
55
+ """An adapter for a model host or deterministic local simulation."""
56
+
57
+ def execute(self, task: AgentTask) -> AgentResult:
58
+ ...
cybertron/exporters.py ADDED
@@ -0,0 +1,150 @@
1
+ """把角色注册表编译成 OpenCode V2 配置和 DeepSeek Harness 静态预设。"""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ from dataclasses import asdict
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List
8
+ from .registry import THIRTEEN_PRIMES_REGISTRY
9
+
10
+ # ============================================================================
11
+
12
+ def compile_opencode_v2_assets(target_dir: Path, *, include_feishu: bool = False) -> List[Path]:
13
+ """Generates OpenCode V2 native configuration files for all 13 Primes."""
14
+ generated: List[Path] = []
15
+ agents_dir = target_dir / ".opencode" / "agents"
16
+ agents_dir.mkdir(parents=True, exist_ok=True)
17
+
18
+ agents_json_map: Dict[str, Any] = {}
19
+ for prime in THIRTEEN_PRIMES_REGISTRY:
20
+ perm_dicts = [
21
+ {"action": r.action, "resource": r.resource, "effect": r.effect.value}
22
+ for r in prime.permissions
23
+ ]
24
+ perm_yaml_lines = [
25
+ f' - action: "{r.action}"\n resource: "{r.resource}"\n effect: "{r.effect.value}"'
26
+ for r in prime.permissions
27
+ ]
28
+ perm_yaml_block = "\n".join(perm_yaml_lines)
29
+
30
+ md_text = (
31
+ f"---\n"
32
+ f'description: "[{prime.cn_name} · {prime.en_name}] {prime.description}"\n'
33
+ f"mode: {prime.mode.value}\n"
34
+ f"model: {prime.model}\n"
35
+ f"steps: {prime.max_steps}\n"
36
+ f'color: "{prime.color}"\n'
37
+ f"permissions:\n{perm_yaml_block}\n"
38
+ f"---\n\n"
39
+ f"# {prime.cn_name} ({prime.en_name}) — 神器:{prime.artifact}\n"
40
+ f"- **所属生命周期阶段 (SDLC Stage)**: `{prime.sdlc_stage}`\n"
41
+ f"- **唯一专职领域 (Exclusive Domain)**: {prime.exclusive_domain}\n"
42
+ f"- **绝对禁止红线 (Strict Prohibitions)**: {prime.strict_prohibitions}\n"
43
+ f"- **标准输入契约 (Input Contract)**: {prime.input_contract}\n"
44
+ f"- **标准输出契约 (Output Contract)**: {prime.output_contract}\n"
45
+ f"- **飞书知识库角色**: {prime.feishu_wiki_role}\n\n"
46
+ f"{prime.system_prompt}\n"
47
+ )
48
+ md_file = agents_dir / f"{prime.agent_id}.md"
49
+ md_file.write_text(md_text, encoding="utf-8")
50
+ generated.append(md_file)
51
+
52
+ agents_json_map[prime.agent_id] = {
53
+ "description": f"[{prime.cn_name}] {prime.description}",
54
+ "mode": prime.mode.value,
55
+ "model": prime.model,
56
+ "steps": prime.max_steps,
57
+ "color": prime.color,
58
+ "permissions": perm_dicts,
59
+ }
60
+
61
+ opencode_jsonc = {
62
+ "$schema": "https://opencode.ai/config.json",
63
+ "default_agent": "prima",
64
+ "agents": agents_json_map,
65
+ }
66
+ if include_feishu:
67
+ opencode_jsonc["mcp"] = {
68
+ "timeout": {
69
+ "startup": 45000,
70
+ "catalog": 30000,
71
+ "execution": 120000,
72
+ },
73
+ "servers": {
74
+ "feishu_wiki": {
75
+ "type": "local",
76
+ "codemode": False,
77
+ "command": [
78
+ "npx",
79
+ "-y",
80
+ "@larksuiteoapi/lark-mcp",
81
+ "mcp",
82
+ "-a",
83
+ "{env:FEISHU_APP_ID}",
84
+ "-s",
85
+ "{env:FEISHU_APP_SECRET}",
86
+ "--token-mode",
87
+ "tenant_access_token",
88
+ "-c",
89
+ "dot",
90
+ "-t",
91
+ "wiki.v2.space.getNode,docx.v1.document.rawContent",
92
+ ],
93
+ "environment": {
94
+ "FEISHU_APP_ID": "{env:FEISHU_APP_ID}",
95
+ "FEISHU_APP_SECRET": "{env:FEISHU_APP_SECRET}",
96
+ "FEISHU_WIKI_SPACE_ID": "{env:FEISHU_WIKI_SPACE_ID}",
97
+ },
98
+ }
99
+ },
100
+ }
101
+ jsonc_path = target_dir / "opencode.jsonc"
102
+ jsonc_path.write_text(json.dumps(opencode_jsonc, ensure_ascii=False, indent=2), encoding="utf-8")
103
+ generated.append(jsonc_path)
104
+ return generated
105
+
106
+
107
+ def compile_deepseek_harness_assets(target_dir: Path) -> List[Path]:
108
+ """Generates DeepSeek Harness (dsh) Cordis plugin and preset files."""
109
+ dsh_dir = target_dir / ".dsh"
110
+ dsh_dir.mkdir(parents=True, exist_ok=True)
111
+ generated: List[Path] = []
112
+
113
+ preset_data = {
114
+ "name": "cybertron-thirteen-primes-sdlc-hardware",
115
+ "version": "5.0.0",
116
+ "supported_modes": ["standard", "ptc"],
117
+ "orchestration": {
118
+ "pattern": "picoagents-plan-based-sdlc-hardware-ledger",
119
+ "primary_orchestrator": "prima",
120
+ "documentation_and_wiki_steward": "alpha-trion",
121
+ "l1_code_reviewer": "micronus-prime",
122
+ "l2_security_reviewer": "liege-maximo",
123
+ "qa_and_sil_verifier": "alchemist",
124
+ "cross_compile_and_prep": "nexus-prime",
125
+ "physical_flash_gatekeeper": "megatronus",
126
+ },
127
+ "agents": [
128
+ {
129
+ "id": p.agent_id,
130
+ "cn_name": p.cn_name,
131
+ "en_name": p.en_name,
132
+ "artifact": p.artifact,
133
+ "sdlc_stage": p.sdlc_stage,
134
+ "mode": p.mode.value,
135
+ "model": p.model,
136
+ "max_steps": p.max_steps,
137
+ "exclusive_domain": p.exclusive_domain,
138
+ "strict_prohibitions": p.strict_prohibitions,
139
+ "input_contract": p.input_contract,
140
+ "output_contract": p.output_contract,
141
+ "feishu_wiki_role": p.feishu_wiki_role,
142
+ "permissions": [asdict(r) for r in p.permissions],
143
+ }
144
+ for p in THIRTEEN_PRIMES_REGISTRY
145
+ ],
146
+ }
147
+ preset_file = dsh_dir / "cybertron-primes-preset.json"
148
+ preset_file.write_text(json.dumps(preset_data, ensure_ascii=False, indent=2), encoding="utf-8")
149
+ generated.append(preset_file)
150
+ return generated