animus-forge 1.3.0__py3-none-any.whl

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 (308) hide show
  1. animus_forge/__init__.py +54 -0
  2. animus_forge/agents/__init__.py +19 -0
  3. animus_forge/agents/convergence.py +303 -0
  4. animus_forge/agents/provider_wrapper.py +169 -0
  5. animus_forge/agents/supervisor.py +654 -0
  6. animus_forge/analytics/__init__.py +54 -0
  7. animus_forge/analytics/analyzers.py +336 -0
  8. animus_forge/analytics/collectors.py +424 -0
  9. animus_forge/analytics/pipeline.py +462 -0
  10. animus_forge/analytics/reporters.py +371 -0
  11. animus_forge/analytics/visualizers.py +289 -0
  12. animus_forge/api.py +453 -0
  13. animus_forge/api_clients/__init__.py +18 -0
  14. animus_forge/api_clients/calendar_client.py +643 -0
  15. animus_forge/api_clients/claude_code_client.py +702 -0
  16. animus_forge/api_clients/github_client.py +196 -0
  17. animus_forge/api_clients/gmail_client.py +114 -0
  18. animus_forge/api_clients/notion_client.py +625 -0
  19. animus_forge/api_clients/openai_client.py +136 -0
  20. animus_forge/api_clients/resilience.py +267 -0
  21. animus_forge/api_clients/slack_client.py +450 -0
  22. animus_forge/api_errors.py +426 -0
  23. animus_forge/api_models.py +194 -0
  24. animus_forge/api_routes/__init__.py +1 -0
  25. animus_forge/api_routes/auth.py +67 -0
  26. animus_forge/api_routes/budgets.py +171 -0
  27. animus_forge/api_routes/coordination.py +137 -0
  28. animus_forge/api_routes/dashboard.py +422 -0
  29. animus_forge/api_routes/executions.py +384 -0
  30. animus_forge/api_routes/graph.py +432 -0
  31. animus_forge/api_routes/health.py +192 -0
  32. animus_forge/api_routes/history.py +73 -0
  33. animus_forge/api_routes/jobs.py +119 -0
  34. animus_forge/api_routes/mcp.py +167 -0
  35. animus_forge/api_routes/prompts.py +53 -0
  36. animus_forge/api_routes/schedules.py +131 -0
  37. animus_forge/api_routes/settings.py +114 -0
  38. animus_forge/api_routes/webhooks.py +247 -0
  39. animus_forge/api_routes/websocket.py +39 -0
  40. animus_forge/api_routes/workflows.py +410 -0
  41. animus_forge/api_state.py +103 -0
  42. animus_forge/auth/__init__.py +25 -0
  43. animus_forge/auth/tenants.py +740 -0
  44. animus_forge/auth/token_auth.py +61 -0
  45. animus_forge/browser/__init__.py +30 -0
  46. animus_forge/browser/automation.py +933 -0
  47. animus_forge/budget/__init__.py +84 -0
  48. animus_forge/budget/manager.py +383 -0
  49. animus_forge/budget/models.py +93 -0
  50. animus_forge/budget/persistence.py +314 -0
  51. animus_forge/budget/preflight.py +383 -0
  52. animus_forge/budget/strategies.py +347 -0
  53. animus_forge/cache/__init__.py +33 -0
  54. animus_forge/cache/backends.py +406 -0
  55. animus_forge/cache/decorators.py +284 -0
  56. animus_forge/cli/__init__.py +5 -0
  57. animus_forge/cli/commands/__init__.py +1 -0
  58. animus_forge/cli/commands/admin.py +244 -0
  59. animus_forge/cli/commands/browser.py +188 -0
  60. animus_forge/cli/commands/budget.py +155 -0
  61. animus_forge/cli/commands/calendar_cmd.py +335 -0
  62. animus_forge/cli/commands/config.py +132 -0
  63. animus_forge/cli/commands/coordination.py +189 -0
  64. animus_forge/cli/commands/dev.py +583 -0
  65. animus_forge/cli/commands/eval_cmd.py +221 -0
  66. animus_forge/cli/commands/graph.py +301 -0
  67. animus_forge/cli/commands/history.py +134 -0
  68. animus_forge/cli/commands/mcp.py +220 -0
  69. animus_forge/cli/commands/memory.py +128 -0
  70. animus_forge/cli/commands/metrics.py +132 -0
  71. animus_forge/cli/commands/schedule.py +152 -0
  72. animus_forge/cli/commands/self_improve.py +180 -0
  73. animus_forge/cli/commands/setup.py +197 -0
  74. animus_forge/cli/commands/workflow.py +380 -0
  75. animus_forge/cli/detection.py +119 -0
  76. animus_forge/cli/helpers.py +114 -0
  77. animus_forge/cli/interactive_runner.py +502 -0
  78. animus_forge/cli/main.py +205 -0
  79. animus_forge/cli/rich_output.py +448 -0
  80. animus_forge/config/__init__.py +13 -0
  81. animus_forge/config/logging.py +122 -0
  82. animus_forge/config/settings.py +450 -0
  83. animus_forge/contracts/__init__.py +31 -0
  84. animus_forge/contracts/base.py +141 -0
  85. animus_forge/contracts/definitions.py +891 -0
  86. animus_forge/contracts/enforcer.py +297 -0
  87. animus_forge/contracts/validator.py +290 -0
  88. animus_forge/dashboard/__init__.py +5 -0
  89. animus_forge/dashboard/app.py +518 -0
  90. animus_forge/dashboard/cost_dashboard.py +361 -0
  91. animus_forge/dashboard/eval_page.py +113 -0
  92. animus_forge/dashboard/mcp_page.py +417 -0
  93. animus_forge/dashboard/monitoring_pages.py +1104 -0
  94. animus_forge/dashboard/plugin_marketplace.py +765 -0
  95. animus_forge/dashboard/workflow_builder/__init__.py +64 -0
  96. animus_forge/dashboard/workflow_builder/builder.py +118 -0
  97. animus_forge/dashboard/workflow_builder/constants.py +467 -0
  98. animus_forge/dashboard/workflow_builder/persistence.py +193 -0
  99. animus_forge/dashboard/workflow_builder/renderers/__init__.py +37 -0
  100. animus_forge/dashboard/workflow_builder/renderers/_helpers.py +20 -0
  101. animus_forge/dashboard/workflow_builder/renderers/canvas.py +303 -0
  102. animus_forge/dashboard/workflow_builder/renderers/execution.py +130 -0
  103. animus_forge/dashboard/workflow_builder/renderers/node_config.py +246 -0
  104. animus_forge/dashboard/workflow_builder/renderers/visualization.py +234 -0
  105. animus_forge/dashboard/workflow_builder/renderers/workflow_io.py +191 -0
  106. animus_forge/dashboard/workflow_builder/state.py +143 -0
  107. animus_forge/dashboard/workflow_builder/yaml_ops.py +145 -0
  108. animus_forge/dashboard/workflow_visualizer.py +423 -0
  109. animus_forge/db.py +353 -0
  110. animus_forge/errors.py +146 -0
  111. animus_forge/evaluation/__init__.py +57 -0
  112. animus_forge/evaluation/base.py +438 -0
  113. animus_forge/evaluation/loader.py +157 -0
  114. animus_forge/evaluation/metrics.py +567 -0
  115. animus_forge/evaluation/reporters.py +388 -0
  116. animus_forge/evaluation/runner.py +405 -0
  117. animus_forge/evaluation/store.py +333 -0
  118. animus_forge/executions/__init__.py +21 -0
  119. animus_forge/executions/manager.py +768 -0
  120. animus_forge/executions/models.py +97 -0
  121. animus_forge/http/__init__.py +33 -0
  122. animus_forge/http/client.py +267 -0
  123. animus_forge/intelligence/__init__.py +87 -0
  124. animus_forge/intelligence/cost_intelligence.py +824 -0
  125. animus_forge/intelligence/cross_workflow_memory.py +620 -0
  126. animus_forge/intelligence/feedback_engine.py +775 -0
  127. animus_forge/intelligence/integration_graph.py +450 -0
  128. animus_forge/intelligence/outcome_tracker.py +384 -0
  129. animus_forge/intelligence/prompt_evolution.py +605 -0
  130. animus_forge/intelligence/provider_router.py +584 -0
  131. animus_forge/jobs/__init__.py +13 -0
  132. animus_forge/jobs/job_manager.py +433 -0
  133. animus_forge/mcp/__init__.py +32 -0
  134. animus_forge/mcp/client.py +253 -0
  135. animus_forge/mcp/manager.py +682 -0
  136. animus_forge/mcp/models.py +140 -0
  137. animus_forge/messaging/__init__.py +27 -0
  138. animus_forge/messaging/base.py +222 -0
  139. animus_forge/messaging/discord_bot.py +529 -0
  140. animus_forge/messaging/telegram_bot.py +591 -0
  141. animus_forge/metrics/__init__.py +79 -0
  142. animus_forge/metrics/audit_checks.py +409 -0
  143. animus_forge/metrics/collector.py +450 -0
  144. animus_forge/metrics/cost_tracker.py +497 -0
  145. animus_forge/metrics/debt_monitor.py +737 -0
  146. animus_forge/metrics/exporters.py +267 -0
  147. animus_forge/metrics/prometheus_server.py +423 -0
  148. animus_forge/monitoring/__init__.py +49 -0
  149. animus_forge/monitoring/metrics.py +383 -0
  150. animus_forge/monitoring/parallel_tracker.py +724 -0
  151. animus_forge/monitoring/tracker.py +214 -0
  152. animus_forge/monitoring/watchers.py +793 -0
  153. animus_forge/notifications/__init__.py +31 -0
  154. animus_forge/notifications/base.py +28 -0
  155. animus_forge/notifications/channels/__init__.py +17 -0
  156. animus_forge/notifications/channels/discord.py +117 -0
  157. animus_forge/notifications/channels/email_channel.py +141 -0
  158. animus_forge/notifications/channels/pagerduty.py +113 -0
  159. animus_forge/notifications/channels/slack.py +121 -0
  160. animus_forge/notifications/channels/teams.py +97 -0
  161. animus_forge/notifications/channels/webhook.py +50 -0
  162. animus_forge/notifications/manager.py +189 -0
  163. animus_forge/notifications/models.py +42 -0
  164. animus_forge/notifications/notifier.py +46 -0
  165. animus_forge/orchestrator/__init__.py +23 -0
  166. animus_forge/orchestrator/workflow_engine.py +54 -0
  167. animus_forge/orchestrator/workflow_engine_adapter.py +262 -0
  168. animus_forge/plugins/__init__.py +66 -0
  169. animus_forge/plugins/base.py +257 -0
  170. animus_forge/plugins/installer.py +684 -0
  171. animus_forge/plugins/loader.py +309 -0
  172. animus_forge/plugins/marketplace.py +567 -0
  173. animus_forge/plugins/models.py +146 -0
  174. animus_forge/plugins/registry.py +264 -0
  175. animus_forge/prompts/__init__.py +5 -0
  176. animus_forge/prompts/template_manager.py +177 -0
  177. animus_forge/providers/__init__.py +69 -0
  178. animus_forge/providers/anthropic_provider.py +287 -0
  179. animus_forge/providers/azure_openai_provider.py +371 -0
  180. animus_forge/providers/base.py +385 -0
  181. animus_forge/providers/bedrock_provider.py +408 -0
  182. animus_forge/providers/hardware.py +206 -0
  183. animus_forge/providers/manager.py +444 -0
  184. animus_forge/providers/mock_provider.py +123 -0
  185. animus_forge/providers/ollama_provider.py +518 -0
  186. animus_forge/providers/openai_provider.py +279 -0
  187. animus_forge/providers/router.py +374 -0
  188. animus_forge/providers/vertex_provider.py +425 -0
  189. animus_forge/ratelimit/__init__.py +42 -0
  190. animus_forge/ratelimit/limiter.py +358 -0
  191. animus_forge/ratelimit/provider.py +353 -0
  192. animus_forge/ratelimit/quota.py +324 -0
  193. animus_forge/resilience/__init__.py +51 -0
  194. animus_forge/resilience/bulkhead.py +343 -0
  195. animus_forge/resilience/concurrency.py +345 -0
  196. animus_forge/resilience/fallback.py +376 -0
  197. animus_forge/scheduler/__init__.py +21 -0
  198. animus_forge/scheduler/schedule_manager.py +579 -0
  199. animus_forge/security/__init__.py +43 -0
  200. animus_forge/security/audit_log.py +190 -0
  201. animus_forge/security/brute_force.py +366 -0
  202. animus_forge/security/field_encryption.py +126 -0
  203. animus_forge/security/request_limits.py +155 -0
  204. animus_forge/self_improve/__init__.py +43 -0
  205. animus_forge/self_improve/analyzer.py +475 -0
  206. animus_forge/self_improve/approval.py +395 -0
  207. animus_forge/self_improve/orchestrator.py +564 -0
  208. animus_forge/self_improve/pr_manager.py +448 -0
  209. animus_forge/self_improve/rollback.py +277 -0
  210. animus_forge/self_improve/safety.py +291 -0
  211. animus_forge/self_improve/sandbox.py +333 -0
  212. animus_forge/settings/__init__.py +14 -0
  213. animus_forge/settings/manager.py +327 -0
  214. animus_forge/settings/models.py +62 -0
  215. animus_forge/skills/__init__.py +91 -0
  216. animus_forge/skills/consensus.py +292 -0
  217. animus_forge/skills/enforcer.py +169 -0
  218. animus_forge/skills/evolver/__init__.py +37 -0
  219. animus_forge/skills/evolver/ab_test.py +278 -0
  220. animus_forge/skills/evolver/analyzer.py +209 -0
  221. animus_forge/skills/evolver/deprecator.py +211 -0
  222. animus_forge/skills/evolver/evolver.py +298 -0
  223. animus_forge/skills/evolver/generator.py +124 -0
  224. animus_forge/skills/evolver/metrics.py +337 -0
  225. animus_forge/skills/evolver/models.py +106 -0
  226. animus_forge/skills/evolver/tuner.py +206 -0
  227. animus_forge/skills/evolver/versioner.py +146 -0
  228. animus_forge/skills/evolver/writer.py +177 -0
  229. animus_forge/skills/library.py +250 -0
  230. animus_forge/skills/loader.py +217 -0
  231. animus_forge/skills/models.py +159 -0
  232. animus_forge/state/__init__.py +54 -0
  233. animus_forge/state/agent_context.py +455 -0
  234. animus_forge/state/agent_memory.py +437 -0
  235. animus_forge/state/backends.py +312 -0
  236. animus_forge/state/checkpoint.py +285 -0
  237. animus_forge/state/context_window.py +411 -0
  238. animus_forge/state/database.py +30 -0
  239. animus_forge/state/memory.py +22 -0
  240. animus_forge/state/memory_models.py +120 -0
  241. animus_forge/state/migrations.py +170 -0
  242. animus_forge/state/persistence.py +428 -0
  243. animus_forge/tools/__init__.py +26 -0
  244. animus_forge/tools/filesystem.py +376 -0
  245. animus_forge/tools/models.py +133 -0
  246. animus_forge/tools/proposals.py +315 -0
  247. animus_forge/tools/safety.py +264 -0
  248. animus_forge/tracing/__init__.py +65 -0
  249. animus_forge/tracing/context.py +337 -0
  250. animus_forge/tracing/export.py +412 -0
  251. animus_forge/tracing/middleware.py +233 -0
  252. animus_forge/tracing/propagation.py +237 -0
  253. animus_forge/tui/__init__.py +5 -0
  254. animus_forge/tui/app.py +317 -0
  255. animus_forge/tui/chat_screen.py +46 -0
  256. animus_forge/tui/commands.py +413 -0
  257. animus_forge/tui/providers.py +73 -0
  258. animus_forge/tui/session.py +144 -0
  259. animus_forge/tui/streaming.py +196 -0
  260. animus_forge/tui/widgets/__init__.py +8 -0
  261. animus_forge/tui/widgets/chat_display.py +102 -0
  262. animus_forge/tui/widgets/input_bar.py +65 -0
  263. animus_forge/tui/widgets/sidebar.py +122 -0
  264. animus_forge/tui/widgets/status_bar.py +34 -0
  265. animus_forge/utils/__init__.py +45 -0
  266. animus_forge/utils/circuit_breaker.py +333 -0
  267. animus_forge/utils/retry.py +479 -0
  268. animus_forge/utils/validation.py +433 -0
  269. animus_forge/webhooks/__init__.py +33 -0
  270. animus_forge/webhooks/webhook_delivery.py +821 -0
  271. animus_forge/webhooks/webhook_manager.py +501 -0
  272. animus_forge/websocket/__init__.py +35 -0
  273. animus_forge/websocket/broadcaster.py +266 -0
  274. animus_forge/websocket/manager.py +286 -0
  275. animus_forge/websocket/messages.py +121 -0
  276. animus_forge/workflow/__init__.py +115 -0
  277. animus_forge/workflow/approval_store.py +204 -0
  278. animus_forge/workflow/arete_hooks.py +202 -0
  279. animus_forge/workflow/auto_parallel.py +277 -0
  280. animus_forge/workflow/composer.py +340 -0
  281. animus_forge/workflow/distributed_rate_limiter.py +425 -0
  282. animus_forge/workflow/executor.py +58 -0
  283. animus_forge/workflow/executor_ai.py +294 -0
  284. animus_forge/workflow/executor_approval.py +70 -0
  285. animus_forge/workflow/executor_arete.py +236 -0
  286. animus_forge/workflow/executor_clients.py +116 -0
  287. animus_forge/workflow/executor_core.py +700 -0
  288. animus_forge/workflow/executor_error.py +134 -0
  289. animus_forge/workflow/executor_integrations.py +856 -0
  290. animus_forge/workflow/executor_mcp.py +169 -0
  291. animus_forge/workflow/executor_parallel_exec.py +353 -0
  292. animus_forge/workflow/executor_patterns.py +720 -0
  293. animus_forge/workflow/executor_results.py +77 -0
  294. animus_forge/workflow/executor_step.py +289 -0
  295. animus_forge/workflow/graph_executor.py +617 -0
  296. animus_forge/workflow/graph_models.py +176 -0
  297. animus_forge/workflow/graph_walker.py +395 -0
  298. animus_forge/workflow/loader.py +580 -0
  299. animus_forge/workflow/parallel.py +639 -0
  300. animus_forge/workflow/rate_limited_executor.py +696 -0
  301. animus_forge/workflow/scheduler.py +477 -0
  302. animus_forge/workflow/version_manager.py +618 -0
  303. animus_forge/workflow/versioning.py +286 -0
  304. animus_forge-1.3.0.dist-info/METADATA +124 -0
  305. animus_forge-1.3.0.dist-info/RECORD +308 -0
  306. animus_forge-1.3.0.dist-info/WHEEL +5 -0
  307. animus_forge-1.3.0.dist-info/entry_points.txt +2 -0
  308. animus_forge-1.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,54 @@
