arkaos 2.0.0 → 2.0.2

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 (109) hide show
  1. package/README.md +100 -74
  2. package/VERSION +1 -1
  3. package/bin/arkaos +1 -1
  4. package/config/constitution.yaml +4 -0
  5. package/config/hooks/user-prompt-submit-v2.sh +20 -38
  6. package/core/__pycache__/__init__.cpython-313.pyc +0 -0
  7. package/core/agents/__pycache__/__init__.cpython-313.pyc +0 -0
  8. package/core/agents/__pycache__/loader.cpython-313.pyc +0 -0
  9. package/core/agents/__pycache__/schema.cpython-313.pyc +0 -0
  10. package/core/agents/__pycache__/validator.cpython-313.pyc +0 -0
  11. package/core/budget/__init__.py +6 -0
  12. package/core/budget/__pycache__/__init__.cpython-313.pyc +0 -0
  13. package/core/budget/__pycache__/manager.cpython-313.pyc +0 -0
  14. package/core/budget/__pycache__/schema.cpython-313.pyc +0 -0
  15. package/core/budget/manager.py +193 -0
  16. package/core/budget/schema.py +82 -0
  17. package/core/conclave/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/core/conclave/__pycache__/advisor_db.cpython-313.pyc +0 -0
  19. package/core/conclave/__pycache__/display.cpython-313.pyc +0 -0
  20. package/core/conclave/__pycache__/matcher.cpython-313.pyc +0 -0
  21. package/core/conclave/__pycache__/persistence.cpython-313.pyc +0 -0
  22. package/core/conclave/__pycache__/profiler.cpython-313.pyc +0 -0
  23. package/core/conclave/__pycache__/prompts.cpython-313.pyc +0 -0
  24. package/core/conclave/__pycache__/schema.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/__init__.cpython-313.pyc +0 -0
  26. package/core/governance/__pycache__/constitution.cpython-313.pyc +0 -0
  27. package/core/obsidian/__init__.py +6 -0
  28. package/core/obsidian/__pycache__/__init__.cpython-313.pyc +0 -0
  29. package/core/obsidian/__pycache__/templates.cpython-313.pyc +0 -0
  30. package/core/obsidian/__pycache__/writer.cpython-313.pyc +0 -0
  31. package/core/obsidian/templates.py +76 -0
  32. package/core/obsidian/writer.py +148 -0
  33. package/core/orchestration/__init__.py +6 -0
  34. package/core/orchestration/__pycache__/__init__.cpython-313.pyc +0 -0
  35. package/core/orchestration/__pycache__/patterns.cpython-313.pyc +0 -0
  36. package/core/orchestration/__pycache__/protocol.cpython-313.pyc +0 -0
  37. package/core/orchestration/patterns.py +136 -0
  38. package/core/orchestration/protocol.py +96 -0
  39. package/core/registry/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/core/registry/__pycache__/generator.cpython-313.pyc +0 -0
  41. package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  42. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  43. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  44. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  45. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  46. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  47. package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
  48. package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
  49. package/core/specs/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/core/specs/__pycache__/manager.cpython-313.pyc +0 -0
  51. package/core/specs/__pycache__/schema.cpython-313.pyc +0 -0
  52. package/core/squads/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/core/squads/__pycache__/loader.cpython-313.pyc +0 -0
  54. package/core/squads/__pycache__/registry.cpython-313.pyc +0 -0
  55. package/core/squads/__pycache__/schema.cpython-313.pyc +0 -0
  56. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  57. package/core/synapse/__pycache__/cache.cpython-313.pyc +0 -0
  58. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  59. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  60. package/core/tasks/__pycache__/__init__.cpython-313.pyc +0 -0
  61. package/core/tasks/__pycache__/manager.cpython-313.pyc +0 -0
  62. package/core/tasks/__pycache__/schema.cpython-313.pyc +0 -0
  63. package/core/tasks/schema.py +6 -0
  64. package/core/workflow/__pycache__/__init__.cpython-313.pyc +0 -0
  65. package/core/workflow/__pycache__/engine.cpython-313.pyc +0 -0
  66. package/core/workflow/__pycache__/loader.cpython-313.pyc +0 -0
  67. package/core/workflow/__pycache__/schema.cpython-313.pyc +0 -0
  68. package/core/workflow/engine.py +44 -0
  69. package/core/workflow/schema.py +1 -0
  70. package/departments/dev/skills/agent-design/SKILL.md +4 -0
  71. package/departments/dev/skills/agent-design/references/architecture-patterns.md +223 -0
  72. package/departments/dev/skills/ai-security/SKILL.md +4 -0
  73. package/departments/dev/skills/ai-security/references/prompt-injection-catalog.md +230 -0
  74. package/departments/dev/skills/ci-cd-pipeline/SKILL.md +4 -0
  75. package/departments/dev/skills/ci-cd-pipeline/references/github-actions-patterns.md +202 -0
  76. package/departments/dev/skills/db-schema/SKILL.md +4 -0
  77. package/departments/dev/skills/db-schema/references/indexing-strategy.md +197 -0
  78. package/departments/dev/skills/dependency-audit/SKILL.md +4 -0
  79. package/departments/dev/skills/dependency-audit/references/license-matrix.md +191 -0
  80. package/departments/dev/skills/incident/SKILL.md +4 -0
  81. package/departments/dev/skills/incident/references/severity-playbook.md +221 -0
  82. package/departments/dev/skills/observability/SKILL.md +4 -0
  83. package/departments/dev/skills/observability/references/slo-design.md +200 -0
  84. package/departments/dev/skills/rag-architect/SKILL.md +5 -0
  85. package/departments/dev/skills/rag-architect/references/chunking-strategies.md +129 -0
  86. package/departments/dev/skills/rag-architect/references/evaluation-guide.md +158 -0
  87. package/departments/dev/skills/red-team/SKILL.md +4 -0
  88. package/departments/dev/skills/red-team/references/mitre-attack-web.md +165 -0
  89. package/departments/dev/skills/security-audit/SKILL.md +4 -0
  90. package/departments/dev/skills/security-audit/references/owasp-2025-deep.md +409 -0
  91. package/departments/dev/skills/security-compliance/SKILL.md +117 -0
  92. package/departments/finance/skills/ciso-advisor/SKILL.md +4 -0
  93. package/departments/finance/skills/ciso-advisor/references/compliance-roadmap.md +172 -0
  94. package/departments/marketing/skills/programmatic-seo/SKILL.md +4 -0
  95. package/departments/marketing/skills/programmatic-seo/references/template-playbooks.md +289 -0
  96. package/departments/ops/skills/gdpr-compliance/SKILL.md +104 -0
  97. package/departments/ops/skills/iso27001/SKILL.md +113 -0
  98. package/departments/ops/skills/quality-management/SKILL.md +118 -0
  99. package/departments/ops/skills/risk-management/SKILL.md +120 -0
  100. package/departments/ops/skills/soc2-compliance/SKILL.md +120 -0
  101. package/departments/strategy/skills/cto-advisor/SKILL.md +4 -0
  102. package/departments/strategy/skills/cto-advisor/references/build-vs-buy-framework.md +190 -0
  103. package/installer/cli.js +13 -2
  104. package/installer/index.js +1 -2
  105. package/installer/migrate.js +123 -0
  106. package/installer/update.js +28 -15
  107. package/package.json +1 -1
  108. package/pyproject.toml +1 -1
  109. package/core/agents/__pycache__/registry_gen.cpython-313.pyc +0 -0
