arkaos 2.17.1 → 2.17.4

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.
Files changed (77) hide show
  1. package/VERSION +1 -1
  2. package/arka/SKILL.md +9 -67
  3. package/arka/skills/comfyui/SKILL.md +50 -12
  4. package/arka/skills/conclave/SKILL.md +43 -141
  5. package/arka/skills/conclave/references/advisors.md +36 -0
  6. package/arka/skills/human-writing/SKILL.md +15 -100
  7. package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
  8. package/config/hooks/post-tool-use.sh +72 -0
  9. package/config/hooks/session-start.sh +16 -0
  10. package/config/hooks/user-prompt-submit.ps1 +2 -2
  11. package/config/hooks/user-prompt-submit.sh +102 -26
  12. package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
  13. package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
  14. package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
  15. package/core/agents/adapters/disc_adapter.py +149 -0
  16. package/core/agents/behavior_enforcer.py +255 -0
  17. package/core/agents/dna_registry.py +235 -0
  18. package/core/forge/__init__.py +36 -0
  19. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  21. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  22. package/core/forge/orchestrator.py +770 -0
  23. package/core/forge/runtime_dispatcher.py +465 -0
  24. package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
  26. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  27. package/core/governance/quality_api.py +280 -0
  28. package/core/governance/quality_router.py +304 -0
  29. package/core/governance/review_workflow.py +386 -0
  30. package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
  31. package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
  32. package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
  33. package/core/memory/compressor.py +269 -0
  34. package/core/memory/rehydrator.py +204 -0
  35. package/core/memory/session_store.py +256 -0
  36. package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
  38. package/core/synapse/__init__.py +10 -3
  39. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  41. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  42. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  43. package/core/synapse/engine.py +27 -16
  44. package/core/synapse/kb_cache.py +382 -0
  45. package/core/synapse/layers.py +253 -50
  46. package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
  47. package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
  48. package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
  49. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  50. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  51. package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
  52. package/core/workflow/announcer.py +246 -0
  53. package/core/workflow/dashboard.py +194 -0
  54. package/core/workflow/enforcer.py +234 -0
  55. package/core/workflow/recovery.py +196 -0
  56. package/core/workflow/rules_registry.py +484 -0
  57. package/core/workflow/session_summary.py +204 -0
  58. package/core/workflow/state.py +12 -2
  59. package/departments/dev/SKILL.md +10 -42
  60. package/departments/dev/skills/agent-design/SKILL.md +6 -26
  61. package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
  62. package/departments/dev/skills/db-schema/SKILL.md +6 -24
  63. package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
  64. package/departments/dev/skills/incident/SKILL.md +6 -24
  65. package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
  66. package/departments/dev/skills/observability/SKILL.md +6 -29
  67. package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
  68. package/departments/dev/skills/rag-architect/SKILL.md +5 -23
  69. package/departments/dev/skills/release/SKILL.md +4 -17
  70. package/departments/dev/skills/spec/SKILL.md +47 -148
  71. package/departments/landing/skills/landing-gen/SKILL.md +3 -15
  72. package/departments/marketing/skills/cold-email/SKILL.md +5 -17
  73. package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
  74. package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
  75. package/package.json +1 -1
  76. package/pyproject.toml +1 -1
  77. package/scripts/synapse-bridge.py +27 -3
