@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.
- package/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +13 -0
- package/.skills.json +19 -0
- package/AGENTS.md +1344 -0
- package/CLAUDE.md +178 -0
- package/GEMINI.md +102 -0
- package/LICENSE +21 -0
- package/README.md +350 -0
- package/SKILL.md +132 -0
- package/bin/agentic-hooks.sh +79 -0
- package/bin/cli.js +1060 -0
- package/core/__init__.py +52 -0
- package/core/ai_evaluator.py +117 -0
- package/core/autopilot_engine.py +368 -0
- package/core/clean_code_guard.py +188 -0
- package/core/engine_py/__init__.py +29 -0
- package/core/engine_py/agent_worker.py +136 -0
- package/core/engine_py/decider.py +150 -0
- package/core/engine_py/energy.py +45 -0
- package/core/engine_py/event_bus.py +63 -0
- package/core/engine_py/executor.py +186 -0
- package/core/engine_py/models.py +193 -0
- package/core/engine_py/queue.py +314 -0
- package/core/engine_py/runner.py +116 -0
- package/core/engine_py/system_workers.py +70 -0
- package/core/engine_py/toon_adapter.py +586 -0
- package/core/engine_py/verification_controller.py +208 -0
- package/core/engine_py/worker.py +167 -0
- package/core/engine_spec/event_schema.json +65 -0
- package/core/engine_spec/example_workflow.yaml +73 -0
- package/core/engine_spec/workflow_schema.json +127 -0
- package/core/hooks/__init__.py +29 -0
- package/core/hooks/adapters/__init__.py +25 -0
- package/core/hooks/adapters/claude_adapter.py +83 -0
- package/core/hooks/adapters/cli_agent_adapter.py +82 -0
- package/core/hooks/adapters/codex_adapter.py +78 -0
- package/core/hooks/adapters/cursor_adapter.py +73 -0
- package/core/hooks/adapters/gemini_adapter.py +93 -0
- package/core/hooks/adapters/homebrew_adapter.py +69 -0
- package/core/hooks/adapters/mcp_proxy.py +133 -0
- package/core/hooks/adapters/shell_adapter.py +65 -0
- package/core/hooks/dispatcher.py +118 -0
- package/core/hooks/policy_engine.py +375 -0
- package/core/hooks/session_end.py +141 -0
- package/core/hooks/types.py +147 -0
- package/core/integrations/__init__.py +28 -0
- package/core/integrations/installer.py +225 -0
- package/core/integrations/lifecycle_director.py +175 -0
- package/core/integrations/registry.py +105 -0
- package/core/multi_agent_system.py +164 -0
- package/core/skills_indexer.py +742 -0
- package/core/system/__init__.py +25 -0
- package/core/system/announcements.py +72 -0
- package/core/system/dependencies.py +69 -0
- package/core/system/doctor.py +171 -0
- package/core/system/health.py +144 -0
- package/core/system/installer.py +137 -0
- package/core/system/notifications.py +97 -0
- package/core/system/refresher.py +110 -0
- package/core/system/updater.py +167 -0
- package/core/system/version_tracker.py +65 -0
- package/docs/architecture_plan.md +7 -0
- package/docs/guides/failure-recovery.md +714 -0
- package/docs/implementation_summary.md +10 -0
- package/docs/protocols/autopilot-execution.md +148 -0
- package/docs/protocols/code-change-protocol.md +49 -0
- package/docs/protocols/context-preservation-detail.md +114 -0
- package/docs/protocols/quality-gates.md +110 -0
- package/docs/protocols/ulw-mode.md +60 -0
- package/docs/research_findings.md +10 -0
- package/docs/solutions/autonomous-autopilot-engine-architecture.md +38 -0
- package/install.sh +111 -0
- package/marketplace.json +37 -0
- package/package.json +81 -0
- package/skills/agentic-workflow/SKILL.md +132 -0
- package/skills/agentic-workflow/skill-spec.json +100 -0
- package/soul.md +445 -0
- package/src/engine_ts/decider.ts +186 -0
- package/src/engine_ts/event-bus.ts +57 -0
- package/src/engine_ts/executor.ts +262 -0
- package/src/engine_ts/index.ts +12 -0
- package/src/engine_ts/queue.ts +93 -0
- package/src/engine_ts/runner.ts +108 -0
- package/src/engine_ts/skills-indexer.ts +264 -0
- package/src/engine_ts/toon-adapter.ts +91 -0
- package/src/engine_ts/types.ts +134 -0
- package/src/engine_ts/verification-controller.ts +204 -0
- package/src/engine_ts/worker.ts +280 -0
- package/src/hooks/adapters/claude-adapter.ts +54 -0
- package/src/hooks/adapters/cli-agent-adapter.ts +46 -0
- package/src/hooks/adapters/codex-adapter.ts +69 -0
- package/src/hooks/adapters/cursor-adapter.ts +60 -0
- package/src/hooks/adapters/gemini-adapter.ts +71 -0
- package/src/hooks/adapters/homebrew-adapter.ts +36 -0
- package/src/hooks/adapters/mcp-proxy.ts +66 -0
- package/src/hooks/adapters/shell-adapter.ts +42 -0
- package/src/hooks/dispatcher.ts +113 -0
- package/src/hooks/index.ts +16 -0
- package/src/hooks/policy-engine.ts +376 -0
- package/src/hooks/session-end.ts +125 -0
- package/src/hooks/types.ts +61 -0
- package/src/index.d.ts +34 -0
- package/src/index.ts +23 -0
- package/src/integrations/index.ts +7 -0
- package/src/integrations/installer.ts +208 -0
- package/src/integrations/lifecycle-director.ts +139 -0
- package/src/integrations/registry.ts +82 -0
- package/src/system/announcements.ts +143 -0
- package/src/system/dependencies.ts +176 -0
- package/src/system/doctor.ts +374 -0
- package/src/system/health.ts +270 -0
- package/src/system/index.ts +14 -0
- package/src/system/installer.ts +262 -0
- package/src/system/notifications.ts +180 -0
- package/src/system/refresher.ts +207 -0
- package/src/system/types.ts +268 -0
- package/src/system/updater.ts +219 -0
- 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
|