agent_os_kernel 3.1.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.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
iatp/ipc_pipes.py
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Typed IPC Pipes - Inter-Agent Communication with Policy Enforcement.
|
|
5
|
+
|
|
6
|
+
This module implements typed pipes for agent-to-agent communication,
|
|
7
|
+
inspired by UNIX pipes but with policy enforcement at the kernel level.
|
|
8
|
+
|
|
9
|
+
Instead of "Workflow Builders" (bloat), real engineers use pipes:
|
|
10
|
+
AgentA | PolicyCheck | AgentB
|
|
11
|
+
|
|
12
|
+
Design Philosophy:
|
|
13
|
+
- Type safety: Only matching types can connect
|
|
14
|
+
- Policy enforcement: Every message passes through policy check
|
|
15
|
+
- Backpressure: Slow consumers don't crash fast producers
|
|
16
|
+
- Audit trail: All pipe traffic is logged to flight recorder
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
# Create a typed pipe
|
|
20
|
+
pipe = TypedPipe[ResearchResult, SummaryRequest]("research-to-summary")
|
|
21
|
+
|
|
22
|
+
# Connect agents via pipe with policy enforcement
|
|
23
|
+
pipeline = (
|
|
24
|
+
research_agent
|
|
25
|
+
| PolicyCheckPipe(allowed_types=[ResearchResult])
|
|
26
|
+
| summary_agent
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Execute pipeline
|
|
30
|
+
result = await pipeline.execute(input_data)
|
|
31
|
+
|
|
32
|
+
This is the IATP extension for secure inter-agent communication.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from abc import ABC, abstractmethod
|
|
36
|
+
from dataclasses import dataclass, field
|
|
37
|
+
from datetime import datetime, timezone
|
|
38
|
+
from enum import Enum, auto
|
|
39
|
+
from typing import (
|
|
40
|
+
Any, AsyncIterator, Callable, Dict, Generic, List, Optional,
|
|
41
|
+
TypeVar, Union, Awaitable, Protocol, runtime_checkable
|
|
42
|
+
)
|
|
43
|
+
import asyncio
|
|
44
|
+
import hashlib
|
|
45
|
+
import json
|
|
46
|
+
import logging
|
|
47
|
+
from queue import Queue
|
|
48
|
+
from threading import Lock
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Type variables for generic pipes
|
|
54
|
+
T_In = TypeVar("T_In")
|
|
55
|
+
T_Out = TypeVar("T_Out")
|
|
56
|
+
T = TypeVar("T")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PipeState(Enum):
|
|
60
|
+
"""State of a pipe."""
|
|
61
|
+
CREATED = auto()
|
|
62
|
+
OPEN = auto()
|
|
63
|
+
FLOWING = auto()
|
|
64
|
+
BLOCKED = auto() # Backpressure
|
|
65
|
+
CLOSED = auto()
|
|
66
|
+
ERROR = auto()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class PipeMessage(Generic[T]):
|
|
71
|
+
"""
|
|
72
|
+
A message flowing through a pipe.
|
|
73
|
+
|
|
74
|
+
Every message carries metadata for policy enforcement and auditing.
|
|
75
|
+
"""
|
|
76
|
+
payload: T
|
|
77
|
+
message_id: str = field(default_factory=lambda: hashlib.sha256(
|
|
78
|
+
str(datetime.now(timezone.utc).timestamp()).encode()
|
|
79
|
+
).hexdigest()[:16])
|
|
80
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
81
|
+
source_agent: Optional[str] = None
|
|
82
|
+
target_agent: Optional[str] = None
|
|
83
|
+
trace_id: Optional[str] = None
|
|
84
|
+
|
|
85
|
+
# Policy enforcement metadata
|
|
86
|
+
policy_checked: bool = False
|
|
87
|
+
policy_result: Optional[str] = None
|
|
88
|
+
|
|
89
|
+
# Type information for runtime checking
|
|
90
|
+
payload_type: Optional[str] = None
|
|
91
|
+
|
|
92
|
+
def __post_init__(self):
|
|
93
|
+
if self.payload_type is None:
|
|
94
|
+
self.payload_type = type(self.payload).__name__
|
|
95
|
+
|
|
96
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
97
|
+
return {
|
|
98
|
+
"message_id": self.message_id,
|
|
99
|
+
"timestamp": self.timestamp.isoformat(),
|
|
100
|
+
"source_agent": self.source_agent,
|
|
101
|
+
"target_agent": self.target_agent,
|
|
102
|
+
"trace_id": self.trace_id,
|
|
103
|
+
"payload_type": self.payload_type,
|
|
104
|
+
"policy_checked": self.policy_checked,
|
|
105
|
+
"policy_result": self.policy_result,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class PipeConfig:
|
|
111
|
+
"""Configuration for a typed pipe."""
|
|
112
|
+
name: str
|
|
113
|
+
buffer_size: int = 100
|
|
114
|
+
timeout_seconds: float = 30.0
|
|
115
|
+
require_policy_check: bool = True
|
|
116
|
+
allowed_payload_types: List[str] = field(default_factory=list)
|
|
117
|
+
max_message_size_bytes: int = 10 * 1024 * 1024 # 10MB default
|
|
118
|
+
enable_compression: bool = False
|
|
119
|
+
enable_encryption: bool = False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@runtime_checkable
|
|
123
|
+
class PipeEndpoint(Protocol[T]):
|
|
124
|
+
"""Protocol for pipe endpoints (can send or receive)."""
|
|
125
|
+
|
|
126
|
+
async def send(self, message: PipeMessage[T]) -> bool:
|
|
127
|
+
"""Send a message through the pipe."""
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
async def receive(self) -> Optional[PipeMessage[T]]:
|
|
131
|
+
"""Receive a message from the pipe."""
|
|
132
|
+
...
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TypedPipe(Generic[T_In, T_Out]):
|
|
136
|
+
"""
|
|
137
|
+
A typed pipe for inter-agent communication.
|
|
138
|
+
|
|
139
|
+
Type parameters:
|
|
140
|
+
T_In: Type of messages this pipe accepts
|
|
141
|
+
T_Out: Type of messages this pipe produces
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
pipe = TypedPipe[ResearchQuery, ResearchResult]("research-pipe")
|
|
145
|
+
await pipe.send(PipeMessage(ResearchQuery(topic="AI Safety")))
|
|
146
|
+
result = await pipe.receive()
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(
|
|
150
|
+
self,
|
|
151
|
+
name: str,
|
|
152
|
+
config: Optional[PipeConfig] = None,
|
|
153
|
+
transform: Optional[Callable[[T_In], T_Out]] = None,
|
|
154
|
+
):
|
|
155
|
+
self.name = name
|
|
156
|
+
self.config = config or PipeConfig(name=name)
|
|
157
|
+
self._transform = transform
|
|
158
|
+
|
|
159
|
+
self._state = PipeState.CREATED
|
|
160
|
+
self._buffer: asyncio.Queue[PipeMessage] = asyncio.Queue(
|
|
161
|
+
maxsize=self.config.buffer_size
|
|
162
|
+
)
|
|
163
|
+
self._lock = asyncio.Lock()
|
|
164
|
+
self._message_count = 0
|
|
165
|
+
self._error_count = 0
|
|
166
|
+
|
|
167
|
+
# Policy enforcement callback
|
|
168
|
+
self._policy_check: Optional[Callable[[PipeMessage], Awaitable[bool]]] = None
|
|
169
|
+
|
|
170
|
+
# Audit callback
|
|
171
|
+
self._audit_callback: Optional[Callable[[PipeMessage, str], None]] = None
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def state(self) -> PipeState:
|
|
175
|
+
return self._state
|
|
176
|
+
|
|
177
|
+
def open(self) -> None:
|
|
178
|
+
"""Open the pipe for communication."""
|
|
179
|
+
self._state = PipeState.OPEN
|
|
180
|
+
logger.info(f"[Pipe] {self.name} opened")
|
|
181
|
+
|
|
182
|
+
def close(self) -> None:
|
|
183
|
+
"""Close the pipe."""
|
|
184
|
+
self._state = PipeState.CLOSED
|
|
185
|
+
logger.info(f"[Pipe] {self.name} closed")
|
|
186
|
+
|
|
187
|
+
def set_policy_check(
|
|
188
|
+
self,
|
|
189
|
+
check: Callable[[PipeMessage], Awaitable[bool]]
|
|
190
|
+
) -> None:
|
|
191
|
+
"""Set the policy check callback."""
|
|
192
|
+
self._policy_check = check
|
|
193
|
+
|
|
194
|
+
def set_audit_callback(
|
|
195
|
+
self,
|
|
196
|
+
callback: Callable[[PipeMessage, str], None]
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Set the audit callback for message logging."""
|
|
199
|
+
self._audit_callback = callback
|
|
200
|
+
|
|
201
|
+
async def send(self, message: PipeMessage[T_In]) -> bool:
|
|
202
|
+
"""
|
|
203
|
+
Send a message through the pipe.
|
|
204
|
+
|
|
205
|
+
Returns True if message was sent, False if rejected by policy.
|
|
206
|
+
"""
|
|
207
|
+
if self._state not in (PipeState.OPEN, PipeState.FLOWING):
|
|
208
|
+
raise RuntimeError(f"Pipe {self.name} is not open (state: {self._state})")
|
|
209
|
+
|
|
210
|
+
# Policy check
|
|
211
|
+
if self.config.require_policy_check:
|
|
212
|
+
if self._policy_check:
|
|
213
|
+
try:
|
|
214
|
+
allowed = await self._policy_check(message)
|
|
215
|
+
message.policy_checked = True
|
|
216
|
+
message.policy_result = "ALLOWED" if allowed else "DENIED"
|
|
217
|
+
|
|
218
|
+
if not allowed:
|
|
219
|
+
logger.warning(
|
|
220
|
+
f"[Pipe] {self.name} rejected message {message.message_id} "
|
|
221
|
+
f"by policy"
|
|
222
|
+
)
|
|
223
|
+
if self._audit_callback:
|
|
224
|
+
self._audit_callback(message, "POLICY_DENIED")
|
|
225
|
+
return False
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"[Pipe] Policy check failed: {e}")
|
|
228
|
+
message.policy_result = f"ERROR: {e}"
|
|
229
|
+
self._error_count += 1
|
|
230
|
+
return False
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(f"[Pipe] {self.name} has no policy check configured")
|
|
233
|
+
|
|
234
|
+
# Type checking
|
|
235
|
+
if self.config.allowed_payload_types:
|
|
236
|
+
if message.payload_type not in self.config.allowed_payload_types:
|
|
237
|
+
logger.error(
|
|
238
|
+
f"[Pipe] {self.name} rejected message with type "
|
|
239
|
+
f"{message.payload_type} (allowed: {self.config.allowed_payload_types})"
|
|
240
|
+
)
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
# Apply transform if configured
|
|
244
|
+
if self._transform:
|
|
245
|
+
try:
|
|
246
|
+
transformed_payload = self._transform(message.payload)
|
|
247
|
+
message = PipeMessage(
|
|
248
|
+
payload=transformed_payload,
|
|
249
|
+
message_id=message.message_id,
|
|
250
|
+
timestamp=message.timestamp,
|
|
251
|
+
source_agent=message.source_agent,
|
|
252
|
+
target_agent=message.target_agent,
|
|
253
|
+
trace_id=message.trace_id,
|
|
254
|
+
policy_checked=message.policy_checked,
|
|
255
|
+
policy_result=message.policy_result,
|
|
256
|
+
)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"[Pipe] Transform failed: {e}")
|
|
259
|
+
self._error_count += 1
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
# Queue message (with backpressure)
|
|
263
|
+
try:
|
|
264
|
+
await asyncio.wait_for(
|
|
265
|
+
self._buffer.put(message),
|
|
266
|
+
timeout=self.config.timeout_seconds
|
|
267
|
+
)
|
|
268
|
+
self._message_count += 1
|
|
269
|
+
self._state = PipeState.FLOWING
|
|
270
|
+
|
|
271
|
+
if self._audit_callback:
|
|
272
|
+
self._audit_callback(message, "SENT")
|
|
273
|
+
|
|
274
|
+
return True
|
|
275
|
+
except asyncio.TimeoutError:
|
|
276
|
+
logger.warning(f"[Pipe] {self.name} timeout - backpressure")
|
|
277
|
+
self._state = PipeState.BLOCKED
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
async def receive(self) -> Optional[PipeMessage[T_Out]]:
|
|
281
|
+
"""
|
|
282
|
+
Receive a message from the pipe.
|
|
283
|
+
|
|
284
|
+
Returns None if pipe is closed or timeout occurs.
|
|
285
|
+
"""
|
|
286
|
+
if self._state == PipeState.CLOSED:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
message = await asyncio.wait_for(
|
|
291
|
+
self._buffer.get(),
|
|
292
|
+
timeout=self.config.timeout_seconds
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if self._audit_callback:
|
|
296
|
+
self._audit_callback(message, "RECEIVED")
|
|
297
|
+
|
|
298
|
+
return message
|
|
299
|
+
except asyncio.TimeoutError:
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
303
|
+
"""Get pipe statistics."""
|
|
304
|
+
return {
|
|
305
|
+
"name": self.name,
|
|
306
|
+
"state": self._state.name,
|
|
307
|
+
"message_count": self._message_count,
|
|
308
|
+
"error_count": self._error_count,
|
|
309
|
+
"buffer_size": self._buffer.qsize(),
|
|
310
|
+
"buffer_max": self.config.buffer_size,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class PolicyCheckPipe(TypedPipe[T, T]):
|
|
315
|
+
"""
|
|
316
|
+
A pipe that only performs policy checking (pass-through).
|
|
317
|
+
|
|
318
|
+
Use this in pipelines to enforce policy at specific points:
|
|
319
|
+
agent_a | PolicyCheckPipe(policy_engine) | agent_b
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
name: str = "policy-check",
|
|
325
|
+
policy_engine: Optional[Any] = None, # PolicyEngine from control plane
|
|
326
|
+
allowed_types: Optional[List[str]] = None,
|
|
327
|
+
):
|
|
328
|
+
config = PipeConfig(
|
|
329
|
+
name=name,
|
|
330
|
+
require_policy_check=True,
|
|
331
|
+
allowed_payload_types=allowed_types or [],
|
|
332
|
+
)
|
|
333
|
+
super().__init__(name, config)
|
|
334
|
+
self._policy_engine = policy_engine
|
|
335
|
+
|
|
336
|
+
# Set up policy check
|
|
337
|
+
if policy_engine:
|
|
338
|
+
self.set_policy_check(self._check_with_engine)
|
|
339
|
+
|
|
340
|
+
async def _check_with_engine(self, message: PipeMessage) -> bool:
|
|
341
|
+
"""Check message against policy engine."""
|
|
342
|
+
if not self._policy_engine:
|
|
343
|
+
return True
|
|
344
|
+
|
|
345
|
+
# Integration with control plane PolicyEngine
|
|
346
|
+
try:
|
|
347
|
+
# This would integrate with the actual policy engine
|
|
348
|
+
# For now, always allow
|
|
349
|
+
return True
|
|
350
|
+
except Exception as e:
|
|
351
|
+
logger.error(f"[PolicyCheckPipe] Engine check failed: {e}")
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@runtime_checkable
|
|
356
|
+
class PipelineStage(Protocol):
|
|
357
|
+
"""Protocol for pipeline stages."""
|
|
358
|
+
|
|
359
|
+
async def process(self, message: PipeMessage) -> Optional[PipeMessage]:
|
|
360
|
+
"""Process a message and optionally produce output."""
|
|
361
|
+
...
|
|
362
|
+
|
|
363
|
+
def __or__(self, other: "PipelineStage") -> "Pipeline":
|
|
364
|
+
"""Support pipe operator: stage1 | stage2"""
|
|
365
|
+
...
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class AgentPipelineStage:
|
|
369
|
+
"""
|
|
370
|
+
Wraps an agent as a pipeline stage.
|
|
371
|
+
|
|
372
|
+
Example:
|
|
373
|
+
stage = AgentPipelineStage(my_agent, input_type="Query", output_type="Response")
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
def __init__(
|
|
377
|
+
self,
|
|
378
|
+
agent: Any,
|
|
379
|
+
agent_id: str,
|
|
380
|
+
process_method: str = "process",
|
|
381
|
+
input_type: Optional[str] = None,
|
|
382
|
+
output_type: Optional[str] = None,
|
|
383
|
+
):
|
|
384
|
+
self.agent = agent
|
|
385
|
+
self.agent_id = agent_id
|
|
386
|
+
self._process_method = process_method
|
|
387
|
+
self.input_type = input_type
|
|
388
|
+
self.output_type = output_type
|
|
389
|
+
|
|
390
|
+
async def process(self, message: PipeMessage) -> Optional[PipeMessage]:
|
|
391
|
+
"""Process a message through the agent."""
|
|
392
|
+
method = getattr(self.agent, self._process_method, None)
|
|
393
|
+
if not method:
|
|
394
|
+
raise AttributeError(
|
|
395
|
+
f"Agent {self.agent_id} has no method '{self._process_method}'"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Call agent's process method
|
|
399
|
+
if asyncio.iscoroutinefunction(method):
|
|
400
|
+
result = await method(message.payload)
|
|
401
|
+
else:
|
|
402
|
+
result = method(message.payload)
|
|
403
|
+
|
|
404
|
+
if result is None:
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
return PipeMessage(
|
|
408
|
+
payload=result,
|
|
409
|
+
source_agent=self.agent_id,
|
|
410
|
+
trace_id=message.trace_id,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
def __or__(self, other: "AgentPipelineStage") -> "Pipeline":
|
|
414
|
+
"""Support pipe operator."""
|
|
415
|
+
return Pipeline([self, other])
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class Pipeline:
|
|
419
|
+
"""
|
|
420
|
+
A pipeline of connected stages.
|
|
421
|
+
|
|
422
|
+
Example:
|
|
423
|
+
pipeline = Pipeline([
|
|
424
|
+
AgentPipelineStage(research_agent, "research"),
|
|
425
|
+
PolicyCheckPipe(),
|
|
426
|
+
AgentPipelineStage(summary_agent, "summary"),
|
|
427
|
+
])
|
|
428
|
+
|
|
429
|
+
result = await pipeline.execute(input_message)
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
def __init__(
|
|
433
|
+
self,
|
|
434
|
+
stages: Optional[List[Union[AgentPipelineStage, TypedPipe]]] = None,
|
|
435
|
+
name: str = "pipeline",
|
|
436
|
+
):
|
|
437
|
+
self.name = name
|
|
438
|
+
self.stages: List[Union[AgentPipelineStage, TypedPipe]] = stages or []
|
|
439
|
+
self._trace_id: Optional[str] = None
|
|
440
|
+
|
|
441
|
+
def add_stage(self, stage: Union[AgentPipelineStage, TypedPipe]) -> "Pipeline":
|
|
442
|
+
"""Add a stage to the pipeline."""
|
|
443
|
+
self.stages.append(stage)
|
|
444
|
+
return self
|
|
445
|
+
|
|
446
|
+
def __or__(self, other: Union[AgentPipelineStage, TypedPipe, "Pipeline"]) -> "Pipeline":
|
|
447
|
+
"""Support pipe operator: pipeline | stage"""
|
|
448
|
+
if isinstance(other, Pipeline):
|
|
449
|
+
return Pipeline(self.stages + other.stages, self.name)
|
|
450
|
+
else:
|
|
451
|
+
return Pipeline(self.stages + [other], self.name)
|
|
452
|
+
|
|
453
|
+
async def execute(
|
|
454
|
+
self,
|
|
455
|
+
input_data: Any,
|
|
456
|
+
trace_id: Optional[str] = None,
|
|
457
|
+
) -> Optional[PipeMessage]:
|
|
458
|
+
"""
|
|
459
|
+
Execute the pipeline with input data.
|
|
460
|
+
|
|
461
|
+
Returns the final message, or None if pipeline filtered it out.
|
|
462
|
+
"""
|
|
463
|
+
self._trace_id = trace_id or hashlib.sha256(
|
|
464
|
+
str(datetime.now(timezone.utc).timestamp()).encode()
|
|
465
|
+
).hexdigest()[:16]
|
|
466
|
+
|
|
467
|
+
# Create initial message
|
|
468
|
+
current_message = PipeMessage(
|
|
469
|
+
payload=input_data,
|
|
470
|
+
trace_id=self._trace_id,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
logger.info(f"[Pipeline] {self.name} executing with trace {self._trace_id}")
|
|
474
|
+
|
|
475
|
+
for i, stage in enumerate(self.stages):
|
|
476
|
+
stage_name = getattr(stage, 'name', None) or getattr(stage, 'agent_id', f'stage-{i}')
|
|
477
|
+
logger.debug(f"[Pipeline] Processing stage: {stage_name}")
|
|
478
|
+
|
|
479
|
+
if isinstance(stage, TypedPipe):
|
|
480
|
+
# For pipes, send and receive
|
|
481
|
+
stage.open()
|
|
482
|
+
if not await stage.send(current_message):
|
|
483
|
+
logger.warning(f"[Pipeline] Stage {stage_name} rejected message")
|
|
484
|
+
return None
|
|
485
|
+
current_message = await stage.receive()
|
|
486
|
+
stage.close()
|
|
487
|
+
elif isinstance(stage, AgentPipelineStage):
|
|
488
|
+
# For agent stages, process directly
|
|
489
|
+
current_message = await stage.process(current_message)
|
|
490
|
+
else:
|
|
491
|
+
# Generic stage with process method
|
|
492
|
+
if hasattr(stage, 'process'):
|
|
493
|
+
if asyncio.iscoroutinefunction(stage.process):
|
|
494
|
+
current_message = await stage.process(current_message)
|
|
495
|
+
else:
|
|
496
|
+
current_message = stage.process(current_message)
|
|
497
|
+
|
|
498
|
+
if current_message is None:
|
|
499
|
+
logger.info(f"[Pipeline] Stage {stage_name} filtered out message")
|
|
500
|
+
return None
|
|
501
|
+
|
|
502
|
+
# Update target agent for next stage
|
|
503
|
+
if i + 1 < len(self.stages):
|
|
504
|
+
next_stage = self.stages[i + 1]
|
|
505
|
+
current_message.target_agent = getattr(
|
|
506
|
+
next_stage, 'agent_id',
|
|
507
|
+
getattr(next_stage, 'name', None)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
logger.info(f"[Pipeline] {self.name} completed trace {self._trace_id}")
|
|
511
|
+
return current_message
|
|
512
|
+
|
|
513
|
+
async def execute_streaming(
|
|
514
|
+
self,
|
|
515
|
+
input_data: Any,
|
|
516
|
+
trace_id: Optional[str] = None,
|
|
517
|
+
) -> AsyncIterator[PipeMessage]:
|
|
518
|
+
"""
|
|
519
|
+
Execute pipeline with streaming output.
|
|
520
|
+
|
|
521
|
+
Yields messages as they flow through stages.
|
|
522
|
+
"""
|
|
523
|
+
self._trace_id = trace_id or hashlib.sha256(
|
|
524
|
+
str(datetime.now(timezone.utc).timestamp()).encode()
|
|
525
|
+
).hexdigest()[:16]
|
|
526
|
+
|
|
527
|
+
current_message = PipeMessage(
|
|
528
|
+
payload=input_data,
|
|
529
|
+
trace_id=self._trace_id,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
yield current_message
|
|
533
|
+
|
|
534
|
+
for stage in self.stages:
|
|
535
|
+
if isinstance(stage, TypedPipe):
|
|
536
|
+
stage.open()
|
|
537
|
+
await stage.send(current_message)
|
|
538
|
+
current_message = await stage.receive()
|
|
539
|
+
stage.close()
|
|
540
|
+
elif hasattr(stage, 'process'):
|
|
541
|
+
if asyncio.iscoroutinefunction(stage.process):
|
|
542
|
+
current_message = await stage.process(current_message)
|
|
543
|
+
else:
|
|
544
|
+
current_message = stage.process(current_message)
|
|
545
|
+
|
|
546
|
+
if current_message:
|
|
547
|
+
yield current_message
|
|
548
|
+
else:
|
|
549
|
+
return
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
# ========== Convenience Functions ==========
|
|
553
|
+
|
|
554
|
+
def create_pipeline(*stages: Union[AgentPipelineStage, TypedPipe]) -> Pipeline:
|
|
555
|
+
"""Create a pipeline from stages."""
|
|
556
|
+
return Pipeline(list(stages))
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def pipe_agents(
|
|
560
|
+
source_agent: Any,
|
|
561
|
+
source_id: str,
|
|
562
|
+
target_agent: Any,
|
|
563
|
+
target_id: str,
|
|
564
|
+
policy_engine: Optional[Any] = None,
|
|
565
|
+
) -> Pipeline:
|
|
566
|
+
"""
|
|
567
|
+
Create a simple two-agent pipeline with policy check.
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
pipeline = pipe_agents(
|
|
571
|
+
research_agent, "research",
|
|
572
|
+
summary_agent, "summary",
|
|
573
|
+
policy_engine=my_policy_engine
|
|
574
|
+
)
|
|
575
|
+
"""
|
|
576
|
+
return Pipeline([
|
|
577
|
+
AgentPipelineStage(source_agent, source_id),
|
|
578
|
+
PolicyCheckPipe(policy_engine=policy_engine),
|
|
579
|
+
AgentPipelineStage(target_agent, target_id),
|
|
580
|
+
])
|