@@ -0,0 +1,255 @@
1
+ """Behavior enforcement middleware.
2
+
3
+ Validates agent outputs against their stated behavioral DNA profile.
4
+ Detects "behavior drift" — when an agent acts outside their profile.
5
+
6
+ This is advisory only (WARN severity) — it doesn't block operations
7
+ but surfaces discrepancies for human review.
8
+ """
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+ from core.agents.dna_registry import DNARegistry, get_registry
15
+ from core.agents.schema import Agent, DISCType
16
+
17
+
18
+ @dataclass
19
+ class BehaviorDrift:
20
+ """A detected deviation from an agent's DNA profile."""
21
+
22
+ agent_id: str
23
+ drift_type: str # "tone", "vocabulary", "approach", "communication_style"
24
+ expected: str
25
+ detected: str
26
+ severity: str = "WARN" # WARN or INFO
27
+ suggestion: str = ""
28
+
29
+
30
+ class BehaviorEnforcer:
31
+ """Middleware to enforce behavioral DNA profiles on agent outputs."""
32
+
33
+ def __init__(self, registry: DNARegistry | None = None):
34
+ self.registry = registry or get_registry()
35
+
36
+ def check_output(
37
+ self,
38
+ agent_id: str,
39
+ output: str,
40
+ context: str = "",
41
+ ) -> list[BehaviorDrift]:
42
+ """Check if an output deviates from the agent's DNA profile.
43
+
44
+ Args:
45
+ agent_id: ID of the agent that produced the output
46
+ output: The text output to check
47
+ context: Optional context about the interaction type
48
+
49
+ Returns:
50
+ List of detected behavior drifts
51
+ """
52
+ agent = self.registry.get(agent_id)
53
+ if not agent:
54
+ return []
55
+
56
+ drifts: list[BehaviorDrift] = []
57
+
58
+ drifts.extend(self._check_tone(agent, output))
59
+ drifts.extend(self._check_vocabulary(agent, output))
60
+ drifts.extend(self._check_communication_style(agent, output))
61
+ drifts.extend(self._check_avoid_list(agent, output))
62
+
63
+ return drifts
64
+
65
+ def _check_tone(self, agent: Agent, output: str) -> list[BehaviorDrift]:
66
+ """Check if output tone matches agent's stated DISC style."""
67
+ drifts = []
68
+ disc = agent.behavioral_dna.disc
69
+ output_lower = output.lower()
70
+
71
+ if disc.primary == DISCType.D:
72
+ if not any(
73
+ word in output_lower for word in ["need", "must", "require", "demand", "critical"]
74
+ ):
75
+ if len(output) > 100 and "?" not in output:
76
+ drifts.append(
77
+ BehaviorDrift(
78
+ agent_id=agent.id,
79
+ drift_type="tone",
80
+ expected="Direct, decisive, bottom-line first",
81
+ detected="Lacks direct command language",
82
+ suggestion="Lead with conclusions. Use 'must', 'need', 'critical'.",
83
+ )
84
+ )
85
+
86
+ elif disc.primary == DISCType.I:
87
+ if not any(
88
+ word in output_lower
89
+ for word in [
90
+ "great",
91
+ "amazing",
92
+ "exciting",
93
+ "fantastic",
94
+ "!" * 3 if "!!" in output else "",
95
+ ]
96
+ ):
97
+ if len(output) > 200 and "!" not in output and "great" not in output_lower:
98
+ drifts.append(
99
+ BehaviorDrift(
100
+ agent_id=agent.id,
101
+ drift_type="tone",
102
+ expected="Enthusiastic, engaging, positive",
103
+ detected="Lacks enthusiasm markers",
104
+ suggestion="Show energy. Use 'great', 'amazing', 'exciting'.",
105
+ )
106
+ )
107
+
108
+ elif disc.primary == DISCType.S:
109
+ if any(word in output_lower for word in ["must", "immediately", "urgent", "asap"]):
110
+ if output.count("must") > 2 or "immediately" in output:
111
+ drifts.append(
112
+ BehaviorDrift(
113
+ agent_id=agent.id,
114
+ drift_type="tone",
115
+ expected="Supportive, steady, patient",
116
+ detected="Overly urgent or demanding",
117
+ suggestion="Soften language. Avoid 'must', 'immediately', 'asap'.",
118
+ )
119
+ )
120
+
121
+ elif disc.primary == DISCType.C:
122
+ if not any(
123
+ pattern in output
124
+ for pattern in ["1.", "2.", "3.", "• ", "- ", "data:", "analysis:"]
125
+ ):
126
+ if len(output) > 150 and not re.search(r"\d+", output):
127
+ drifts.append(
128
+ BehaviorDrift(
129
+ agent_id=agent.id,
130
+ drift_type="tone",
131
+ expected="Precise, analytical, structured",
132
+ detected="Lacks structured format",
133
+ suggestion="Use numbered lists, bullet points, and data references.",
134
+ )
135
+ )
136
+
137
+ return drifts
138
+
139
+ def _check_vocabulary(self, agent: Agent, output: str) -> list[BehaviorDrift]:
140
+ """Check if output uses appropriate vocabulary level."""
141
+ drifts = []
142
+ vocab_level = agent.communication.vocabulary_level
143
+ output_lower = output.lower()
144
+
145
+ if vocab_level == "specialist":
146
+ tech_terms = ["architecture", "infrastructure", "optimization", "latency", "throughput"]
147
+ if not any(term in output_lower for term in tech_terms):
148
+ if len(output) > 150:
149
+ drifts.append(
150
+ BehaviorDrift(
151
+ agent_id=agent.id,
152
+ drift_type="vocabulary",
153
+ expected="Specialist-level technical vocabulary",
154
+ detected="Generic language detected",
155
+ suggestion="Use precise technical terms relevant to the domain.",
156
+ )
157
+ )
158
+
159
+ return drifts
160
+
161
+ def _check_communication_style(self, agent: Agent, output: str) -> list[BehaviorDrift]:
162
+ """Check if output matches preferred communication format."""
163
+ drifts = []
164
+ preferred = agent.communication.preferred_format
165
+ output_lower = output.lower()
166
+
167
+ if "structured" in preferred or "diagrams" in preferred:
168
+ if len(output) > 300 and not any(
169
+ marker in output for marker in ["1.", "2.", "3.", "##", "###", "|"]
170
+ ):
171
+ drifts.append(
172
+ BehaviorDrift(
173
+ agent_id=agent.id,
174
+ drift_type="communication_style",
175
+ expected=f"Structured with {preferred}",
176
+ detected="Free-form text without structure",
177
+ suggestion="Add headers, numbered steps, or tabular format.",
178
+ )
179
+ )
180
+
181
+ if "code examples" in preferred:
182
+ if "```" not in output and len(output) > 200:
183
+ drifts.append(
184
+ BehaviorDrift(
185
+ agent_id=agent.id,
186
+ drift_type="communication_style",
187
+ expected="Includes code examples",
188
+ detected="No code blocks found",
189
+ suggestion="Add code examples to illustrate points.",
190
+ )
191
+ )
192
+
193
+ return drifts
194
+
195
+ def _check_avoid_list(self, agent: Agent, output: str) -> list[BehaviorDrift]:
196
+ """Check if output contains items from agent's avoid list."""
197
+ drifts = []
198
+ avoid_list = agent.communication.avoid
199
+ output_lower = output.lower()
200
+
201
+ for item in avoid_list:
202
+ if item.lower() in output_lower:
203
+ drifts.append(
204
+ BehaviorDrift(
205
+ agent_id=agent.id,
206
+ drift_type="tone",
207
+ expected=f"Avoid: {item}",
208
+ detected=f"Contains '{item}'",
209
+ severity="INFO",
210
+ suggestion=f"Avoid using '{item}' when communicating.",
211
+ )
212
+ )
213
+
214
+ return drifts
215
+
216
+ def enforce_output(
217
+ self,
218
+ agent_id: str,
219
+ output: str,
220
+ context: str = "",
221
+ ) -> tuple[str, list[BehaviorDrift]]:
222
+ """Check output and return (possibly modified) output with drifts.
223
+
224
+ This is advisory — it doesn't modify output but returns drifts
225
+ for logging or review purposes.
226
+
227
+ Args:
228
+ agent_id: ID of the agent
229
+ output: The output to check
230
+ context: Optional interaction context
231
+
232
+ Returns:
233
+ Tuple of (output unchanged, list of drifts)
234
+ """
235
+ drifts = self.check_output(agent_id, output, context)
236
+ return output, drifts
237
+
238
+
239
+ def check_agent_behavior(
240
+ agent_id: str,
241
+ output: str,
242
+ context: str = "",
243
+ ) -> list[BehaviorDrift]:
244
+ """Convenience function to check an agent's output.
245
+
246
+ Args:
247
+ agent_id: ID of the agent
248
+ output: Output text to check
249
+ context: Optional context
250
+
251
+ Returns:
252
+ List of BehaviorDrift objects
253
+ """
254
+ enforcer = BehaviorEnforcer()
255
+ return enforcer.check_output(agent_id, output, context)
@@ -0,0 +1,235 @@
1
+ """Agent DNA Registry — query and filter agents by behavioral DNA traits.
2
+
3
+ Provides:
4
+ - Load all agents from departments/*/agents/*.yaml
5
+ - Query agents by DISC type, Enneagram, Big Five traits, MBTI
6
+ - Find compatible agents for collaboration
7
+ - Validate DNA profile completeness
8
+ """
9
+
10
+ from pathlib import Path
11
+ from typing import Iterator
12
+
13
+ from core.agents.loader import load_all_agents
14
+ from core.agents.schema import (
15
+ Agent,
16
+ BehavioralDNA,
17
+ DISCType,
18
+ EnneagramType,
19
+ BigFiveProfile,
20
+ MBTIType,
21
+ )
22
+
23
+
24
+ class DNARegistry:
25
+ """Registry for querying agents by behavioral DNA traits."""
26
+
27
+ def __init__(self, base_dir: str | Path | None = None):
28
+ """Initialize registry.
29
+
30
+ Args:
31
+ base_dir: Root of departments tree. Defaults to repo root.
32
+ """
33
+ if base_dir is None:
34
+ base_dir = Path(__file__).parent.parent.parent / "departments"
35
+ self.base_dir = Path(base_dir)
36
+ self._agents: list[Agent] = []
37
+ self._by_id: dict[str, Agent] = {}
38
+ self._load_agents()
39
+
40
+ def _load_agents(self) -> None:
41
+ """Load all agents from YAML files."""
42
+ self._agents = load_all_agents(self.base_dir)
43
+ self._by_id = {a.id: a for a in self._agents}
44
+
45
+ def reload(self) -> None:
46
+ """Reload all agents from disk."""
47
+ self._load_agents()
48
+
49
+ def get(self, agent_id: str) -> Agent | None:
50
+ """Get agent by ID."""
51
+ return self._by_id.get(agent_id)
52
+
53
+ def all(self) -> list[Agent]:
54
+ """Get all loaded agents."""
55
+ return self._agents.copy()
56
+
57
+ def by_disc(self, primary: DISCType) -> list[Agent]:
58
+ """Find agents by DISC primary type."""
59
+ return [a for a in self._agents if a.behavioral_dna.disc.primary == primary]
60
+
61
+ def by_enneagram(self, type_: EnneagramType) -> list[Agent]:
62
+ """Find agents by Enneagram type."""
63
+ return [a for a in self._agents if a.behavioral_dna.enneagram.type == type_]
64
+
65
+ def by_mbti(self, type_: MBTIType) -> list[Agent]:
66
+ """Find agents by MBTI type."""
67
+ return [a for a in self._agents if a.behavioral_dna.mbti.type == type_]
68
+
69
+ def by_department(self, department: str) -> list[Agent]:
70
+ """Find agents by department."""
71
+ return [a for a in self._agents if a.department == department]
72
+
73
+ def by_tier(self, tier: int) -> list[Agent]:
74
+ """Find agents by tier (0-3)."""
75
+ return [a for a in self._agents if a.tier == tier]
76
+
77
+ def by_big_five(
78
+ self,
79
+ openness_min: int = 0,
80
+ conscientiousness_min: int = 0,
81
+ extraversion_min: int = 0,
82
+ agreeableness_min: int = 0,
83
+ neuroticism_max: int = 100,
84
+ ) -> list[Agent]:
85
+ """Find agents matching Big Five thresholds."""
86
+ results = []
87
+ for a in self._agents:
88
+ bf = a.behavioral_dna.big_five
89
+ if (
90
+ bf.openness >= openness_min
91
+ and bf.conscientiousness >= conscientiousness_min
92
+ and bf.extraversion >= extraversion_min
93
+ and bf.agreeableness >= agreeableness_min
94
+ and bf.neuroticism <= neuroticism_max
95
+ ):
96
+ results.append(a)
97
+ return results
98
+
99
+ def compatible_with(self, agent_id: str) -> list[Agent]:
100
+ """Find agents compatible for collaboration.
101
+
102
+ Compatibility is based on complementary DISC profiles
103
+ and similar communication styles.
104
+ """
105
+ agent = self.get(agent_id)
106
+ if not agent:
107
+ return []
108
+
109
+ disc = agent.behavioral_dna.disc
110
+ disc_map = {
111
+ DISCType.D: {DISCType.I, DISCType.S}, # D works with I and S
112
+ DISCType.I: {DISCType.D, DISCType.S}, # I works with D and S
113
+ DISCType.S: {DISCType.D, DISCType.I, DISCType.C}, # S works with most
114
+ DISCType.C: {DISCType.S, DISCType.C}, # C works with S and C
115
+ }
116
+ compatible_types = disc_map.get(disc.primary, set())
117
+
118
+ return [
119
+ a
120
+ for a in self._agents
121
+ if a.id != agent_id and a.behavioral_dna.disc.primary in compatible_types
122
+ ]
123
+
124
+ def contrasting_disc(self, agent_id: str) -> list[Agent]:
125
+ """Find agents with contrasting DISC profiles.
126
+
127
+ Useful for getting different perspectives.
128
+ """
129
+ agent = self.get(agent_id)
130
+ if not agent:
131
+ return []
132
+
133
+ primary = agent.behavioral_dna.disc.primary
134
+ opposites = {
135
+ DISCType.D: {DISCType.S, DISCType.C},
136
+ DISCType.I: {DISCType.C},
137
+ DISCType.S: {DISCType.D},
138
+ DISCType.C: {DISCType.D, DISCType.I},
139
+ }
140
+ opposite_types = opposites.get(primary, set())
141
+
142
+ return [
143
+ a
144
+ for a in self._agents
145
+ if a.id != agent_id and a.behavioral_dna.disc.primary in opposite_types
146
+ ]
147
+
148
+ def validate_dna_completeness(self) -> dict[str, list[str]]:
149
+ """Validate that all agents have complete DNA profiles.
150
+
151
+ Returns:
152
+ Dict mapping agent_id to list of missing fields
153
+ """
154
+ missing: dict[str, list[str]] = {}
155
+ for agent in self._agents:
156
+ issues = []
157
+ dna = agent.behavioral_dna
158
+
159
+ if not dna.disc.primary:
160
+ issues.append("disc.primary")
161
+ if not dna.enneagram.type:
162
+ issues.append("enneagram.type")
163
+ if not dna.big_five.openness:
164
+ issues.append("big_five.openness")
165
+ if not dna.mbti.type:
166
+ issues.append("mbti.type")
167
+
168
+ if issues:
169
+ missing[agent.id] = issues
170
+
171
+ return missing
172
+
173
+ def get_communication_style(self, agent_id: str) -> str | None:
174
+ """Get the communication style hint for an agent."""
175
+ agent = self.get(agent_id)
176
+ if not agent:
177
+ return None
178
+ return agent.communication.tone
179
+
180
+ def get_tone_for_recipient(self, sender_id: str, recipient_id: str) -> str:
181
+ """Get adapted tone when sender communicates with recipient.
182
+
183
+ Args:
184
+ sender_id: The sender's agent ID
185
+ recipient_id: The recipient's agent ID
186
+
187
+ Returns:
188
+ Communication tone adapted for the recipient
189
+ """
190
+ sender = self.get(sender_id)
191
+ recipient = self.get(recipient_id)
192
+ if not sender or not recipient:
193
+ return "professional"
194
+
195
+ sender_disc = sender.behavioral_dna.disc.primary
196
+ recipient_disc = recipient.behavioral_dna.disc.primary
197
+
198
+ tone_map = {
199
+ (DISCType.D, DISCType.D): "direct and decisive",
200
+ (DISCType.D, DISCType.I): "direct but enthusiastic",
201
+ (DISCType.D, DISCType.S): "direct and supportive",
202
+ (DISCType.D, DISCType.C): "direct and precise",
203
+ (DISCType.I, DISCType.D): "enthusiastic but direct",
204
+ (DISCType.I, DISCType.I): "enthusiastic and collaborative",
205
+ (DISCType.I, DISCType.S): "enthusiastic and warm",
206
+ (DISCType.I, DISCType.C): "enthusiastic but detailed",
207
+ (DISCType.S, DISCType.D): "supportive but direct",
208
+ (DISCType.S, DISCType.I): "warm and collaborative",
209
+ (DISCType.S, DISCType.S): "warm and steady",
210
+ (DISCType.S, DISCType.C): "supportive and precise",
211
+ (DISCType.C, DISCType.D): "precise and direct",
212
+ (DISCType.C, DISCType.I): "precise but friendly",
213
+ (DISCType.C, DISCType.S): "precise and supportive",
214
+ (DISCType.C, DISCType.C): "precise and thorough",
215
+ }
216
+
217
+ return tone_map.get((sender_disc, recipient_disc), "professional")
218
+
219
+
220
+ _REGISTRY: DNARegistry | None = None
221
+
222
+
223
+ def get_registry() -> DNARegistry:
224
+ """Get the global DNA registry (singleton)."""
225
+ global _REGISTRY
226
+ if _REGISTRY is None:
227
+ _REGISTRY = DNARegistry()
228
+ return _REGISTRY
229
+
230
+
231
+ def reload_registry() -> DNARegistry:
232
+ """Reload and return the global registry."""
233
+ global _REGISTRY
234
+ _REGISTRY = DNARegistry()
235
+ return _REGISTRY
@@ -55,6 +55,26 @@ from core.forge.handoff import (
55
55
  check_repo_drift,
56
56
  )
