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,465 @@
1
+ """Forge Runtime Dispatcher — multi-runtime Task tool abstraction.
2
+
3
+ Provides a unified interface for dispatching explorer and critic subagents
4
+ across all supported runtimes (Claude Code, Codex CLI, Gemini CLI, Cursor).
5
+ Each runtime gets its own dispatcher implementation.
6
+
7
+ Model routing per tier:
8
+ - shallow (≤30): haiku (cost-optimized, inline execution)
9
+ - standard (31-65): sonnet (balanced cost/quality)
10
+ - deep (66-85): opus (highest quality for complex judgment)
11
+ - super (≥86): opus (full synthesis, highest judgment)
12
+ """
13
+
14
+ import json
15
+ import re
16
+ from abc import ABC, abstractmethod
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime, timezone
19
+ from typing import Any
20
+
21
+ from core.forge.schema import (
22
+ ExplorerLens,
23
+ ExplorerApproach,
24
+ CriticVerdict,
25
+ ForgeContext,
26
+ ForgeTier,
27
+ PhaseDeliverable,
28
+ KeyDecision,
29
+ RiskSeverity,
30
+ RejectedElement,
31
+ IdentifiedRisk,
32
+ )
33
+
34
+
35
+ EXPLORER_PREAMBLE = """You are an expert planning agent within ArkaOS. Your task is to produce a structured execution plan for the following prompt."""
36
+
37
+ CRITIC_PREAMBLE = """You are the Plan Critic within ArkaOS. You have received {n} independent planning approaches for the same prompt. Your job is to synthesize the best plan by combining the strongest elements from each approach."""
38
+
39
+
40
+ CONSTITUTION_RULES = """CONSTITUTION RULES (non-negotiable):
41
+ - branch-isolation: all work on feature branches, never main
42
+ - spec-driven: spec must exist before implementation
43
+ - solid-clean-code: SOLID + Clean Code on all code
44
+ - mandatory-qa: tests must pass, coverage >= 80%
45
+ - quality-gate: Marta + Eduardo + Francisca must APPROVE before done
46
+ - conventional-commits: all commits follow conventional commit format"""
47
+
48
+ AVAILABLE_DEPARTMENTS = "dev, ops, mkt, brand, fin, strat, pm, saas, landing, content, ecom, kb, sales, lead, community, org"
49
+
50
+ AVAILABLE_AGENTS = """dev: Paulo (lead), Gabriel (architect), Andre (backend), Diana (frontend), Bruno (security), Carlos (devops), Rita (qa), Vasco (dba)
51
+ ops: Daniel (lead), Lucas (automation)
52
+ mkt: Luna (lead), Sofia (growth), Pedro (analytics), Carla (email)
53
+ brand: Valentina (lead), Miguel (visual), Ana (copy), Joao (ux)
54
+ quality (cross-cutting): Marta (CQO), Eduardo (copy), Francisca (tech)"""
55
+
56
+ LENS_INSTRUCTIONS = {
57
+ ExplorerLens.PRAGMATIC: """Your lens: PRAGMATIC
58
+ Question to answer: "What is the simplest thing that works?"
59
+ Principles:
60
+ - Minimum viable approach — reuse existing patterns, avoid over-engineering
61
+ - Maximum reuse of existing ArkaOS skills and workflows
62
+ - Fewer phases is better — collapse where safe to do so
63
+ - Prefer known, proven solutions over novel ones
64
+ - Identify what can be skipped without meaningful quality loss
65
+ Be direct. Challenge gold-plating. Propose the leanest plan that still satisfies all Constitution rules.""",
66
+ ExplorerLens.ARCHITECTURAL: """Your lens: ARCHITECTURAL
67
+ Question to answer: "What is the right way to build this for the long term?"
68
+ Principles:
69
+ - Long-term extensibility and maintainability over short-term speed
70
+ - Proper separation of concerns, clean boundaries
71
+ - Identify technical debt that would be created by a simpler approach
72
+ - Ensure observability, testability, and deployability are first-class
73
+ - Reference DDD, SOLID, Clean Architecture where applicable
74
+ Be thorough. Do not cut corners that will create future problems.""",
75
+ ExplorerLens.CONTRARIAN: """Your lens: CONTRARIAN
76
+ Question to answer: "What is everyone missing or assuming wrongly?"
77
+ Principles:
78
+ - Challenge the premise of the prompt — is this the right problem to solve?
79
+ - Surface hidden dependencies, risks, and blockers others would miss
80
+ - Question every assumed constraint — are they real?
81
+ - Propose an alternative framing if the original prompt is misguided
82
+ - Identify the single biggest risk in the other approaches
83
+ Be adversarial but constructive. Your job is to stress-test assumptions, not to be contrarian for its own sake. You must still produce a valid plan.""",
84
+ }
85
+
86
+
87
+ @dataclass
88
+ class DispatchResult:
89
+ """Result from a dispatcher call."""
90
+
91
+ success: bool
92
+ output: str = ""
93
+ error: str = ""
94
+ raw_response: str = ""
95
+
96
+
97
+ @dataclass
98
+ class ExplorerDispatchRequest:
99
+ """Request to dispatch an explorer subagent."""
100
+
101
+ lens: ExplorerLens
102
+ prompt: str
103
+ context: ForgeContext
104
+ similar_plans: list[str] = field(default_factory=list)
105
+ reused_patterns: list[str] = field(default_factory=list)
106
+ model: str = "sonnet"
107
+
108
+
109
+ @dataclass
110
+ class CriticDispatchRequest:
111
+ """Request to dispatch a critic subagent."""
112
+
113
+ original_prompt: str
114
+ approaches: list[ExplorerApproach]
115
+ model: str = "sonnet"
116
+
117
+
118
+ def _tier_to_model(tier: ForgeTier) -> str:
119
+ """Map tier to default model."""
120
+ mapping = {
121
+ ForgeTier.SHALLOW: "haiku",
122
+ ForgeTier.STANDARD: "sonnet",
123
+ ForgeTier.DEEP: "opus",
124
+ }
125
+ return mapping.get(tier, "sonnet")
126
+
127
+
128
+ def _build_explorer_prompt(req: ExplorerDispatchRequest) -> str:
129
+ """Build the full prompt for an explorer subagent."""
130
+ parts = [
131
+ EXPLORER_PREAMBLE,
132
+ "",
133
+ f"PROMPT: {req.prompt}",
134
+ "",
135
+ "REPO CONTEXT:",
136
+ f" Repo: {req.context.repo}",
137
+ f" Branch: {req.context.branch}",
138
+ f" Commit: {req.context.commit_at_forge}",
139
+ f" ArkaOS: {req.context.arkaos_version}",
140
+ "",
141
+ "OBSIDIAN KNOWLEDGE:",
142
+ f" Similar plans found: {', '.join(req.similar_plans) if req.similar_plans else 'none'}",
143
+ f" Reusable patterns: {', '.join(req.reused_patterns) if req.reused_patterns else 'none'}",
144
+ "",
145
+ CONSTITUTION_RULES,
146
+ "",
147
+ f"AVAILABLE DEPARTMENTS: {AVAILABLE_DEPARTMENTS}",
148
+ "",
149
+ f"AVAILABLE AGENTS:\n{AVAILABLE_AGENTS}",
150
+ "",
151
+ LENS_INSTRUCTIONS[req.lens],
152
+ "",
153
+ "Your output MUST follow this exact format:",
154
+ "EXPLORER: <your lens name>",
155
+ "SUMMARY: <2-3 sentences>",
156
+ "KEY_DECISIONS:",
157
+ " - decision: <what>",
158
+ " rationale: <why>",
159
+ "PHASES:",
160
+ " - name: <phase name>",
161
+ " department: <dept>",
162
+ " agents: [<names>]",
163
+ " deliverables: [<items>]",
164
+ " acceptance_criteria: [<items>]",
165
+ " depends_on: [<phase names>]",
166
+ ]
167
+ return "\n".join(parts)
168
+
169
+
170
+ def _build_critic_prompt(req: CriticDispatchRequest) -> str:
171
+ """Build the full prompt for a critic subagent."""
172
+ approach_texts = []
173
+ for i, approach in enumerate(req.approaches, 1):
174
+ decisions = "\n".join(
175
+ f" - decision: {d.decision}\n rationale: {d.rationale}"
176
+ for d in approach.key_decisions
177
+ )
178
+ phases = "\n".join(
179
+ f" - name: {p.name}\n department: {p.department}\n agents: {p.agents}\n deliverables: {p.deliverables}\n acceptance_criteria: {p.acceptance_criteria}\n depends_on: {p.depends_on}"
180
+ for p in approach.phases
181
+ )
182
+ text = f"""APPROACH {i} ({approach.explorer.value}):
183
+ Summary: {approach.summary}
184
+ Key Decisions:
185
+ {decisions}
186
+ Phases:
187
+ {phases}"""
188
+ approach_texts.append(text)
189
+
190
+ parts = [
191
+ CRITIC_PREAMBLE.format(n=len(req.approaches)),
192
+ "",
193
+ f"PROMPT: {req.original_prompt}",
194
+ "",
195
+ "APPROACHES:",
196
+ "\n---\n".join(approach_texts),
197
+ "",
198
+ "RULES:",
199
+ " - You MUST adopt the best elements from multiple approaches (do not just pick one)",
200
+ " - You MUST reject at least 1 element with a clear reason",
201
+ " - You MUST identify at least 1 risk in the final plan",
202
+ " - Confidence score must reflect genuine uncertainty (do not always output 0.9)",
203
+ " - The final phase list MUST include a Quality Gate phase as the penultimate step",
204
+ " - The final phase list MUST include an Obsidian persistence phase as the last step",
205
+ " - All Constitution rules apply to the final plan",
206
+ "",
207
+ "Your output MUST follow this exact format:",
208
+ "CONFIDENCE: <0.0-1.0>",
209
+ "SYNTHESIS:",
210
+ " approach_1: [<adopted elements from approach 1>]",
211
+ " approach_2: [<adopted elements from approach 2>]",
212
+ " approach_3: [<adopted elements from approach 3, if applicable>]",
213
+ "REJECTED:",
214
+ " - element: <what>",
215
+ " reason: <why>",
216
+ "RISKS:",
217
+ " - risk: <description>",
218
+ " severity: high|medium|low",
219
+ " mitigation: <how to address>",
220
+ "FINAL_PHASES:",
221
+ " - name: <phase name>",
222
+ " department: <dept>",
223
+ " agents: [<names>]",
224
+ " deliverables: [<items>]",
225
+ " acceptance_criteria: [<items>]",
226
+ " depends_on: [<phase names>]",
227
+ ]
228
+ return "\n".join(parts)
229
+
230
+
231
+ def _parse_explorer_output(raw: str, lens: ExplorerLens) -> ExplorerApproach:
232
+ """Parse structured text output from an explorer subagent."""
233
+ explorer_match = re.search(r"EXPLORER:\s*(\w+)", raw)
234
+ summary_match = re.search(r"SUMMARY:\s*(.+?)(?=KEY_DECISIONS:|$)", raw, re.DOTALL)
235
+ key_decisions = []
236
+ for match in re.finditer(
237
+ r"-\s*decision:\s*(.+?)\n\s*rationale:\s*(.+?)(?=\n\s*-\s*decision:|\nPHASES:|$)",
238
+ raw,
239
+ re.DOTALL,
240
+ ):
241
+ key_decisions.append(
242
+ KeyDecision(decision=match.group(1).strip(), rationale=match.group(2).strip())
243
+ )
244
+
245
+ phases = []
246
+ phase_blocks = re.split(r"(?=^\s*-\s*name:)", raw, flags=re.MULTILINE)
247
+ for block in phase_blocks[1:]:
248
+ name_match = re.search(r"name:\s*(.+?)(?=\n|$)", block)
249
+ dept_match = re.search(r"department:\s*(\w+)", block)
250
+ agents_match = re.findall(r"agents:\s*\[(.+?)\]", block)
251
+ deliverables_match = re.findall(r"deliverables:\s*\[(.+?)\]", block)
252
+ criteria_match = re.findall(r"acceptance_criteria:\s*\[(.+?)\]", block)
253
+ depends_match = re.findall(r"depends_on:\s*\[(.+?)\]", block)
254
+ if name_match and dept_match:
255
+ phases.append(
256
+ PhaseDeliverable(
257
+ name=name_match.group(1).strip(),
258
+ deliverables=[d.strip() for d in deliverables_match],
259
+ agents=[a.strip() for a in agents_match],
260
+ acceptance_criteria=[c.strip() for c in criteria_match],
261
+ depends_on=[d.strip() for d in depends_match],
262
+ )
263
+ )
264
+
265
+ return ExplorerApproach(
266
+ explorer=lens,
267
+ summary=summary_match.group(1).strip() if summary_match else "",
268
+ key_decisions=key_decisions,
269
+ phases=phases,
270
+ )
271
+
272
+
273
+ def _parse_critic_output(raw: str) -> CriticVerdict:
274
+ """Parse structured text output from a critic subagent."""
275
+ confidence_match = re.search(r"CONFIDENCE:\s*([\d.]+)", raw)
276
+ confidence = float(confidence_match.group(1)) if confidence_match else 0.0
277
+
278
+ synthesis = {}
279
+ synthesis_section = re.search(r"SYNTHESIS:(.+?)(?=REJECTED:|$)", raw, re.DOTALL)
280
+ if synthesis_section:
281
+ for approach_match in re.finditer(
282
+ r"(\w+):\s*\[(.+?)\]", synthesis_section.group(1), re.DOTALL
283
+ ):
284
+ items = [i.strip() for i in approach_match.group(2).split(",") if i.strip()]
285
+ synthesis[approach_match.group(1)] = items
286
+
287
+ rejected = []
288
+ for match in re.finditer(
289
+ r"-\s*element:\s*(.+?)\n\s*reason:\s*(.+?)(?=\n\s*-\s*element:|\nRISKS:|$)", raw, re.DOTALL
290
+ ):
291
+ rejected.append(
292
+ RejectedElement(element=match.group(1).strip(), reason=match.group(2).strip())
293
+ )
294
+
295
+ risks = []
296
+ for match in re.finditer(
297
+ r"-\s*risk:\s*(.+?)\n\s*severity:\s*(\w+)\n\s*mitigation:\s*(.+?)(?=\n\s*-\s*risk:|$)",
298
+ raw,
299
+ re.DOTALL,
300
+ ):
301
+ severity_str = match.group(2).strip().lower()
302
+ severity = (
303
+ RiskSeverity.HIGH
304
+ if severity_str == "high"
305
+ else RiskSeverity.MEDIUM
306
+ if severity_str == "medium"
307
+ else RiskSeverity.LOW
308
+ )
309
+ risks.append(
310
+ IdentifiedRisk(
311
+ risk=match.group(1).strip(), mitigation=match.group(3).strip(), severity=severity
312
+ )
313
+ )
314
+
315
+ phases = []
316
+ phase_section = re.search(r"FINAL_PHASES:(.+?)$", raw, re.DOTALL)
317
+ if phase_section:
318
+ for match in re.finditer(
319
+ r"-\s*name:\s*(.+?)\n\s*department:\s*(\w+)\n\s*agents:\s*\[(.+?)\]\n\s*deliverables:\s*\[(.+?)\]\n\s*acceptance_criteria:\s*\[(.+?)\]\n\s*depends_on:\s*\[(.+?)\]",
320
+ phase_section.group(1),
321
+ re.DOTALL,
322
+ ):
323
+ phases.append(
324
+ PhaseDeliverable(
325
+ name=match.group(1).strip(),
326
+ deliverables=[d.strip() for d in match.group(4).split(",") if d.strip()],
327
+ agents=[a.strip() for a in match.group(3).split(",") if a.strip()],
328
+ acceptance_criteria=[c.strip() for c in match.group(5).split(",") if c.strip()],
329
+ depends_on=[d.strip() for d in match.group(6).split(",") if d.strip()],
330
+ )
331
+ )
332
+
333
+ return CriticVerdict(
334
+ synthesis=synthesis,
335
+ rejected_elements=rejected,
336
+ risks=risks,
337
+ confidence=confidence,
338
+ estimated_phases=len(phases),
339
+ )
340
+
341
+
342
+ class ForgeTaskDispatcher(ABC):
343
+ """Abstract base for Forge Task tool dispatchers.
344
+
345
+ Each runtime (Claude Code, Codex, Gemini CLI, Cursor) implements
346
+ this interface to dispatch explorer and critic subagents.
347
+ """
348
+
349
+ @abstractmethod
350
+ def dispatch_explorer(self, request: ExplorerDispatchRequest) -> DispatchResult:
351
+ """Dispatch an explorer subagent via Task tool.
352
+
353
+ Args:
354
+ request: Explorer dispatch request with lens, prompt, context
355
+
356
+ Returns:
357
+ DispatchResult with raw response text from the subagent
358
+ """
359
+
360
+ @abstractmethod
361
+ def dispatch_critic(self, request: CriticDispatchRequest) -> DispatchResult:
362
+ """Dispatch a critic subagent via Task tool.
363
+
364
+ Args:
365
+ request: Critic dispatch request with approaches and prompt
366
+
367
+ Returns:
368
+ DispatchResult with raw response text from the subagent
369
+ """
370
+
371
+ def dispatch_explorer_and_parse(self, request: ExplorerDispatchRequest) -> ExplorerApproach:
372
+ """Dispatch an explorer and parse its structured output."""
373
+ result = self.dispatch_explorer(request)
374
+ if not result.success:
375
+ raise RuntimeError(f"Explorer dispatch failed: {result.error}")
376
+ return _parse_explorer_output(result.raw_response, request.lens)
377
+
378
+ def dispatch_critic_and_parse(self, request: CriticDispatchRequest) -> CriticVerdict:
379
+ """Dispatch a critic and parse its structured output."""
380
+ result = self.dispatch_critic(request)
381
+ if not result.success:
382
+ raise RuntimeError(f"Critic dispatch failed: {result.error}")
383
+ return _parse_critic_output(result.raw_response)
384
+
385
+
386
+ class ClaudeCodeForgeDispatcher(ForgeTaskDispatcher):
387
+ """Claude Code implementation using the Task tool with model parameter.
388
+
389
+ Claude Code supports the Agent tool with model routing:
390
+ - haiku: fast, cost-optimized
391
+ - sonnet: balanced
392
+ - opus: highest capability
393
+ """
394
+
395
+ def dispatch_explorer(self, request: ExplorerDispatchRequest) -> DispatchResult:
396
+ """Dispatch explorer using Claude Code's Agent tool."""
397
+ prompt = _build_explorer_prompt(request)
398
+ raw_response = self._call_agent(prompt, model=request.model, agent_type="explorer")
399
+ return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
400
+
401
+ def dispatch_critic(self, request: CriticDispatchRequest) -> DispatchResult:
402
+ """Dispatch critic using Claude Code's Agent tool."""
403
+ prompt = _build_critic_prompt(request)
404
+ raw_response = self._call_agent(prompt, model=request.model, agent_type="critic")
405
+ return DispatchResult(success=True, raw_response=raw_response, output=raw_response)
406
+
407
+ def _call_agent(
408
+ self,
409
+ prompt: str,
410
+ model: str,
411
+ agent_type: str,
412
+ timeout: int = 120,
413
+ ) -> str:
414
+ """Call the Claude Code Agent tool.
415
+
416
+ This method should be overridden in tests to mock the actual
417
+ Task tool call. The real implementation uses the Agent tool
418
+ from Claude Code's toolkit.
419
+ """
420
+ from agentTool import Agent # type: ignore
421
+
422
+ task_result = Agent(
423
+ prompt=prompt,
424
+ model=model,
425
+ task_type=agent_type,
426
+ )
427
+ return task_result.output
428
+
429
+
430
+ def create_dispatcher(runtime: str | None = None) -> ForgeTaskDispatcher:
431
+ """Create the appropriate dispatcher for the current runtime.
432
+
433
+ Args:
434
+ runtime: Runtime identifier. If None, auto-detected.
435
+
436
+ Returns:
437
+ ForgeTaskDispatcher instance for the runtime
438
+ """
439
+ if runtime is None:
440
+ runtime = _detect_runtime()
441
+
442
+ dispatchers = {
443
+ "claude-code": ClaudeCodeForgeDispatcher,
444
+ "claude": ClaudeCodeForgeDispatcher,
445
+ }
446
+
447
+ dispatcher_class = dispatchers.get(runtime, ClaudeCodeForgeDispatcher)
448
+ return dispatcher_class()
449
+
450
+
451
+ def _detect_runtime() -> str:
452
+ """Auto-detect the current runtime environment."""
453
+ import os
454
+
455
+ if os.environ.get("ARKAOS_RUNTIME"):
456
+ return os.environ["ARKAOS_RUNTIME"]
457
+
458
+ try:
459
+ import claude
460
+
461
+ return "claude-code"
462
+ except ImportError:
463
+ pass
464
+
465
+ return "claude-code"