@mamdouh-aboammar/agentic-workflow 1.2.0

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 (118) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +13 -0
  3. package/.skills.json +19 -0
  4. package/AGENTS.md +1344 -0
  5. package/CLAUDE.md +178 -0
  6. package/GEMINI.md +102 -0
  7. package/LICENSE +21 -0
  8. package/README.md +350 -0
  9. package/SKILL.md +132 -0
  10. package/bin/agentic-hooks.sh +79 -0
  11. package/bin/cli.js +1060 -0
  12. package/core/__init__.py +52 -0
  13. package/core/ai_evaluator.py +117 -0
  14. package/core/autopilot_engine.py +368 -0
  15. package/core/clean_code_guard.py +188 -0
  16. package/core/engine_py/__init__.py +29 -0
  17. package/core/engine_py/agent_worker.py +136 -0
  18. package/core/engine_py/decider.py +150 -0
  19. package/core/engine_py/energy.py +45 -0
  20. package/core/engine_py/event_bus.py +63 -0
  21. package/core/engine_py/executor.py +186 -0
  22. package/core/engine_py/models.py +193 -0
  23. package/core/engine_py/queue.py +314 -0
  24. package/core/engine_py/runner.py +116 -0
  25. package/core/engine_py/system_workers.py +70 -0
  26. package/core/engine_py/toon_adapter.py +586 -0
  27. package/core/engine_py/verification_controller.py +208 -0
  28. package/core/engine_py/worker.py +167 -0
  29. package/core/engine_spec/event_schema.json +65 -0
  30. package/core/engine_spec/example_workflow.yaml +73 -0
  31. package/core/engine_spec/workflow_schema.json +127 -0
  32. package/core/hooks/__init__.py +29 -0
  33. package/core/hooks/adapters/__init__.py +25 -0
  34. package/core/hooks/adapters/claude_adapter.py +83 -0
  35. package/core/hooks/adapters/cli_agent_adapter.py +82 -0
  36. package/core/hooks/adapters/codex_adapter.py +78 -0
  37. package/core/hooks/adapters/cursor_adapter.py +73 -0
  38. package/core/hooks/adapters/gemini_adapter.py +93 -0
  39. package/core/hooks/adapters/homebrew_adapter.py +69 -0
  40. package/core/hooks/adapters/mcp_proxy.py +133 -0
  41. package/core/hooks/adapters/shell_adapter.py +65 -0
  42. package/core/hooks/dispatcher.py +118 -0
  43. package/core/hooks/policy_engine.py +375 -0
  44. package/core/hooks/session_end.py +141 -0
  45. package/core/hooks/types.py +147 -0
  46. package/core/integrations/__init__.py +28 -0
  47. package/core/integrations/installer.py +225 -0
  48. package/core/integrations/lifecycle_director.py +175 -0
  49. package/core/integrations/registry.py +105 -0
  50. package/core/multi_agent_system.py +164 -0
  51. package/core/skills_indexer.py +742 -0
  52. package/core/system/__init__.py +25 -0
  53. package/core/system/announcements.py +72 -0
  54. package/core/system/dependencies.py +69 -0
  55. package/core/system/doctor.py +171 -0
  56. package/core/system/health.py +144 -0
  57. package/core/system/installer.py +137 -0
  58. package/core/system/notifications.py +97 -0
  59. package/core/system/refresher.py +110 -0
  60. package/core/system/updater.py +167 -0
  61. package/core/system/version_tracker.py +65 -0
  62. package/docs/architecture_plan.md +7 -0
  63. package/docs/guides/failure-recovery.md +714 -0
  64. package/docs/implementation_summary.md +10 -0
  65. package/docs/protocols/autopilot-execution.md +148 -0
  66. package/docs/protocols/code-change-protocol.md +49 -0
  67. package/docs/protocols/context-preservation-detail.md +114 -0
  68. package/docs/protocols/quality-gates.md +110 -0
  69. package/docs/protocols/ulw-mode.md +60 -0
  70. package/docs/research_findings.md +10 -0
  71. package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
  72. package/install.sh +111 -0
  73. package/marketplace.json +37 -0
  74. package/package.json +81 -0
  75. package/skills/agentic-workflow/SKILL.md +132 -0
  76. package/skills/agentic-workflow/skill-spec.json +100 -0
  77. package/soul.md +445 -0
  78. package/src/engine_ts/decider.ts +186 -0
  79. package/src/engine_ts/event-bus.ts +57 -0
  80. package/src/engine_ts/executor.ts +262 -0
  81. package/src/engine_ts/index.ts +12 -0
  82. package/src/engine_ts/queue.ts +93 -0
  83. package/src/engine_ts/runner.ts +108 -0
  84. package/src/engine_ts/skills-indexer.ts +264 -0
  85. package/src/engine_ts/toon-adapter.ts +91 -0
  86. package/src/engine_ts/types.ts +134 -0
  87. package/src/engine_ts/verification-controller.ts +204 -0
  88. package/src/engine_ts/worker.ts +280 -0
  89. package/src/hooks/adapters/claude-adapter.ts +54 -0
  90. package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
  91. package/src/hooks/adapters/codex-adapter.ts +69 -0
  92. package/src/hooks/adapters/cursor-adapter.ts +60 -0
  93. package/src/hooks/adapters/gemini-adapter.ts +71 -0
  94. package/src/hooks/adapters/homebrew-adapter.ts +36 -0
  95. package/src/hooks/adapters/mcp-proxy.ts +66 -0
  96. package/src/hooks/adapters/shell-adapter.ts +42 -0
  97. package/src/hooks/dispatcher.ts +113 -0
  98. package/src/hooks/index.ts +16 -0
  99. package/src/hooks/policy-engine.ts +376 -0
  100. package/src/hooks/session-end.ts +125 -0
  101. package/src/hooks/types.ts +61 -0
  102. package/src/index.d.ts +34 -0
  103. package/src/index.ts +23 -0
  104. package/src/integrations/index.ts +7 -0
  105. package/src/integrations/installer.ts +208 -0
  106. package/src/integrations/lifecycle-director.ts +139 -0
  107. package/src/integrations/registry.ts +82 -0
  108. package/src/system/announcements.ts +143 -0
  109. package/src/system/dependencies.ts +176 -0
  110. package/src/system/doctor.ts +374 -0
  111. package/src/system/health.ts +270 -0
  112. package/src/system/index.ts +14 -0
  113. package/src/system/installer.ts +262 -0
  114. package/src/system/notifications.ts +180 -0
  115. package/src/system/refresher.ts +207 -0
  116. package/src/system/types.ts +268 -0
  117. package/src/system/updater.ts +219 -0
  118. package/src/system/version-tracker.ts +137 -0
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ lifecycle_director.py — Sequential Operational Lifecycle Director
4
+
5
+ Directs the entire autonomous workflow sequentially across 5 core phases:
6
+ 1. Continuous Protocol Layer (TOON v4.1 & Caveman Brevity)
7
+ 2. Research & Intelligence (Skills Mesh & Fable Discover)
8
+ 3. Architecture & Planning (Ponytail YAGNI Ladder Rung 1-3 & Fable Plan)
9
+ 4. Production Implementation (Ponytail Rung 4-7 Surgical Diff & Fable Circuit Breaker)
10
+ 5. Multi-Pass Verification & Quality Gates (Clean Code Guard, Ponytail Audit & L0-L2)
11
+ 6. Handoff & Continuation State (Fable Handoff & TOON Ledger)
12
+
13
+ Ensures supportive tools are automatically employed in the correct place without
14
+ requiring manual user decision.
15
+ """
16
+
17
+ import os
18
+ import json
19
+ import time
20
+ from dataclasses import dataclass, field
21
+ from typing import Dict, List, Optional, Any
22
+
23
+ from core.integrations.registry import IntegrationsRegistry, get_default_registry
24
+
25
+
26
+ @dataclass
27
+ class PhaseDirectives:
28
+ phase: str
29
+ active_integrations: List[str]
30
+ system_prompt_overlay: str
31
+ rules: List[str]
32
+ tools_engaged: List[str]
33
+
34
+
35
+ class LifecycleDirector:
36
+ """Orchestrates supportive tools sequentially throughout the workflow lifecycle."""
37
+
38
+ def __init__(self, project_dir: str = ".", registry: Optional[IntegrationsRegistry] = None):
39
+ self.project_dir = os.path.abspath(project_dir)
40
+ self.registry = registry or get_default_registry(self.project_dir)
41
+
42
+ def get_phase_directives(self, phase: str) -> PhaseDirectives:
43
+ """Synthesizes unified operational directives for the specified workflow phase."""
44
+ phase_norm = phase.lower().strip()
45
+ matched = self.registry.get_for_phase(phase_norm)
46
+
47
+ active_names = [m.name for m in matched]
48
+ tools_engaged = [m.id for m in matched]
49
+ rules = []
50
+ directive_sections = []
51
+
52
+ # 1. Continuous protocols (TOON & Caveman)
53
+ toon_item = self.registry.get("toon")
54
+ if toon_item and "continuous" in toon_item.directives:
55
+ directive_sections.append(f"### [Continuous Protocol] TOON v4.1 Serialization\n{toon_item.directives['continuous']}")
56
+ rules.append("Format all structured datasets, task tables, and trace logs in TOON syntax to conserve 30-60% tokens.")
57
+
58
+ caveman_item = self.registry.get("caveman")
59
+ if caveman_item and "continuous" in caveman_item.directives:
60
+ directive_sections.append(f"### [Communication Protocol] Caveman Terse Mode\n{caveman_item.directives['continuous']}")
61
+ rules.append("Eliminate pleasantries and conversational filler in logs and thoughts; preserve exact code, paths, and errors.")
62
+
63
+ # 2. Phase-specific directives
64
+ for item in matched:
65
+ if phase_norm in item.directives:
66
+ directive_sections.append(f"### [{item.name}] Phase Directives ({phase_norm.capitalize()})\n{item.directives[phase_norm]}")
67
+ rules.append(f"[{item.name}] {item.directives[phase_norm]}")
68
+
69
+ # Phase-specific synthesized overview
70
+ if phase_norm == "planning":
71
+ overview = (
72
+ "## 🧭 Sequential Lifecycle Directive: Phase 2 — Architecture & Planning\n"
73
+ "Before approving or implementing any architectural design, you MUST enforce the Ponytail YAGNI ladder:\n"
74
+ "1. Does this speculative requirement need to exist at all? If not, skip it.\n"
75
+ "2. Is a helper or pattern already present in this codebase? Reuse it.\n"
76
+ "3. Does the standard library or runtime platform cover it? Use it.\n"
77
+ "4. Structure deliverables as verifiable Fable contracts with explicit success criteria.\n"
78
+ "5. Compile and validate OmniSkill SkillSpec contracts and dynamic DAG execution paths."
79
+ )
80
+ elif phase_norm == "implementation":
81
+ overview = (
82
+ "## 🔨 Sequential Lifecycle Directive: Phase 3 — Production Implementation\n"
83
+ "Enforce surgical code changes:\n"
84
+ "1. Shortest working diff wins. Minimum code needed to fulfill requirements.\n"
85
+ "2. Fix root causes at callers/callees, not symptoms.\n"
86
+ "3. Fable Circuit Breaker is active: if failure streak >= 2, halt speculative modifications.\n"
87
+ "4. Follow OmniSkill progressive disclosure: frontmatter <=1024 chars, core SKILL.md, references/, scripts/."
88
+ )
89
+ elif phase_norm == "verification":
90
+ overview = (
91
+ "## 🛡️ Sequential Lifecycle Directive: Phase 4 — Verification & Quality Gates\n"
92
+ "1. Run Clean Code Guard (SOLID, 24 Imperatives).\n"
93
+ "2. Conduct Ponytail Anti-Debt audit: inspect for unnecessary scaffolding, dead config, or bloat.\n"
94
+ "3. Execute L0 Anti-Skip, L1 Verification, L1.5 pACS (min score >= 70), and L2 Review.\n"
95
+ "4. Enforce OmniSkill 4-Layer Validation (Artifact, Discovery, Behavior, Portability)."
96
+ )
97
+ elif phase_norm == "handoff":
98
+ overview = (
99
+ "## 🏁 Sequential Lifecycle Directive: Phase 5 — Handoff & Continuation\n"
100
+ "1. Compact session learnings into durable continuation state (.fable/state.json and .fable/PROGRESS.md).\n"
101
+ "2. Archive telemetry and audit logs using high-density TOON format."
102
+ )
103
+ else:
104
+ overview = f"## ⚡ Sequential Lifecycle Directive: {phase_norm.capitalize()} Phase"
105
+
106
+ system_prompt = f"{overview}\n\n" + "\n\n".join(directive_sections)
107
+
108
+ return PhaseDirectives(
109
+ phase=phase_norm,
110
+ active_integrations=active_names,
111
+ system_prompt_overlay=system_prompt,
112
+ rules=rules,
113
+ tools_engaged=tools_engaged
114
+ )
115
+
116
+ def execute_pre_phase_guards(self, phase: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
117
+ """Runs automated pre-phase safety gates and policy evaluations."""
118
+ ctx = context or {}
119
+ phase_norm = phase.lower().strip()
120
+ result = {
121
+ "phase": phase_norm,
122
+ "allowed": True,
123
+ "warnings": [],
124
+ "actions_taken": []
125
+ }
126
+
127
+ # Check circuit breaker if in implementation
128
+ if phase_norm == "implementation":
129
+ failure_streak = ctx.get("failure_streak", 0)
130
+ if failure_streak >= 2:
131
+ result["allowed"] = False
132
+ result["warnings"].append(
133
+ f"Fable Circuit Breaker TRIPPED: Consecutive failures ({failure_streak}) >= 2. "
134
+ "Halting speculative execution to prevent thrashing."
135
+ )
136
+ result["actions_taken"].append("trip_circuit_breaker")
137
+
138
+ # Check planning simplicity gate
139
+ if phase_norm == "planning":
140
+ result["actions_taken"].append("enforce_ponytail_yagni_gate")
141
+
142
+ return result
143
+
144
+ def execute_post_phase_actions(self, phase: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
145
+ """Executes automated post-phase validations and handoffs."""
146
+ ctx = context or {}
147
+ phase_norm = phase.lower().strip()
148
+ result = {
149
+ "phase": phase_norm,
150
+ "success": True,
151
+ "artifacts_generated": []
152
+ }
153
+
154
+ # If handoff phase, ensure .fable continuation state
155
+ if phase_norm == "handoff":
156
+ fable_dir = os.path.join(self.project_dir, ".fable")
157
+ os.makedirs(fable_dir, exist_ok=True)
158
+ state_file = os.path.join(fable_dir, "state.json")
159
+ progress_file = os.path.join(fable_dir, "PROGRESS.md")
160
+
161
+ state_payload = {
162
+ "trace_id": ctx.get("trace_id", f"trace_{int(time.time())}"),
163
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
164
+ "status": "COMPLETED",
165
+ "next_action": ctx.get("next_action", "All workflow stages verified cleanly.")
166
+ }
167
+ with open(state_file, "w", encoding="utf-8") as f:
168
+ json.dump(state_payload, f, indent=2)
169
+
170
+ with open(progress_file, "w", encoding="utf-8") as f:
171
+ f.write(f"# Fable Continuation Progress\n\n- Timestamp: {state_payload['timestamp']}\n- Trace: `{state_payload['trace_id']}`\n- Next Action: {state_payload['next_action']}\n")
172
+
173
+ result["artifacts_generated"].extend([state_file, progress_file])
174
+
175
+ return result
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ registry.py — Integrations Registry & Manifest Manager
4
+
5
+ Maintains declarative metadata for supportive tools, frameworks, and agentic skills
6
+ such as Ponytail, TOON, Fable, and Caveman, supporting native dynamic expansion.
7
+ """
8
+
9
+ import os
10
+ import json
11
+ from dataclasses import dataclass, field, asdict
12
+ from typing import Dict, List, Optional, Any
13
+
14
+
15
+ @dataclass
16
+ class IntegrationDefinition:
17
+ id: str
18
+ name: str
19
+ repo: str
20
+ description: str
21
+ category: str # simplicity_governor, notation_protocol, lifecycle_harness, terse_comm, etc.
22
+ lifecycle_phases: List[str] = field(default_factory=list)
23
+ install: Dict[str, Any] = field(default_factory=dict)
24
+ detection: Dict[str, Any] = field(default_factory=dict)
25
+ directives: Dict[str, str] = field(default_factory=dict)
26
+
27
+ def to_dict(self) -> Dict[str, Any]:
28
+ return asdict(self)
29
+
30
+ @classmethod
31
+ def from_dict(cls, data: Dict[str, Any]) -> "IntegrationDefinition":
32
+ return cls(
33
+ id=data["id"],
34
+ name=data.get("name", data["id"]),
35
+ repo=data.get("repo", ""),
36
+ description=data.get("description", ""),
37
+ category=data.get("category", "general"),
38
+ lifecycle_phases=data.get("lifecycle_phases", []),
39
+ install=data.get("install", {}),
40
+ detection=data.get("detection", {}),
41
+ directives=data.get("directives", {})
42
+ )
43
+
44
+
45
+ class IntegrationsRegistry:
46
+ """Manages the full catalog of supportive integrations."""
47
+
48
+ def __init__(self, manifest_path: Optional[str] = None):
49
+ self.manifest_path = manifest_path
50
+ self._integrations: Dict[str, IntegrationDefinition] = {}
51
+ if manifest_path and os.path.exists(manifest_path):
52
+ self.load_from_file(manifest_path)
53
+
54
+ def load_from_file(self, path: str) -> None:
55
+ """Loads integrations manifest from JSON."""
56
+ self.manifest_path = os.path.abspath(path)
57
+ with open(self.manifest_path, "r", encoding="utf-8") as f:
58
+ data = json.load(f)
59
+ self._integrations.clear()
60
+ for item in data.get("integrations", []):
61
+ defn = IntegrationDefinition.from_dict(item)
62
+ self._integrations[defn.id] = defn
63
+
64
+ def save_to_file(self, path: Optional[str] = None) -> None:
65
+ """Persists integrations manifest to JSON."""
66
+ target = path or self.manifest_path
67
+ if not target:
68
+ raise ValueError("No target path specified for saving integrations manifest.")
69
+ data = {
70
+ "version": "1.0.0",
71
+ "description": "Declarative registry of supportive tools, frameworks, and agentic skills for AgenticWorkflow",
72
+ "integrations": [i.to_dict() for i in self._integrations.values()]
73
+ }
74
+ with open(target, "w", encoding="utf-8") as f:
75
+ json.dump(data, f, indent=2)
76
+
77
+ def register(self, definition: IntegrationDefinition) -> None:
78
+ """Registers or updates an integration definition."""
79
+ self._integrations[definition.id] = definition
80
+
81
+ def get(self, integration_id: str) -> Optional[IntegrationDefinition]:
82
+ return self._integrations.get(integration_id)
83
+
84
+ def list_all(self) -> List[IntegrationDefinition]:
85
+ return list(self._integrations.values())
86
+
87
+ def get_for_phase(self, phase: str) -> List[IntegrationDefinition]:
88
+ """Returns all integrations bound to a specific workflow phase or continuous."""
89
+ phase_clean = phase.lower().strip()
90
+ matched = []
91
+ for item in self._integrations.values():
92
+ if "continuous" in item.lifecycle_phases or phase_clean in item.lifecycle_phases:
93
+ matched.append(item)
94
+ return matched
95
+
96
+
97
+ def get_default_registry(project_dir: str = ".") -> IntegrationsRegistry:
98
+ """Instantiates registry from project root integrations.json with repo fallback."""
99
+ manifest_path = os.path.join(os.path.abspath(project_dir), "integrations.json")
100
+ if not os.path.exists(manifest_path):
101
+ repo_fallback = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "integrations.json"))
102
+ if os.path.exists(repo_fallback):
103
+ manifest_path = repo_fallback
104
+ return IntegrationsRegistry(manifest_path=manifest_path)
105
+
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ multi_agent_system.py — Multi-Agent System Architecture & Governance Engine
4
+
5
+ Implements patterns from /agency-multi-agent-systems-architect:
6
+ 1. Hierarchical Orchestrator-Subagent Topology with Task Ledger
7
+ 2. Least-Privilege Agent Permissions & Sandboxing
8
+ 3. Circuit Breaker Pattern (CLOSED -> OPEN -> HALF_OPEN)
9
+ 4. Trace-Based Observability (trace_id, span_id, latency, cost)
10
+ 5. Contradiction Detection & Multi-Agent Arbitration
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import json
16
+ import time
17
+ import uuid
18
+ from enum import Enum
19
+ from dataclasses import dataclass, field, asdict
20
+ from typing import Dict, List, Optional, Any
21
+
22
+
23
+ class CircuitBreakerState(Enum):
24
+ CLOSED = "CLOSED" # Healthy: calls proceed normally
25
+ OPEN = "OPEN" # Tripped: failures exceeded threshold (recovers via diagnosis)
26
+ HALF_OPEN = "HALF_OPEN" # Testing recovery with single canary request
27
+
28
+
29
+ class AgentRole(Enum):
30
+ ORCHESTRATOR = "orchestrator"
31
+ RESEARCHER = "researcher"
32
+ ARCHITECT = "architect"
33
+ ENGINEER = "engineer"
34
+ REVIEWER = "reviewer"
35
+ FACT_CHECKER = "fact_checker"
36
+ CLEAN_CODE_GUARD = "clean_code_guard"
37
+ RECOVERY = "fable_recovery"
38
+
39
+
40
+ # Least-Privilege Tool Permissions Matrix
41
+ TOOL_PERMISSIONS: Dict[AgentRole, List[str]] = {
42
+ AgentRole.ORCHESTRATOR: ["read_file", "write_sot", "dispatch_agent", "log_trace"],
43
+ AgentRole.RESEARCHER: ["read_file", "search_web", "read_url", "extract_data"],
44
+ AgentRole.ARCHITECT: ["read_file", "propose_plan", "diagram_topology"],
45
+ AgentRole.ENGINEER: ["read_file", "write_file", "run_tests", "execute_code"],
46
+ AgentRole.REVIEWER: ["read_file", "rate_pacs", "lint_code"], # Strictly read-only
47
+ AgentRole.FACT_CHECKER: ["read_file", "search_web", "verify_claim"],
48
+ AgentRole.CLEAN_CODE_GUARD: ["read_file", "ast_check", "report_violations"],
49
+ AgentRole.RECOVERY: ["read_file", "diagnose_context", "rollback_checkpoint", "repair_state"]
50
+ }
51
+
52
+
53
+ @dataclass
54
+ class AgentSpan:
55
+ trace_id: str
56
+ span_id: str
57
+ agent_id: str
58
+ role: str
59
+ step: int
60
+ started_at: float
61
+ completed_at: Optional[float] = None
62
+ latency_ms: Optional[float] = None
63
+ input_tokens: int = 0
64
+ output_tokens: int = 0
65
+ confidence: float = 1.0
66
+ tools_called: List[str] = field(default_factory=list)
67
+ status: str = "running" # success, failure, partial, escalated
68
+ error_message: Optional[str] = None
69
+ output_summary: Optional[str] = None
70
+
71
+ def finish(self, status: str = "success", error: Optional[str] = None) -> None:
72
+ self.completed_at = time.time()
73
+ self.latency_ms = (self.completed_at - self.started_at) * 1000.0
74
+ self.status = status
75
+ self.error_message = error
76
+
77
+
78
+ class CircuitBreaker:
79
+ """Fable & Distributed Systems Circuit Breaker for agent actions."""
80
+
81
+ def __init__(self, failure_threshold: int = 2, cooldown_seconds: float = 30.0):
82
+ self.failure_threshold = failure_threshold
83
+ self.cooldown_seconds = cooldown_seconds
84
+ self.state = CircuitBreakerState.CLOSED
85
+ self.failure_streak = 0
86
+ self.last_trip_time: Optional[float] = None
87
+
88
+ def record_success(self) -> None:
89
+ self.failure_streak = 0
90
+ if self.state == CircuitBreakerState.HALF_OPEN:
91
+ self.state = CircuitBreakerState.CLOSED
92
+
93
+ def record_failure(self) -> None:
94
+ self.failure_streak += 1
95
+ if self.failure_streak >= self.failure_threshold:
96
+ self.state = CircuitBreakerState.OPEN
97
+ self.last_trip_time = time.time()
98
+
99
+ def can_execute(self) -> bool:
100
+ if self.state == CircuitBreakerState.CLOSED:
101
+ return True
102
+ if self.state == CircuitBreakerState.OPEN:
103
+ if self.last_trip_time and (time.time() - self.last_trip_time) > self.cooldown_seconds:
104
+ self.state = CircuitBreakerState.HALF_OPEN
105
+ return True
106
+ return False
107
+ return True # HALF_OPEN allows single test execution
108
+
109
+
110
+ class MultiAgentManager:
111
+ """Coordinates agent role scoping, trace logging, and fault containment."""
112
+
113
+ def __init__(self, trace_dir: str = ".traces"):
114
+ self.trace_dir = trace_dir
115
+ self.circuit_breakers: Dict[str, CircuitBreaker] = {}
116
+ os.makedirs(self.trace_dir, exist_ok=True)
117
+
118
+ def get_circuit_breaker(self, agent_role: str) -> CircuitBreaker:
119
+ if agent_role not in self.circuit_breakers:
120
+ self.circuit_breakers[agent_role] = CircuitBreaker(failure_threshold=2)
121
+ return self.circuit_breakers[agent_role]
122
+
123
+ def authorize_tool(self, role: AgentRole, tool_name: str) -> bool:
124
+ """Enforces least-privilege boundary. Returns True if tool is permitted."""
125
+ allowed = TOOL_PERMISSIONS.get(role, [])
126
+ return tool_name in allowed
127
+
128
+ def start_span(self, trace_id: str, agent_id: str, role: AgentRole, step: int) -> AgentSpan:
129
+ """Initiates an observable trace span for an agent execution step."""
130
+ return AgentSpan(
131
+ trace_id=trace_id,
132
+ span_id=str(uuid.uuid4())[:8],
133
+ agent_id=agent_id,
134
+ role=role.value,
135
+ step=step,
136
+ started_at=time.time()
137
+ )
138
+
139
+ def record_span(self, span: AgentSpan) -> None:
140
+ """Persists the trace span to disk for full observability."""
141
+ trace_file = os.path.join(self.trace_dir, f"trace_{span.trace_id}.jsonl")
142
+ record = asdict(span)
143
+ try:
144
+ with open(trace_file, "a", encoding="utf-8") as f:
145
+ f.write(json.dumps(record) + "\n")
146
+ except OSError as log_err:
147
+ # Telemetry error handling without silent swallowing
148
+ sys.stderr.write(f"Trace telemetry notice: {log_err}\n")
149
+
150
+ def detect_contradictions(self, outputs: Dict[str, str]) -> List[str]:
151
+ """Detects conflicting claims or opposing verdicts between peer agents."""
152
+ contradictions = []
153
+ verdicts = {}
154
+ for agent_name, text in outputs.items():
155
+ if "FAIL" in text:
156
+ verdicts[agent_name] = "FAIL"
157
+ elif "PASS" in text:
158
+ verdicts[agent_name] = "PASS"
159
+
160
+ if "FAIL" in verdicts.values() and "PASS" in verdicts.values():
161
+ contradictions.append(
162
+ f"Verdict contradiction detected between agents: {verdicts}"
163
+ )
164
+ return contradictions