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.
- animus_forge/__init__.py +54 -0
- animus_forge/agents/__init__.py +19 -0
- animus_forge/agents/convergence.py +303 -0
- animus_forge/agents/provider_wrapper.py +169 -0
- animus_forge/agents/supervisor.py +654 -0
- animus_forge/analytics/__init__.py +54 -0
- animus_forge/analytics/analyzers.py +336 -0
- animus_forge/analytics/collectors.py +424 -0
- animus_forge/analytics/pipeline.py +462 -0
- animus_forge/analytics/reporters.py +371 -0
- animus_forge/analytics/visualizers.py +289 -0
- animus_forge/api.py +453 -0
- animus_forge/api_clients/__init__.py +18 -0
- animus_forge/api_clients/calendar_client.py +643 -0
- animus_forge/api_clients/claude_code_client.py +702 -0
- animus_forge/api_clients/github_client.py +196 -0
- animus_forge/api_clients/gmail_client.py +114 -0
- animus_forge/api_clients/notion_client.py +625 -0
- animus_forge/api_clients/openai_client.py +136 -0
- animus_forge/api_clients/resilience.py +267 -0
- animus_forge/api_clients/slack_client.py +450 -0
- animus_forge/api_errors.py +426 -0
- animus_forge/api_models.py +194 -0
- animus_forge/api_routes/__init__.py +1 -0
- animus_forge/api_routes/auth.py +67 -0
- animus_forge/api_routes/budgets.py +171 -0
- animus_forge/api_routes/coordination.py +137 -0
- animus_forge/api_routes/dashboard.py +422 -0
- animus_forge/api_routes/executions.py +384 -0
- animus_forge/api_routes/graph.py +432 -0
- animus_forge/api_routes/health.py +192 -0
- animus_forge/api_routes/history.py +73 -0
- animus_forge/api_routes/jobs.py +119 -0
- animus_forge/api_routes/mcp.py +167 -0
- animus_forge/api_routes/prompts.py +53 -0
- animus_forge/api_routes/schedules.py +131 -0
- animus_forge/api_routes/settings.py +114 -0
- animus_forge/api_routes/webhooks.py +247 -0
- animus_forge/api_routes/websocket.py +39 -0
- animus_forge/api_routes/workflows.py +410 -0
- animus_forge/api_state.py +103 -0
- animus_forge/auth/__init__.py +25 -0
- animus_forge/auth/tenants.py +740 -0
- animus_forge/auth/token_auth.py +61 -0
- animus_forge/browser/__init__.py +30 -0
- animus_forge/browser/automation.py +933 -0
- animus_forge/budget/__init__.py +84 -0
- animus_forge/budget/manager.py +383 -0
- animus_forge/budget/models.py +93 -0
- animus_forge/budget/persistence.py +314 -0
- animus_forge/budget/preflight.py +383 -0
- animus_forge/budget/strategies.py +347 -0
- animus_forge/cache/__init__.py +33 -0
- animus_forge/cache/backends.py +406 -0
- animus_forge/cache/decorators.py +284 -0
- animus_forge/cli/__init__.py +5 -0
- animus_forge/cli/commands/__init__.py +1 -0
- animus_forge/cli/commands/admin.py +244 -0
- animus_forge/cli/commands/browser.py +188 -0
- animus_forge/cli/commands/budget.py +155 -0
- animus_forge/cli/commands/calendar_cmd.py +335 -0
- animus_forge/cli/commands/config.py +132 -0
- animus_forge/cli/commands/coordination.py +189 -0
- animus_forge/cli/commands/dev.py +583 -0
- animus_forge/cli/commands/eval_cmd.py +221 -0
- animus_forge/cli/commands/graph.py +301 -0
- animus_forge/cli/commands/history.py +134 -0
- animus_forge/cli/commands/mcp.py +220 -0
- animus_forge/cli/commands/memory.py +128 -0
- animus_forge/cli/commands/metrics.py +132 -0
- animus_forge/cli/commands/schedule.py +152 -0
- animus_forge/cli/commands/self_improve.py +180 -0
- animus_forge/cli/commands/setup.py +197 -0
- animus_forge/cli/commands/workflow.py +380 -0
- animus_forge/cli/detection.py +119 -0
- animus_forge/cli/helpers.py +114 -0
- animus_forge/cli/interactive_runner.py +502 -0
- animus_forge/cli/main.py +205 -0
- animus_forge/cli/rich_output.py +448 -0
- animus_forge/config/__init__.py +13 -0
- animus_forge/config/logging.py +122 -0
- animus_forge/config/settings.py +450 -0
- animus_forge/contracts/__init__.py +31 -0
- animus_forge/contracts/base.py +141 -0
- animus_forge/contracts/definitions.py +891 -0
- animus_forge/contracts/enforcer.py +297 -0
- animus_forge/contracts/validator.py +290 -0
- animus_forge/dashboard/__init__.py +5 -0
- animus_forge/dashboard/app.py +518 -0
- animus_forge/dashboard/cost_dashboard.py +361 -0
- animus_forge/dashboard/eval_page.py +113 -0
- animus_forge/dashboard/mcp_page.py +417 -0
- animus_forge/dashboard/monitoring_pages.py +1104 -0
- animus_forge/dashboard/plugin_marketplace.py +765 -0
- animus_forge/dashboard/workflow_builder/__init__.py +64 -0
- animus_forge/dashboard/workflow_builder/builder.py +118 -0
- animus_forge/dashboard/workflow_builder/constants.py +467 -0
- animus_forge/dashboard/workflow_builder/persistence.py +193 -0
- animus_forge/dashboard/workflow_builder/renderers/__init__.py +37 -0
- animus_forge/dashboard/workflow_builder/renderers/_helpers.py +20 -0
- animus_forge/dashboard/workflow_builder/renderers/canvas.py +303 -0
- animus_forge/dashboard/workflow_builder/renderers/execution.py +130 -0
- animus_forge/dashboard/workflow_builder/renderers/node_config.py +246 -0
- animus_forge/dashboard/workflow_builder/renderers/visualization.py +234 -0
- animus_forge/dashboard/workflow_builder/renderers/workflow_io.py +191 -0
- animus_forge/dashboard/workflow_builder/state.py +143 -0
- animus_forge/dashboard/workflow_builder/yaml_ops.py +145 -0
- animus_forge/dashboard/workflow_visualizer.py +423 -0
- animus_forge/db.py +353 -0
- animus_forge/errors.py +146 -0
- animus_forge/evaluation/__init__.py +57 -0
- animus_forge/evaluation/base.py +438 -0
- animus_forge/evaluation/loader.py +157 -0
- animus_forge/evaluation/metrics.py +567 -0
- animus_forge/evaluation/reporters.py +388 -0
- animus_forge/evaluation/runner.py +405 -0
- animus_forge/evaluation/store.py +333 -0
- animus_forge/executions/__init__.py +21 -0
- animus_forge/executions/manager.py +768 -0
- animus_forge/executions/models.py +97 -0
- animus_forge/http/__init__.py +33 -0
- animus_forge/http/client.py +267 -0
- animus_forge/intelligence/__init__.py +87 -0
- animus_forge/intelligence/cost_intelligence.py +824 -0
- animus_forge/intelligence/cross_workflow_memory.py +620 -0
- animus_forge/intelligence/feedback_engine.py +775 -0
- animus_forge/intelligence/integration_graph.py +450 -0
- animus_forge/intelligence/outcome_tracker.py +384 -0
- animus_forge/intelligence/prompt_evolution.py +605 -0
- animus_forge/intelligence/provider_router.py +584 -0
- animus_forge/jobs/__init__.py +13 -0
- animus_forge/jobs/job_manager.py +433 -0
- animus_forge/mcp/__init__.py +32 -0
- animus_forge/mcp/client.py +253 -0
- animus_forge/mcp/manager.py +682 -0
- animus_forge/mcp/models.py +140 -0
- animus_forge/messaging/__init__.py +27 -0
- animus_forge/messaging/base.py +222 -0
- animus_forge/messaging/discord_bot.py +529 -0
- animus_forge/messaging/telegram_bot.py +591 -0
- animus_forge/metrics/__init__.py +79 -0
- animus_forge/metrics/audit_checks.py +409 -0
- animus_forge/metrics/collector.py +450 -0
- animus_forge/metrics/cost_tracker.py +497 -0
- animus_forge/metrics/debt_monitor.py +737 -0
- animus_forge/metrics/exporters.py +267 -0
- animus_forge/metrics/prometheus_server.py +423 -0
- animus_forge/monitoring/__init__.py +49 -0
- animus_forge/monitoring/metrics.py +383 -0
- animus_forge/monitoring/parallel_tracker.py +724 -0
- animus_forge/monitoring/tracker.py +214 -0
- animus_forge/monitoring/watchers.py +793 -0
- animus_forge/notifications/__init__.py +31 -0
- animus_forge/notifications/base.py +28 -0
- animus_forge/notifications/channels/__init__.py +17 -0
- animus_forge/notifications/channels/discord.py +117 -0
- animus_forge/notifications/channels/email_channel.py +141 -0
- animus_forge/notifications/channels/pagerduty.py +113 -0
- animus_forge/notifications/channels/slack.py +121 -0
- animus_forge/notifications/channels/teams.py +97 -0
- animus_forge/notifications/channels/webhook.py +50 -0
- animus_forge/notifications/manager.py +189 -0
- animus_forge/notifications/models.py +42 -0
- animus_forge/notifications/notifier.py +46 -0
- animus_forge/orchestrator/__init__.py +23 -0
- animus_forge/orchestrator/workflow_engine.py +54 -0
- animus_forge/orchestrator/workflow_engine_adapter.py +262 -0
- animus_forge/plugins/__init__.py +66 -0
- animus_forge/plugins/base.py +257 -0
- animus_forge/plugins/installer.py +684 -0
- animus_forge/plugins/loader.py +309 -0
- animus_forge/plugins/marketplace.py +567 -0
- animus_forge/plugins/models.py +146 -0
- animus_forge/plugins/registry.py +264 -0
- animus_forge/prompts/__init__.py +5 -0
- animus_forge/prompts/template_manager.py +177 -0
- animus_forge/providers/__init__.py +69 -0
- animus_forge/providers/anthropic_provider.py +287 -0
- animus_forge/providers/azure_openai_provider.py +371 -0
- animus_forge/providers/base.py +385 -0
- animus_forge/providers/bedrock_provider.py +408 -0
- animus_forge/providers/hardware.py +206 -0
- animus_forge/providers/manager.py +444 -0
- animus_forge/providers/mock_provider.py +123 -0
- animus_forge/providers/ollama_provider.py +518 -0
- animus_forge/providers/openai_provider.py +279 -0
- animus_forge/providers/router.py +374 -0
- animus_forge/providers/vertex_provider.py +425 -0
- animus_forge/ratelimit/__init__.py +42 -0
- animus_forge/ratelimit/limiter.py +358 -0
- animus_forge/ratelimit/provider.py +353 -0
- animus_forge/ratelimit/quota.py +324 -0
- animus_forge/resilience/__init__.py +51 -0
- animus_forge/resilience/bulkhead.py +343 -0
- animus_forge/resilience/concurrency.py +345 -0
- animus_forge/resilience/fallback.py +376 -0
- animus_forge/scheduler/__init__.py +21 -0
- animus_forge/scheduler/schedule_manager.py +579 -0
- animus_forge/security/__init__.py +43 -0
- animus_forge/security/audit_log.py +190 -0
- animus_forge/security/brute_force.py +366 -0
- animus_forge/security/field_encryption.py +126 -0
- animus_forge/security/request_limits.py +155 -0
- animus_forge/self_improve/__init__.py +43 -0
- animus_forge/self_improve/analyzer.py +475 -0
- animus_forge/self_improve/approval.py +395 -0
- animus_forge/self_improve/orchestrator.py +564 -0
- animus_forge/self_improve/pr_manager.py +448 -0
- animus_forge/self_improve/rollback.py +277 -0
- animus_forge/self_improve/safety.py +291 -0
- animus_forge/self_improve/sandbox.py +333 -0
- animus_forge/settings/__init__.py +14 -0
- animus_forge/settings/manager.py +327 -0
- animus_forge/settings/models.py +62 -0
- animus_forge/skills/__init__.py +91 -0
- animus_forge/skills/consensus.py +292 -0
- animus_forge/skills/enforcer.py +169 -0
- animus_forge/skills/evolver/__init__.py +37 -0
- animus_forge/skills/evolver/ab_test.py +278 -0
- animus_forge/skills/evolver/analyzer.py +209 -0
- animus_forge/skills/evolver/deprecator.py +211 -0
- animus_forge/skills/evolver/evolver.py +298 -0
- animus_forge/skills/evolver/generator.py +124 -0
- animus_forge/skills/evolver/metrics.py +337 -0
- animus_forge/skills/evolver/models.py +106 -0
- animus_forge/skills/evolver/tuner.py +206 -0
- animus_forge/skills/evolver/versioner.py +146 -0
- animus_forge/skills/evolver/writer.py +177 -0
- animus_forge/skills/library.py +250 -0
- animus_forge/skills/loader.py +217 -0
- animus_forge/skills/models.py +159 -0
- animus_forge/state/__init__.py +54 -0
- animus_forge/state/agent_context.py +455 -0
- animus_forge/state/agent_memory.py +437 -0
- animus_forge/state/backends.py +312 -0
- animus_forge/state/checkpoint.py +285 -0
- animus_forge/state/context_window.py +411 -0
- animus_forge/state/database.py +30 -0
- animus_forge/state/memory.py +22 -0
- animus_forge/state/memory_models.py +120 -0
- animus_forge/state/migrations.py +170 -0
- animus_forge/state/persistence.py +428 -0
- animus_forge/tools/__init__.py +26 -0
- animus_forge/tools/filesystem.py +376 -0
- animus_forge/tools/models.py +133 -0
- animus_forge/tools/proposals.py +315 -0
- animus_forge/tools/safety.py +264 -0
- animus_forge/tracing/__init__.py +65 -0
- animus_forge/tracing/context.py +337 -0
- animus_forge/tracing/export.py +412 -0
- animus_forge/tracing/middleware.py +233 -0
- animus_forge/tracing/propagation.py +237 -0
- animus_forge/tui/__init__.py +5 -0
- animus_forge/tui/app.py +317 -0
- animus_forge/tui/chat_screen.py +46 -0
- animus_forge/tui/commands.py +413 -0
- animus_forge/tui/providers.py +73 -0
- animus_forge/tui/session.py +144 -0
- animus_forge/tui/streaming.py +196 -0
- animus_forge/tui/widgets/__init__.py +8 -0
- animus_forge/tui/widgets/chat_display.py +102 -0
- animus_forge/tui/widgets/input_bar.py +65 -0
- animus_forge/tui/widgets/sidebar.py +122 -0
- animus_forge/tui/widgets/status_bar.py +34 -0
- animus_forge/utils/__init__.py +45 -0
- animus_forge/utils/circuit_breaker.py +333 -0
- animus_forge/utils/retry.py +479 -0
- animus_forge/utils/validation.py +433 -0
- animus_forge/webhooks/__init__.py +33 -0
- animus_forge/webhooks/webhook_delivery.py +821 -0
- animus_forge/webhooks/webhook_manager.py +501 -0
- animus_forge/websocket/__init__.py +35 -0
- animus_forge/websocket/broadcaster.py +266 -0
- animus_forge/websocket/manager.py +286 -0
- animus_forge/websocket/messages.py +121 -0
- animus_forge/workflow/__init__.py +115 -0
- animus_forge/workflow/approval_store.py +204 -0
- animus_forge/workflow/arete_hooks.py +202 -0
- animus_forge/workflow/auto_parallel.py +277 -0
- animus_forge/workflow/composer.py +340 -0
- animus_forge/workflow/distributed_rate_limiter.py +425 -0
- animus_forge/workflow/executor.py +58 -0
- animus_forge/workflow/executor_ai.py +294 -0
- animus_forge/workflow/executor_approval.py +70 -0
- animus_forge/workflow/executor_arete.py +236 -0
- animus_forge/workflow/executor_clients.py +116 -0
- animus_forge/workflow/executor_core.py +700 -0
- animus_forge/workflow/executor_error.py +134 -0
- animus_forge/workflow/executor_integrations.py +856 -0
- animus_forge/workflow/executor_mcp.py +169 -0
- animus_forge/workflow/executor_parallel_exec.py +353 -0
- animus_forge/workflow/executor_patterns.py +720 -0
- animus_forge/workflow/executor_results.py +77 -0
- animus_forge/workflow/executor_step.py +289 -0
- animus_forge/workflow/graph_executor.py +617 -0
- animus_forge/workflow/graph_models.py +176 -0
- animus_forge/workflow/graph_walker.py +395 -0
- animus_forge/workflow/loader.py +580 -0
- animus_forge/workflow/parallel.py +639 -0
- animus_forge/workflow/rate_limited_executor.py +696 -0
- animus_forge/workflow/scheduler.py +477 -0
- animus_forge/workflow/version_manager.py +618 -0
- animus_forge/workflow/versioning.py +286 -0
- animus_forge-1.3.0.dist-info/METADATA +124 -0
- animus_forge-1.3.0.dist-info/RECORD +308 -0
- animus_forge-1.3.0.dist-info/WHEEL +5 -0
- animus_forge-1.3.0.dist-info/entry_points.txt +2 -0
- animus_forge-1.3.0.dist-info/top_level.txt +1 -0
animus_forge/__init__.py
ADDED
|
@@ -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}")
|