1
+ """AI Workflow Orchestrator - A unified automation layer for AI-powered workflows."""
2
+
3
+ __version__ = "1.2.0"
4
+
5
+ from .auth import TokenAuth, create_access_token, verify_token
6
+ from .config import Settings, get_settings
7
+ from .jobs import (
8
+ Job,
9
+ JobManager,
10
+ JobStatus,
11
+ )
12
+ from .orchestrator import Workflow, WorkflowEngineAdapter, WorkflowResult, WorkflowStep
13
+ from .prompts import PromptTemplate, PromptTemplateManager
14
+ from .scheduler import (
15
+ CronConfig,
16
+ IntervalConfig,
17
+ ScheduleManager,
18
+ ScheduleStatus,
19
+ ScheduleType,
20
+ WorkflowSchedule,
21
+ )
22
+ from .webhooks import (
23
+ PayloadMapping,
24
+ Webhook,
25
+ WebhookManager,
26
+ WebhookStatus,
27
+ )
28
+
29
+ __all__ = [
30
+ "Settings",
31
+ "get_settings",
32
+ "WorkflowEngineAdapter",
33
+ "Workflow",
34
+ "WorkflowStep",
35
+ "WorkflowResult",
36
+ "PromptTemplateManager",
37
+ "PromptTemplate",
38
+ "TokenAuth",
39
+ "create_access_token",
40
+ "verify_token",
41
+ "ScheduleManager",
42
+ "WorkflowSchedule",
43
+ "ScheduleType",
44
+ "ScheduleStatus",
45
+ "CronConfig",
46
+ "IntervalConfig",
47
+ "WebhookManager",
48
+ "Webhook",
49
+ "WebhookStatus",
50
+ "PayloadMapping",
51
+ "JobManager",
52
+ "Job",
53
+ "JobStatus",
54
+ ]
@@ -0,0 +1,19 @@
1
+ """AI Agents for autonomous task orchestration.
2
+
3
+ This module provides intelligent agents that can analyze user requests,
4
+ delegate to specialized sub-agents, and synthesize results.
5
+ """
6
+
7
+ from .convergence import HAS_CONVERGENT, ConvergenceResult, DelegationConvergenceChecker
8
+ from .provider_wrapper import AgentProvider, create_agent_provider
9
+ from .supervisor import AgentDelegation, SupervisorAgent
10
+
11
+ __all__ = [
12
+ "SupervisorAgent",
13
+ "AgentDelegation",
14
+ "AgentProvider",
15
+ "create_agent_provider",
16
+ "ConvergenceResult",
17
+ "DelegationConvergenceChecker",
18
+ "HAS_CONVERGENT",
19
+ ]
@@ -0,0 +1,303 @@
1
+ """Adapter between Convergent's IntentResolver and Gorgon's delegation pipeline.
2
+
3
+ Optional integration — Gorgon works without Convergent installed.
4
+ When available, checks delegations for coherence before parallel execution:
5
+ overlapping tasks, conflicting agents, redundant work.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ from dataclasses import dataclass, field
12
+ from typing import Any
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ try:
17
+ from convergent import (
18
+ Intent,
19
+ InterfaceKind,
20
+ InterfaceSpec,
21
+ create_delegation_checker,
22
+ )
23
+
24
+ HAS_CONVERGENT = True
25
+ except ImportError:
26
+ HAS_CONVERGENT = False
27
+
28
+
29
+ @dataclass
30
+ class ConvergenceResult:
31
+ """Result of checking delegations for coherence."""
32
+
33
+ adjustments: list[dict[str, Any]] = field(default_factory=list)
34
+ conflicts: list[dict[str, Any]] = field(default_factory=list)
35
+ dropped_agents: set[str] = field(default_factory=set)
36
+
37
+ @property
38
+ def has_conflicts(self) -> bool:
39
+ return len(self.conflicts) > 0
40
+
41
+
42
+ class DelegationConvergenceChecker:
43
+ """Checks delegations for coherence using Convergent's IntentResolver.
44
+
45
+ No-ops gracefully when Convergent is not installed.
46
+ """
47
+
48
+ def __init__(self, resolver: Any | None = None) -> None:
49
+ self._resolver = resolver
50
+ self._enabled = HAS_CONVERGENT and resolver is not None
51
+
52
+ @property
53
+ def enabled(self) -> bool:
54
+ return self._enabled
55
+
56
+ def check_delegations(self, delegations: list[dict[str, str]]) -> ConvergenceResult:
57
+ """Check a list of delegations for overlap and conflicts.
58
+
59
+ Each delegation is {"agent": str, "task": str}. Publishes each as
60
+ an Intent, then resolves each against the graph.
61
+
62
+ Returns:
63
+ ConvergenceResult with any adjustments, conflicts, or agents to drop.
64
+ """
65
+ if not self._enabled:
66
+ return ConvergenceResult()
67
+
68
+ result = ConvergenceResult()
69
+
70
+ # Publish all delegations as intents
71
+ intents: list[tuple[str, Any]] = []
72
+ for delegation in delegations:
73
+ intent = self._delegation_to_intent(delegation)
74
+ self._resolver.publish(intent)
75
+ intents.append((delegation.get("agent", "unknown"), intent))
76
+
77
+ # Resolve each against the graph
78
+ for agent_name, intent in intents:
79
+ resolution = self._resolver.resolve(intent)
80
+
81
+ for adj in resolution.adjustments:
82
+ result.adjustments.append(
83
+ {
84
+ "agent": agent_name,
85
+ "kind": adj.kind,
86
+ "description": adj.description,
87
+ "confidence": adj.confidence,
88
+ }
89
+ )
90
+ # If told to consume instead, the agent is redundant
91
+ if adj.kind == "ConsumeInstead" and adj.confidence >= 0.7:
92
+ result.dropped_agents.add(agent_name)
93
+
94
+ for conflict in resolution.conflicts:
95
+ result.conflicts.append(
96
+ {
97
+ "agent": agent_name,
98
+ "description": conflict.description,
99
+ "their_stability": conflict.their_stability,
100
+ "confidence": conflict.confidence,
101
+ }
102
+ )
103
+
104
+ return result
105
+
106
+ @staticmethod
107
+ def _delegation_to_intent(delegation: dict[str, str]) -> Any:
108
+ """Convert a Gorgon delegation dict to a Convergent Intent."""
109
+ agent = delegation.get("agent", "unknown")
110
+ task = delegation.get("task", "")
111
+
112
+ # Infer tags from the agent role
113
+ role_tags = {
114
+ "planner": ["planning", "architecture", "design"],
115
+ "builder": ["implementation", "code", "feature"],
116
+ "tester": ["testing", "qa", "coverage"],
117
+ "reviewer": ["review", "security", "quality"],
118
+ "architect": ["architecture", "design", "system"],
119
+ "documenter": ["documentation", "docs", "guide"],
120
+ "analyst": ["analysis", "data", "metrics"],
121
+ }
122
+ tags = role_tags.get(agent, [agent])
123
+
124
+ return Intent(
125
+ agent_id=agent,
126
+ intent=task,
127
+ provides=[
128
+ InterfaceSpec(
129
+ name=f"{agent}_output",
130
+ kind=InterfaceKind.FUNCTION,
131
+ signature="(task: str) -> str",
132
+ tags=tags,
133
+ ),
134
+ ],
135
+ )
136
+
137
+
138
+ def create_checker() -> DelegationConvergenceChecker:
139
+ """Create a DelegationConvergenceChecker with a fresh resolver.
140
+
141
+ Returns a disabled checker if Convergent is not installed.
142
+ """
143
+ if not HAS_CONVERGENT:
144
+ logger.info("Convergent not installed — delegation coherence checking disabled")
145
+ return DelegationConvergenceChecker(resolver=None)
146
+
147
+ resolver = create_delegation_checker(min_stability=0.0)
148
+ logger.info("Convergent delegation coherence checker enabled")
149
+ return DelegationConvergenceChecker(resolver=resolver)
150
+
151
+
152
+ def format_convergence_alert(result: ConvergenceResult) -> str:
153
+ """Format a ConvergenceResult into a human-readable alert string.
154
+
155
+ Returns empty string if no conflicts or dropped agents.
156
+ """
157
+ parts: list[str] = []
158
+
159
+ if result.conflicts:
160
+ parts.append(f"Conflicts ({len(result.conflicts)}):")
161
+ for c in result.conflicts:
162
+ parts.append(f" - {c.get('agent', '?')}: {c.get('description', '?')}")
163
+
164
+ if result.dropped_agents:
165
+ agents = ", ".join(sorted(result.dropped_agents))
166
+ parts.append(f"Dropped agents ({len(result.dropped_agents)}): {agents}")
167
+
168
+ if result.adjustments:
169
+ parts.append(f"Adjustments ({len(result.adjustments)}):")
170
+ for a in result.adjustments:
171
+ parts.append(f" - {a.get('agent', '?')}: {a.get('description', '?')}")
172
+
173
+ return "\n".join(parts)
174
+
175
+
176
+ def create_bridge(db_path: str | None = None) -> Any:
177
+ """Create a GorgonBridge for coordination protocol features.
178
+
179
+ Returns None if Convergent is not installed.
180
+ """
181
+ if not HAS_CONVERGENT:
182
+ logger.info("Convergent not installed — coordination bridge disabled")
183
+ return None
184
+ try:
185
+ from pathlib import Path
186
+
187
+ from convergent import CoordinationConfig, GorgonBridge
188
+
189
+ if db_path is None:
190
+ db_dir = Path.home() / ".gorgon"
191
+ db_dir.mkdir(parents=True, exist_ok=True)
192
+ db_path = str(db_dir / "coordination.db")
193
+
194
+ bridge = GorgonBridge(CoordinationConfig(db_path=db_path))
195
+ logger.info("Convergent coordination bridge enabled (db=%s)", db_path)
196
+ return bridge
197
+ except Exception as e:
198
+ logger.warning("Failed to create coordination bridge: %s", e)
199
+ return None
200
+
201
+
202
+ def create_event_log(db_path: str | None = None) -> Any:
203
+ """Create a Convergent EventLog for coordination event tracking.
204
+
205
+ Returns None if Convergent is not installed.
206
+
207
+ Args:
208
+ db_path: Path to SQLite database. Defaults to ~/.gorgon/coordination.events.db.
209
+
210
+ Returns:
211
+ EventLog instance or None.
212
+ """
213
+ if not HAS_CONVERGENT:
214
+ logger.info("Convergent not installed — coordination event log disabled")
215
+ return None
216
+ try:
217
+ from pathlib import Path
218
+
219
+ from convergent import EventLog
220
+
221
+ if db_path is None:
222
+ db_dir = Path.home() / ".gorgon"
223
+ db_dir.mkdir(parents=True, exist_ok=True)
224
+ db_path = str(db_dir / "coordination.events.db")
225
+
226
+ event_log = EventLog(db_path)
227
+ logger.info("Convergent event log enabled (db=%s)", db_path)
228
+ return event_log
229
+ except Exception as e:
230
+ logger.warning("Failed to create coordination event log: %s", e)
231
+ return None
232
+
233
+
234
+ def get_coordination_health(bridge: Any) -> dict[str, Any]:
235
+ """Run a coordination health check via Convergent's HealthChecker.
236
+
237
+ Args:
238
+ bridge: A GorgonBridge instance.
239
+
240
+ Returns:
241
+ Dict with grade, issues, and subsystem metrics. Empty dict on failure.
242
+ """
243
+ if not HAS_CONVERGENT or bridge is None:
244
+ return {}
245
+ try:
246
+ from dataclasses import asdict
247
+
248
+ from convergent import HealthChecker
249
+
250
+ checker = HealthChecker.from_bridge(bridge)
251
+ health = checker.check()
252
+ return asdict(health)
253
+ except Exception as e:
254
+ logger.warning("Coordination health check failed: %s", e)
255
+ return {}
256
+
257
+
258
+ def check_dependency_cycles(resolver: Any) -> list[dict[str, Any]]:
259
+ """Check the intent graph for dependency cycles.
260
+
261
+ Args:
262
+ resolver: An IntentResolver instance.
263
+
264
+ Returns:
265
+ List of cycle dicts with intent_ids and agent_ids. Empty on failure.
266
+ """
267
+ if not HAS_CONVERGENT or resolver is None:
268
+ return []
269
+ try:
270
+ from convergent import find_cycles
271
+
272
+ cycles = find_cycles(resolver)
273
+ return [
274
+ {
275
+ "intent_ids": list(c.intent_ids),
276
+ "agent_ids": list(c.agent_ids),
277
+ "display": str(c),
278
+ }
279
+ for c in cycles
280
+ ]
281
+ except Exception as e:
282
+ logger.warning("Dependency cycle check failed: %s", e)
283
+ return []
284
+
285
+
286
+ def get_execution_order(resolver: Any) -> list[str]:
287
+ """Get topological execution order for intents.
288
+
289
+ Args:
290
+ resolver: An IntentResolver instance.
291
+
292
+ Returns:
293
+ List of intent IDs in dependency-first order. Empty on failure.
294
+ """
295
+ if not HAS_CONVERGENT or resolver is None:
296
+ return []
297
+ try:
298
+ from convergent import topological_order
299
+
300
+ return topological_order(resolver)
301
+ except Exception as e:
302
+ logger.warning("Execution order computation failed: %s", e)
303
+ return []
@@ -0,0 +1,169 @@
1
+ """Provider wrapper for agents with streaming support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import AsyncGenerator
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from animus_forge.providers.base import Provider
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class AgentProvider:
16
+ """Wrapper around Provider with async and streaming support."""
17
+
18
+ def __init__(self, provider: Provider):
19
+ """Initialize with a provider.
20
+
21
+ Args:
22
+ provider: The underlying AI provider.
23
+ """
24
+ self.provider = provider
25
+ if not self.provider._initialized:
26
+ self.provider.initialize()
27
+
28
+ async def complete(self, messages: list[dict[str, str]]) -> str:
29
+ """Complete a conversation.
30
+
31
+ Args:
32
+ messages: List of message dicts with 'role' and 'content'.
33
+
34
+ Returns:
35
+ The assistant's response.
36
+ """
37
+ from animus_forge.providers.base import CompletionRequest
38
+
39
+ # Extract system prompt from messages
40
+ system_prompt = None
41
+ filtered_messages = []
42
+ for msg in messages:
43
+ if msg.get("role") == "system":
44
+ if system_prompt is None:
45
+ system_prompt = msg.get("content", "")
46
+ else:
47
+ system_prompt += "\n\n" + msg.get("content", "")
48
+ else:
49
+ filtered_messages.append(msg)
50
+
51
+ request = CompletionRequest(
52
+ prompt=filtered_messages[-1].get("content", "") if filtered_messages else "",
53
+ system_prompt=system_prompt or "You are a helpful assistant.",
54
+ messages=filtered_messages,
55
+ temperature=0.7,
56
+ max_tokens=4096,
57
+ )
58
+
59
+ response = await self.provider.complete_async(request)
60
+ return response.content
61
+
62
+ async def stream_completion(
63
+ self,
64
+ messages: list[dict[str, str]],
65
+ ) -> AsyncGenerator[str, None]:
66
+ """Stream a completion response.
67
+
68
+ Args:
69
+ messages: List of message dicts with 'role' and 'content'.
70
+
71
+ Yields:
72
+ Text chunks as they're generated.
73
+ """
74
+ # Check if provider has native streaming
75
+ if hasattr(self.provider, "_async_client") and self.provider._async_client:
76
+ try:
77
+ async for chunk in self._stream_anthropic(messages):
78
+ yield chunk
79
+ return
80
+ except Exception as e:
81
+ logger.warning(f"Streaming failed, falling back to non-streaming: {e}")
82
+
83
+ # Fall back to non-streaming
84
+ response = await self.complete(messages)
85
+ yield response
86
+
87
+ async def _stream_anthropic(
88
+ self,
89
+ messages: list[dict[str, str]],
90
+ ) -> AsyncGenerator[str, None]:
91
+ """Stream using Anthropic's native streaming API.
92
+
93
+ Args:
94
+ messages: List of message dicts.
95
+
96
+ Yields:
97
+ Text chunks.
98
+ """
99
+ # Extract system prompt
100
+ system_prompt = None
101
+ filtered_messages = []
102
+ for msg in messages:
103
+ if msg.get("role") == "system":
104
+ if system_prompt is None:
105
+ system_prompt = msg.get("content", "")
106
+ else:
107
+ system_prompt += "\n\n" + msg.get("content", "")
108
+ else:
109
+ filtered_messages.append(msg)
110
+
111
+ try:
112
+ async with self.provider._async_client.messages.stream(
113
+ model=self.provider.default_model,
114
+ system=system_prompt or "You are a helpful assistant.",
115
+ messages=filtered_messages,
116
+ max_tokens=4096,
117
+ ) as stream:
118
+ async for text in stream.text_stream:
119
+ yield text
120
+ except Exception as e:
121
+ logger.error(f"Anthropic streaming error: {e}")
122
+ raise
123
+
124
+
125
+ def create_agent_provider(provider_type: str = "anthropic") -> AgentProvider:
126
+ """Create an agent provider.
127
+
128
+ Args:
129
+ provider_type: Type of provider ('anthropic', 'openai', or 'ollama').
130
+
131
+ Returns:
132
+ Configured AgentProvider.
133
+ """
134
+ if provider_type == "anthropic":
135
+ from animus_forge.config import get_settings
136
+ from animus_forge.providers.anthropic_provider import AnthropicProvider
137
+
138
+ settings = get_settings()
139
+ provider = AnthropicProvider(api_key=settings.anthropic_api_key)
140
+ return AgentProvider(provider)
141
+
142
+ elif provider_type == "openai":
143
+ from animus_forge.config import get_settings
144
+ from animus_forge.providers.openai_provider import OpenAIProvider
145
+
146
+ settings = get_settings()
147
+ provider = OpenAIProvider(api_key=settings.openai_api_key)
148
+ return AgentProvider(provider)
149
+
150
+ elif provider_type == "ollama":
151
+ import os
152
+
153
+ from animus_forge.providers.base import ProviderConfig, ProviderType
154
+ from animus_forge.providers.ollama_provider import OllamaProvider
155
+
156
+ host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
157
+ model = os.environ.get("OLLAMA_MODEL", "deepseek-coder-v2")
158
+ provider = OllamaProvider(
159
+ config=ProviderConfig(
160
+ provider_type=ProviderType.OLLAMA,
161
+ base_url=host,
162
+ default_model=model,
163
+ timeout=600.0,
164
+ ),
165
+ )
166
+ return AgentProvider(provider)
167
+
168
+ else:
169
+ raise ValueError(f"Unknown provider type: {provider_type}")