@@ -0,0 +1,6 @@
1
+ """Obsidian vault integration — write workflow outputs to knowledge base."""
2
+
3
+ from core.obsidian.writer import ObsidianWriter
4
+ from core.obsidian.templates import build_frontmatter
5
+
6
+ __all__ = ["ObsidianWriter", "build_frontmatter"]
@@ -0,0 +1,76 @@
1
+ """Obsidian frontmatter templates and conventions."""
2
+
3
+ from datetime import date, datetime
4
+ from typing import Any
5
+
6
+
7
+ def build_frontmatter(
8
+ department: str = "",
9
+ agent: str = "",
10
+ workflow: str = "",
11
+ tags: list[str] | None = None,
12
+ extra: dict[str, Any] | None = None,
13
+ ) -> str:
14
+ """Build YAML frontmatter for an Obsidian note.
15
+
16
+ Returns:
17
+ String with opening/closing --- delimiters.
18
+ """
19
+ fields: dict[str, Any] = {}
20
+ fields["created"] = datetime.now().strftime("%Y-%m-%d %H:%M")
21
+ fields["source"] = "arkaos"
22
+
23
+ if department:
24
+ fields["department"] = department
25
+ if agent:
26
+ fields["agent"] = agent
27
+ if workflow:
28
+ fields["workflow"] = workflow
29
+
30
+ all_tags = ["arkaos"]
31
+ if department:
32
+ all_tags.append(f"dept/{department}")
33
+ if tags:
34
+ all_tags.extend(tags)
35
+ fields["tags"] = all_tags
36
+
37
+ if extra:
38
+ fields.update(extra)
39
+
40
+ lines = ["---"]
41
+ for key, value in fields.items():
42
+ if isinstance(value, list):
43
+ lines.append(f"{key}:")
44
+ for item in value:
45
+ lines.append(f" - {item}")
46
+ elif isinstance(value, bool):
47
+ lines.append(f"{key}: {'true' if value else 'false'}")
48
+ else:
49
+ lines.append(f"{key}: {value}")
50
+ lines.append("---")
51
+
52
+ return "\n".join(lines)
53
+
54
+
55
+ def resolve_template_vars(path: str, vars: dict[str, str] | None = None) -> str:
56
+ """Resolve template variables in an Obsidian path.
57
+
58
+ Supported variables:
59
+ {project}, {department}, {agent}, {date}, {number}, {name}
60
+ """
61
+ defaults = {
62
+ "date": date.today().isoformat(),
63
+ "project": "Default",
64
+ "department": "general",
65
+ "agent": "unknown",
66
+ "number": "001",
67
+ "name": "untitled",
68
+ }
69
+ if vars:
70
+ defaults.update(vars)
71
+
72
+ result = path
73
+ for key, value in defaults.items():
74
+ result = result.replace(f"{{{key}}}", value)
75
+
76
+ return result
@@ -0,0 +1,148 @@
1
+ """Obsidian vault writer — save workflow outputs to knowledge base.
2
+
3
+ Resolves vault path from:
4
+ 1. Constructor argument
5
+ 2. knowledge/obsidian-config.json → vault_path
6
+ 3. ARKAOS_VAULT environment variable
7
+ 4. Fallback: ~/.arkaos/vault/
8
+ """
9
+
10
+ import json
11
+ import os
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Any, Optional
15
+
16
+ from core.obsidian.templates import build_frontmatter, resolve_template_vars
17
+
18
+
19
+ class ObsidianWriter:
20
+ """Writes markdown files to an Obsidian vault with frontmatter."""
21
+
22
+ def __init__(self, vault_path: str | Path | None = None, arkaos_root: str | Path | None = None) -> None:
23
+ self._vault_path = self._resolve_vault_path(vault_path, arkaos_root)
24
+
25
+ def save(
26
+ self,
27
+ obsidian_path: str,
28
+ content: str,
29
+ department: str = "",
30
+ agent: str = "",
31
+ workflow: str = "",
32
+ tags: list[str] | None = None,
33
+ template_vars: dict[str, str] | None = None,
34
+ extra_frontmatter: dict[str, Any] | None = None,
35
+ ) -> Path:
36
+ """Save content to the Obsidian vault.
37
+
38
+ Args:
39
+ obsidian_path: Relative path within vault (may contain template vars).
40
+ content: Markdown content to save.
41
+ department: Source department.
42
+ agent: Agent that produced the output.
43
+ workflow: Workflow that generated this output.
44
+ tags: Additional tags for frontmatter.
45
+ template_vars: Variables to resolve in path ({project}, {date}, etc.).
46
+ extra_frontmatter: Additional frontmatter fields.
47
+
48
+ Returns:
49
+ Absolute path to the saved file.
50
+ """
51
+ # Resolve template variables in path
52
+ resolved_path = resolve_template_vars(obsidian_path, template_vars)
53
+
54
+ # Build full path
55
+ full_path = self._vault_path / resolved_path
56
+
57
+ # If path doesn't end with .md, treat as directory and add filename
58
+ if not full_path.suffix:
59
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H%M")
60
+ filename = f"{department or 'output'}-{timestamp}.md"
61
+ full_path = full_path / filename
62
+ elif full_path.suffix != ".md":
63
+ full_path = full_path.with_suffix(".md")
64
+
65
+ # Handle duplicate filenames
66
+ if full_path.exists():
67
+ stem = full_path.stem
68
+ timestamp = datetime.now().strftime("%H%M%S")
69
+ full_path = full_path.with_stem(f"{stem}-{timestamp}")
70
+
71
+ # Create directories
72
+ full_path.parent.mkdir(parents=True, exist_ok=True)
73
+
74
+ # Build frontmatter
75
+ frontmatter = build_frontmatter(
76
+ department=department,
77
+ agent=agent,
78
+ workflow=workflow,
79
+ tags=tags,
80
+ extra=extra_frontmatter,
81
+ )
82
+
83
+ # Write file
84
+ file_content = f"{frontmatter}\n\n{content}"
85
+ full_path.write_text(file_content, encoding="utf-8")
86
+
87
+ return full_path
88
+
89
+ def ensure_vault(self) -> bool:
90
+ """Verify the vault directory exists."""
91
+ return self._vault_path.exists()
92
+
93
+ def list_outputs(self, department: str = "", limit: int = 50) -> list[Path]:
94
+ """List recent ArkaOS outputs in the vault."""
95
+ if not self._vault_path.exists():
96
+ return []
97
+
98
+ pattern = "**/*.md"
99
+ files = sorted(self._vault_path.glob(pattern), key=lambda p: p.stat().st_mtime, reverse=True)
100
+
101
+ results = []
102
+ for f in files:
103
+ if len(results) >= limit:
104
+ break
105
+ try:
106
+ head = f.read_text(encoding="utf-8")[:200]
107
+ if "source: arkaos" in head:
108
+ if not department or f"department: {department}" in head:
109
+ results.append(f)
110
+ except (OSError, UnicodeDecodeError):
111
+ continue
112
+
113
+ return results
114
+
115
+ @property
116
+ def vault_path(self) -> Path:
117
+ return self._vault_path
118
+
119
+ def _resolve_vault_path(self, explicit: str | Path | None, arkaos_root: str | Path | None) -> Path:
120
+ """Resolve vault path from multiple sources."""
121
+ # 1. Explicit argument
122
+ if explicit:
123
+ return Path(explicit)
124
+
125
+ # 2. Config file
126
+ if arkaos_root:
127
+ config_path = Path(arkaos_root) / "knowledge" / "obsidian-config.json"
128
+ else:
129
+ config_path = Path(__file__).resolve().parent.parent.parent / "knowledge" / "obsidian-config.json"
130
+
131
+ if config_path.exists():
132
+ try:
133
+ config = json.loads(config_path.read_text())
134
+ vault = config.get("vault_path", "")
135
+ if vault and Path(vault).exists():
136
+ return Path(vault)
137
+ except (json.JSONDecodeError, OSError):
138
+ pass
139
+
140
+ # 3. Environment variable
141
+ env_vault = os.environ.get("ARKAOS_VAULT", "")
142
+ if env_vault and Path(env_vault).exists():
143
+ return Path(env_vault)
144
+
145
+ # 4. Fallback
146
+ fallback = Path.home() / ".arkaos" / "vault"
147
+ fallback.mkdir(parents=True, exist_ok=True)
148
+ return fallback
@@ -0,0 +1,6 @@
1
+ """Orchestration protocol — coordination patterns for multi-agent work."""
2
+
3
+ from core.orchestration.protocol import OrchestrationPattern, PhaseHandoff, OrchestrationPlan
4
+ from core.orchestration.patterns import PATTERNS
5
+
6
+ __all__ = ["OrchestrationPattern", "PhaseHandoff", "OrchestrationPlan", "PATTERNS"]
@@ -0,0 +1,136 @@
1
+ """Built-in orchestration patterns.
2
+
3
+ Four patterns adapted from claude-skills orchestration protocol,
4
+ mapped to ArkaOS department/agent/skill system.
5
+ """
6
+
7
+ from core.orchestration.protocol import OrchestrationPattern, PatternType
8
+
9
+
10
+ SOLO_SPRINT = OrchestrationPattern(
11
+ type=PatternType.SOLO_SPRINT,
12
+ name="Solo Sprint",
13
+ description="One department lead drives a multi-phase sprint, pulling skills from their squad. Fast, focused execution.",
14
+ when_to_use=[
15
+ "Single-domain task with clear scope",
16
+ "Time-constrained delivery (< 1 week)",
17
+ "One department has all required expertise",
18
+ ],
19
+ structure="Lead → Phase 1 (skills A,B) → Phase 2 (skills C,D) → Quality Gate → Deliver",
20
+ example=(
21
+ "Objective: Launch MVP landing page\n"
22
+ "Lead: Ines (Landing)\n"
23
+ "Phase 1: /landing copy-framework + /landing funnel-design\n"
24
+ "Phase 2: /landing page-build + /landing seo-optimize\n"
25
+ "Phase 3: Quality Gate → Ship"
26
+ ),
27
+ anti_patterns=[
28
+ "Using for cross-department work (use Multi-Agent Handoff instead)",
29
+ "Skipping Quality Gate because 'it's just one department'",
30
+ ],
31
+ )
32
+
33
+ DOMAIN_DEEP_DIVE = OrchestrationPattern(
34
+ type=PatternType.DOMAIN_DEEP_DIVE,
35
+ name="Domain Deep-Dive",
36
+ description="One agent, multiple stacked skills for thorough analysis. Deep expertise over breadth.",
37
+ when_to_use=[
38
+ "Complex technical audit or review",
39
+ "Due diligence or compliance assessment",
40
+ "Research requiring depth in one area",
41
+ ],
42
+ structure="Agent → Skill 1 (foundation) → Skill 2 (analysis) → Skill 3 (recommendations) → Report",
43
+ example=(
44
+ "Objective: Full security assessment\n"
45
+ "Agent: Bruno (Security Engineer)\n"
46
+ "Skills stacked:\n"
47
+ " 1. /dev security-audit (OWASP scan)\n"
48
+ " 2. /dev dependency-audit (CVE check)\n"
49
+ " 3. /dev red-team (attack simulation)\n"
50
+ " 4. /dev ai-security (LLM-specific risks)\n"
51
+ "Output: Consolidated security report with severity rankings"
52
+ ),
53
+ anti_patterns=[
54
+ "Stacking unrelated skills (each skill should build on previous)",
55
+ "Skipping the foundational skill and going straight to advanced",
56
+ "No consolidated output (each skill reports independently)",
57
+ ],
58
+ )
59
+
60
+ MULTI_AGENT_HANDOFF = OrchestrationPattern(
61
+ type=PatternType.MULTI_AGENT_HANDOFF,
62
+ name="Multi-Agent Handoff",
63
+ description="Work flows between departments with structured handoffs. Each department adds its expertise.",
64
+ when_to_use=[
65
+ "Cross-department projects (product launch, brand + marketing)",
66
+ "Sequential expertise needed (strategy → dev → marketing)",
67
+ "Each phase requires different domain knowledge",
68
+ ],
69
+ structure="Dept A → Handoff → Dept B → Handoff → Dept C → Quality Gate → Deliver",
70
+ example=(
71
+ "Objective: Launch new SaaS product\n"
72
+ "Phase 1: Tomas (Strategy) → /strat bmc + /strat five-forces\n"
73
+ " Handoff: Market validation, pricing strategy, competitive position\n"
74
+ "Phase 2: Paulo (Dev) → /dev spec + /dev feature\n"
75
+ " Handoff: MVP built, deployed to staging\n"
76
+ "Phase 3: Luna (Marketing) → /mkt growth-plan + /mkt email-sequence\n"
77
+ " Handoff: Landing page, email funnel, launch plan\n"
78
+ "Phase 4: Quality Gate → Launch"
79
+ ),
80
+ anti_patterns=[
81
+ "No handoff context (next department starts from zero)",
82
+ "Too many departments (>4 phases becomes unwieldy)",
83
+ "Skipping departments that should review (e.g., security for a public launch)",
84
+ ],
85
+ )
86
+
87
+ SKILL_CHAIN = OrchestrationPattern(
88
+ type=PatternType.SKILL_CHAIN,
89
+ name="Skill Chain",
90
+ description="Sequential procedural skills with no specific agent identity. Pure execution pipeline.",
91
+ when_to_use=[
92
+ "Procedural work with well-defined inputs/outputs",
93
+ "Automated pipelines (generate → validate → publish)",
94
+ "No judgment calls needed, just execution",
95
+ ],
96
+ structure="Skill A (input) → Skill B (transform) → Skill C (output) → Done",
97
+ example=(
98
+ "Objective: Content production pipeline\n"
99
+ "Chain:\n"
100
+ " 1. /content hook-write → 10 headline options\n"
101
+ " 2. python scripts/tools/headline_scorer.py → scored + ranked\n"
102
+ " 3. /content viral-design → full post with winning hook\n"
103
+ " 4. /mkt social-strategy → distribution plan\n"
104
+ "No persona needed — pure skill execution"
105
+ ),
106
+ anti_patterns=[
107
+ "Using for work that needs judgment (use Solo Sprint or Handoff)",
108
+ "Long chains with no validation checkpoints",
109
+ "Mixing judgment skills with procedural skills",
110
+ ],
111
+ )
112
+
113
+
114
+ PATTERNS = {
115
+ PatternType.SOLO_SPRINT: SOLO_SPRINT,
116
+ PatternType.DOMAIN_DEEP_DIVE: DOMAIN_DEEP_DIVE,
117
+ PatternType.MULTI_AGENT_HANDOFF: MULTI_AGENT_HANDOFF,
118
+ PatternType.SKILL_CHAIN: SKILL_CHAIN,
119
+ }
120
+
121
+
122
+ def select_pattern(
123
+ departments_involved: int,
124
+ needs_judgment: bool,
125
+ is_sequential: bool,
126
+ ) -> PatternType:
127
+ """Recommend an orchestration pattern based on task characteristics."""
128
+ if departments_involved == 1 and needs_judgment:
129
+ return PatternType.SOLO_SPRINT
130
+ if departments_involved == 1 and not needs_judgment:
131
+ return PatternType.SKILL_CHAIN
132
+ if departments_involved > 1 and is_sequential:
133
+ return PatternType.MULTI_AGENT_HANDOFF
134
+ if departments_involved == 1:
135
+ return PatternType.DOMAIN_DEEP_DIVE
136
+ return PatternType.MULTI_AGENT_HANDOFF
@@ -0,0 +1,96 @@
1
+ """Orchestration protocol schemas.
2
+
3
+ Defines the structure for multi-agent coordination across departments.
4
+ Four patterns: Solo Sprint, Domain Deep-Dive, Multi-Agent Handoff, Skill Chain.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class PatternType(str, Enum):
14
+ SOLO_SPRINT = "solo_sprint"
15
+ DOMAIN_DEEP_DIVE = "domain_deep_dive"
16
+ MULTI_AGENT_HANDOFF = "multi_agent_handoff"
17
+ SKILL_CHAIN = "skill_chain"
18
+
19
+
20
+ class PhaseHandoff(BaseModel):
21
+ """Context passed between orchestration phases."""
22
+ phase_number: int
23
+ phase_name: str
24
+ decisions: list[str] = Field(default_factory=list)
25
+ artifacts: list[str] = Field(default_factory=list)
26
+ open_questions: list[str] = Field(default_factory=list)
27
+ next_department: str = ""
28
+ next_agent: str = ""
29
+ next_skills: list[str] = Field(default_factory=list)
30
+
31
+ def to_context(self) -> str:
32
+ """Render handoff as context string for next phase."""
33
+ lines = [f"Phase {self.phase_number} ({self.phase_name}) complete."]
34
+ if self.decisions:
35
+ lines.append(f"Decisions: {', '.join(self.decisions)}")
36
+ if self.artifacts:
37
+ lines.append(f"Artifacts: {', '.join(self.artifacts)}")
38
+ if self.open_questions:
39
+ lines.append(f"Open questions: {', '.join(self.open_questions)}")
40
+ if self.next_department:
41
+ lines.append(f"Next: {self.next_department}/{self.next_agent}")
42
+ return "\n".join(lines)
43
+
44
+
45
+ class OrchestrationPhase(BaseModel):
46
+ """A single phase in an orchestration plan."""
47
+ number: int
48
+ name: str
49
+ department: str
50
+ agent_id: str
51
+ skills: list[str] = Field(default_factory=list)
52
+ objective: str = ""
53
+ outputs: list[str] = Field(default_factory=list)
54
+ gate: str = "user_approval" # user_approval, auto, quality_gate
55
+
56
+
57
+ class OrchestrationPlan(BaseModel):
58
+ """A complete orchestration plan spanning multiple departments."""
59
+ objective: str
60
+ pattern: PatternType
61
+ constraints: list[str] = Field(default_factory=list)
62
+ success_criteria: list[str] = Field(default_factory=list)
63
+ phases: list[OrchestrationPhase] = Field(default_factory=list)
64
+ current_phase: int = 0
65
+
66
+ def next_phase(self) -> Optional[OrchestrationPhase]:
67
+ """Get the next phase to execute."""
68
+ if self.current_phase < len(self.phases):
69
+ return self.phases[self.current_phase]
70
+ return None
71
+
72
+ def advance(self, handoff: PhaseHandoff) -> Optional[OrchestrationPhase]:
73
+ """Record handoff and advance to next phase."""
74
+ self.current_phase += 1
75
+ return self.next_phase()
76
+
77
+ @property
78
+ def is_complete(self) -> bool:
79
+ return self.current_phase >= len(self.phases)
80
+
81
+ @property
82
+ def progress_percent(self) -> int:
83
+ if not self.phases:
84
+ return 0
85
+ return int((self.current_phase / len(self.phases)) * 100)
86
+
87
+
88
+ class OrchestrationPattern(BaseModel):
89
+ """Definition of a coordination pattern."""
90
+ type: PatternType
91
+ name: str
92
+ description: str
93
+ when_to_use: list[str] = Field(default_factory=list)
94
+ structure: str = ""
95
+ example: str = ""
96
+ anti_patterns: list[str] = Field(default_factory=list)
@@ -47,6 +47,12 @@ class Task(BaseModel):
47
47
  output_data: dict[str, Any] = Field(default_factory=dict)
48
48
  output_path: str = "" # File path for output
49
49
 
50
+ # Budget
51
+ tokens_estimated: int = 0 # Pre-execution estimate
52
+ tokens_actual: int = 0 # Post-execution actual
53
+ budget_approved: bool = True # True if within budget or approved
54
+ approved_by: str = "" # Tier 0 agent who approved overrun
55
+
50
56
  # Progress
51
57
  progress_percent: int = 0 # 0-100
52
58
  progress_message: str = ""