57
57
 
58
+ from core.forge.orchestrator import (
59
+ ForgeOrchestrator,
60
+ ForgeDecision,
61
+ ForgeStep,
62
+ ForgeStatusOutput,
63
+ ForgeHistoryEntry,
64
+ ForgeCompareOutput,
65
+ CONSTITUTION_PHASES,
66
+ )
67
+
68
+ from core.forge.runtime_dispatcher import (
69
+ ForgeTaskDispatcher,
70
+ ClaudeCodeForgeDispatcher,
71
+ ExplorerDispatchRequest,
72
+ CriticDispatchRequest,
73
+ DispatchResult,
74
+ _tier_to_model,
75
+ create_dispatcher,
76
+ )
77
+
58
78
  __all__ = [
59
79
  # schema
60
80
  "ForgeTier",
@@ -101,4 +121,20 @@ __all__ = [
101
121
  "select_execution_path",
102
122
  "generate_workflow_yaml",
103
123
  "check_repo_drift",
124
+ # orchestrator
125
+ "ForgeOrchestrator",
126
+ "ForgeDecision",
127
+ "ForgeStep",
128
+ "ForgeStatusOutput",
129
+ "ForgeHistoryEntry",
130
+ "ForgeCompareOutput",
131
+ "CONSTITUTION_PHASES",
132
+ # runtime dispatcher
133
+ "ForgeTaskDispatcher",
134
+ "ClaudeCodeForgeDispatcher",
135
+ "ExplorerDispatchRequest",
136
+ "CriticDispatchRequest",
137
+ "DispatchResult",
138
+ "_tier_to_model",
139
+ "create_dispatcher",
104
140
  ]