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
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Listener Agent - Layer 5 Reference Implementation
|
|
5
|
+
|
|
6
|
+
The Listener Agent is a passive observer that monitors graph states
|
|
7
|
+
without interfering until configured thresholds are exceeded.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
- Consolidates: agent-control-plane, scak, iatp, caas
|
|
11
|
+
- Pattern: Observer with threshold-based intervention
|
|
12
|
+
- Principle: Monitor passively, intervene only when necessary
|
|
13
|
+
|
|
14
|
+
This module is pure wiring - it delegates to lower layers:
|
|
15
|
+
- Knowledge graph operations → scak (intelligence layer)
|
|
16
|
+
- Security validation → iatp (security layer)
|
|
17
|
+
- Context management → caas (context layer)
|
|
18
|
+
- Base orchestration → agent-control-plane
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Dict, Any, Optional, List, Callable
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from datetime import datetime, timedelta
|
|
24
|
+
from enum import Enum, auto
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
from collections import deque
|
|
28
|
+
|
|
29
|
+
from ..knowledge_graph.multidimensional_graph import MultidimensionalKnowledgeGraph
|
|
30
|
+
from ..core.handshake_protocol import HandshakeProtocol, HandshakeSession, HandshakeState
|
|
31
|
+
from ..core.reasoning_agent import ReasoningAgent
|
|
32
|
+
from ..core.execution_agent import ExecutionAgent
|
|
33
|
+
from ..super_system.router import SuperSystemRouter
|
|
34
|
+
|
|
35
|
+
from .threshold_config import (
|
|
36
|
+
ThresholdConfig,
|
|
37
|
+
ThresholdType,
|
|
38
|
+
InterventionLevel,
|
|
39
|
+
ThresholdRule,
|
|
40
|
+
DEFAULT_THRESHOLDS,
|
|
41
|
+
)
|
|
42
|
+
from .state_observer import StateObserver, ObservationResult
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ListenerState(Enum):
|
|
46
|
+
"""States of the Listener Agent."""
|
|
47
|
+
|
|
48
|
+
# Not actively observing
|
|
49
|
+
IDLE = "idle"
|
|
50
|
+
|
|
51
|
+
# Passively observing - no intervention
|
|
52
|
+
OBSERVING = "observing"
|
|
53
|
+
|
|
54
|
+
# Detected threshold breach - evaluating response
|
|
55
|
+
EVALUATING = "evaluating"
|
|
56
|
+
|
|
57
|
+
# Actively intervening
|
|
58
|
+
INTERVENING = "intervening"
|
|
59
|
+
|
|
60
|
+
# Intervention complete, returning to observation
|
|
61
|
+
RECOVERING = "recovering"
|
|
62
|
+
|
|
63
|
+
# Stopped - not operational
|
|
64
|
+
STOPPED = "stopped"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class InterventionEvent:
|
|
69
|
+
"""
|
|
70
|
+
Record of a Listener intervention.
|
|
71
|
+
|
|
72
|
+
This provides an audit trail of when and why the Listener
|
|
73
|
+
transitioned from passive observation to active intervention.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
event_id: str
|
|
77
|
+
timestamp: datetime
|
|
78
|
+
triggered_rules: List[ThresholdRule]
|
|
79
|
+
intervention_level: InterventionLevel
|
|
80
|
+
metrics_snapshot: Dict[str, float]
|
|
81
|
+
context: Dict[str, Any]
|
|
82
|
+
action_taken: str
|
|
83
|
+
outcome: Optional[str] = None
|
|
84
|
+
duration_ms: Optional[float] = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class ListenerConfig:
|
|
89
|
+
"""Configuration for the Listener Agent."""
|
|
90
|
+
|
|
91
|
+
# Threshold configuration
|
|
92
|
+
thresholds: ThresholdConfig = field(default_factory=lambda: DEFAULT_THRESHOLDS)
|
|
93
|
+
|
|
94
|
+
# Observation settings
|
|
95
|
+
observation_interval_seconds: float = 1.0
|
|
96
|
+
max_observation_history: int = 1000
|
|
97
|
+
|
|
98
|
+
# Intervention settings
|
|
99
|
+
auto_intervention: bool = True
|
|
100
|
+
require_confirmation: bool = False
|
|
101
|
+
max_interventions_per_minute: int = 10
|
|
102
|
+
|
|
103
|
+
# Recovery settings
|
|
104
|
+
recovery_observation_count: int = 5
|
|
105
|
+
recovery_success_threshold: float = 0.8
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ListenerAgent:
|
|
109
|
+
"""
|
|
110
|
+
Layer 5 Reference Implementation: The Listener Agent
|
|
111
|
+
|
|
112
|
+
A passive observer that monitors graph states and only intervenes
|
|
113
|
+
when configured thresholds are exceeded.
|
|
114
|
+
|
|
115
|
+
Design Principles:
|
|
116
|
+
1. Passive by default - observe without interference
|
|
117
|
+
2. Threshold-driven intervention - clear, configurable triggers
|
|
118
|
+
3. Minimal footprint - delegate to lower layers
|
|
119
|
+
4. Full audit trail - every intervention is logged
|
|
120
|
+
|
|
121
|
+
Usage:
|
|
122
|
+
```python
|
|
123
|
+
# Create core components
|
|
124
|
+
kg = MultidimensionalKnowledgeGraph()
|
|
125
|
+
protocol = HandshakeProtocol()
|
|
126
|
+
router = SuperSystemRouter(kg)
|
|
127
|
+
|
|
128
|
+
# Create and start listener
|
|
129
|
+
listener = ListenerAgent(kg, protocol, router)
|
|
130
|
+
listener.start()
|
|
131
|
+
|
|
132
|
+
# Listener now monitors passively...
|
|
133
|
+
# When thresholds are exceeded, it intervenes automatically
|
|
134
|
+
|
|
135
|
+
# Stop when done
|
|
136
|
+
listener.stop()
|
|
137
|
+
```
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
knowledge_graph: MultidimensionalKnowledgeGraph,
|
|
143
|
+
protocol: HandshakeProtocol,
|
|
144
|
+
router: SuperSystemRouter,
|
|
145
|
+
config: Optional[ListenerConfig] = None,
|
|
146
|
+
# Optional lower-layer adapters
|
|
147
|
+
security_adapter: Optional[Any] = None, # iatp adapter
|
|
148
|
+
context_adapter: Optional[Any] = None, # caas adapter
|
|
149
|
+
):
|
|
150
|
+
"""
|
|
151
|
+
Initialize the Listener Agent.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
knowledge_graph: The graph to monitor (via scak)
|
|
155
|
+
protocol: The handshake protocol to observe
|
|
156
|
+
router: The super system router
|
|
157
|
+
config: Listener configuration
|
|
158
|
+
security_adapter: Optional adapter to iatp security layer
|
|
159
|
+
context_adapter: Optional adapter to caas context layer
|
|
160
|
+
"""
|
|
161
|
+
self.knowledge_graph = knowledge_graph
|
|
162
|
+
self.protocol = protocol
|
|
163
|
+
self.router = router
|
|
164
|
+
self.config = config or ListenerConfig()
|
|
165
|
+
|
|
166
|
+
# Lower-layer adapters (for consolidated stack)
|
|
167
|
+
self._security_adapter = security_adapter
|
|
168
|
+
self._context_adapter = context_adapter
|
|
169
|
+
|
|
170
|
+
# State observer
|
|
171
|
+
self.observer = StateObserver(
|
|
172
|
+
knowledge_graph=knowledge_graph,
|
|
173
|
+
protocol=protocol,
|
|
174
|
+
router=router,
|
|
175
|
+
sample_window_seconds=self.config.thresholds.window_size_seconds,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Current state
|
|
179
|
+
self._state = ListenerState.IDLE
|
|
180
|
+
self._state_lock = threading.Lock()
|
|
181
|
+
|
|
182
|
+
# Observation thread
|
|
183
|
+
self._observation_thread: Optional[threading.Thread] = None
|
|
184
|
+
self._stop_event = threading.Event()
|
|
185
|
+
|
|
186
|
+
# Intervention tracking
|
|
187
|
+
self._interventions: deque = deque(
|
|
188
|
+
maxlen=self.config.max_observation_history
|
|
189
|
+
)
|
|
190
|
+
self._intervention_count_this_minute = 0
|
|
191
|
+
self._minute_start = datetime.now()
|
|
192
|
+
self._event_counter = 0
|
|
193
|
+
|
|
194
|
+
# Callbacks
|
|
195
|
+
self._intervention_callbacks: List[Callable[[InterventionEvent], None]] = []
|
|
196
|
+
self._state_change_callbacks: List[Callable[[ListenerState, ListenerState], None]] = []
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def state(self) -> ListenerState:
|
|
200
|
+
"""Get current listener state."""
|
|
201
|
+
with self._state_lock:
|
|
202
|
+
return self._state
|
|
203
|
+
|
|
204
|
+
def _set_state(self, new_state: ListenerState) -> None:
|
|
205
|
+
"""Set listener state with callback notification."""
|
|
206
|
+
with self._state_lock:
|
|
207
|
+
old_state = self._state
|
|
208
|
+
self._state = new_state
|
|
209
|
+
|
|
210
|
+
# Notify callbacks outside lock
|
|
211
|
+
for callback in self._state_change_callbacks:
|
|
212
|
+
try:
|
|
213
|
+
callback(old_state, new_state)
|
|
214
|
+
except Exception:
|
|
215
|
+
pass # Don't let callback errors affect listener
|
|
216
|
+
|
|
217
|
+
def start(self) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Start the Listener Agent.
|
|
220
|
+
|
|
221
|
+
Begins passive observation of graph states. The listener will
|
|
222
|
+
continue observing until stop() is called or an intervention
|
|
223
|
+
threshold is exceeded.
|
|
224
|
+
"""
|
|
225
|
+
if self._state != ListenerState.IDLE and self._state != ListenerState.STOPPED:
|
|
226
|
+
raise RuntimeError(f"Cannot start listener in state {self._state}")
|
|
227
|
+
|
|
228
|
+
self._stop_event.clear()
|
|
229
|
+
self._set_state(ListenerState.OBSERVING)
|
|
230
|
+
|
|
231
|
+
# Start observation thread
|
|
232
|
+
self._observation_thread = threading.Thread(
|
|
233
|
+
target=self._observation_loop,
|
|
234
|
+
name="ListenerAgent-Observer",
|
|
235
|
+
daemon=True,
|
|
236
|
+
)
|
|
237
|
+
self._observation_thread.start()
|
|
238
|
+
|
|
239
|
+
def stop(self) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Stop the Listener Agent.
|
|
242
|
+
|
|
243
|
+
Ceases observation and any ongoing intervention.
|
|
244
|
+
"""
|
|
245
|
+
self._stop_event.set()
|
|
246
|
+
self._set_state(ListenerState.STOPPED)
|
|
247
|
+
|
|
248
|
+
if self._observation_thread and self._observation_thread.is_alive():
|
|
249
|
+
self._observation_thread.join(timeout=5.0)
|
|
250
|
+
|
|
251
|
+
def observe_once(self, context: Optional[Dict[str, Any]] = None) -> ObservationResult:
|
|
252
|
+
"""
|
|
253
|
+
Perform a single observation cycle.
|
|
254
|
+
|
|
255
|
+
This is useful for synchronous observation without starting
|
|
256
|
+
the background observation loop.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
context: Optional context to include
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
ObservationResult from this cycle
|
|
263
|
+
"""
|
|
264
|
+
return self.observer.observe(context)
|
|
265
|
+
|
|
266
|
+
def evaluate_thresholds(
|
|
267
|
+
self,
|
|
268
|
+
observation: ObservationResult
|
|
269
|
+
) -> tuple[List[ThresholdRule], InterventionLevel]:
|
|
270
|
+
"""
|
|
271
|
+
Evaluate observation against configured thresholds.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
observation: The observation to evaluate
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Tuple of (triggered_rules, max_intervention_level)
|
|
278
|
+
"""
|
|
279
|
+
# Convert observation to threshold metrics
|
|
280
|
+
threshold_metrics = observation.to_threshold_metrics()
|
|
281
|
+
|
|
282
|
+
# Evaluate against thresholds
|
|
283
|
+
triggered_rules = self.config.thresholds.evaluate_all(
|
|
284
|
+
threshold_metrics,
|
|
285
|
+
context={"observation": observation},
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Get maximum intervention level
|
|
289
|
+
max_level = self.config.thresholds.get_maximum_intervention_level(
|
|
290
|
+
triggered_rules
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return triggered_rules, max_level
|
|
294
|
+
|
|
295
|
+
def _observation_loop(self) -> None:
|
|
296
|
+
"""Background observation loop."""
|
|
297
|
+
while not self._stop_event.is_set():
|
|
298
|
+
try:
|
|
299
|
+
self._run_observation_cycle()
|
|
300
|
+
except Exception as e:
|
|
301
|
+
# Log error but continue observation
|
|
302
|
+
# In production, this would integrate with logging framework
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
# Wait for next observation interval
|
|
306
|
+
self._stop_event.wait(self.config.observation_interval_seconds)
|
|
307
|
+
|
|
308
|
+
def _run_observation_cycle(self) -> None:
|
|
309
|
+
"""Run a single observation cycle."""
|
|
310
|
+
# Perform observation
|
|
311
|
+
observation = self.observer.observe()
|
|
312
|
+
|
|
313
|
+
# Evaluate thresholds
|
|
314
|
+
triggered_rules, intervention_level = self.evaluate_thresholds(observation)
|
|
315
|
+
|
|
316
|
+
if not triggered_rules:
|
|
317
|
+
# No thresholds exceeded - continue passive observation
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
# Thresholds exceeded - evaluate intervention
|
|
321
|
+
self._set_state(ListenerState.EVALUATING)
|
|
322
|
+
|
|
323
|
+
# Check rate limiting
|
|
324
|
+
if not self._can_intervene():
|
|
325
|
+
self._set_state(ListenerState.OBSERVING)
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
# Determine if intervention is needed based on level
|
|
329
|
+
if intervention_level == InterventionLevel.OBSERVE:
|
|
330
|
+
# Log only
|
|
331
|
+
self._set_state(ListenerState.OBSERVING)
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
# Perform intervention
|
|
335
|
+
if self.config.auto_intervention:
|
|
336
|
+
self._perform_intervention(
|
|
337
|
+
triggered_rules,
|
|
338
|
+
intervention_level,
|
|
339
|
+
observation,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Return to observation (or recovery)
|
|
343
|
+
if self._state == ListenerState.INTERVENING:
|
|
344
|
+
self._set_state(ListenerState.RECOVERING)
|
|
345
|
+
# In recovery mode, continue observation with heightened awareness
|
|
346
|
+
self._recovery_check()
|
|
347
|
+
|
|
348
|
+
def _can_intervene(self) -> bool:
|
|
349
|
+
"""Check if intervention is allowed (rate limiting)."""
|
|
350
|
+
now = datetime.now()
|
|
351
|
+
|
|
352
|
+
# Reset counter if minute has passed
|
|
353
|
+
if (now - self._minute_start).total_seconds() >= 60:
|
|
354
|
+
self._intervention_count_this_minute = 0
|
|
355
|
+
self._minute_start = now
|
|
356
|
+
|
|
357
|
+
return self._intervention_count_this_minute < self.config.max_interventions_per_minute
|
|
358
|
+
|
|
359
|
+
def _perform_intervention(
|
|
360
|
+
self,
|
|
361
|
+
triggered_rules: List[ThresholdRule],
|
|
362
|
+
intervention_level: InterventionLevel,
|
|
363
|
+
observation: ObservationResult,
|
|
364
|
+
) -> InterventionEvent:
|
|
365
|
+
"""
|
|
366
|
+
Perform an intervention based on triggered rules.
|
|
367
|
+
|
|
368
|
+
This is where the Listener transitions from passive to active.
|
|
369
|
+
"""
|
|
370
|
+
self._set_state(ListenerState.INTERVENING)
|
|
371
|
+
start_time = datetime.now()
|
|
372
|
+
|
|
373
|
+
# Generate event ID
|
|
374
|
+
self._event_counter += 1
|
|
375
|
+
event_id = f"intervention_{self._event_counter}_{start_time.timestamp()}"
|
|
376
|
+
|
|
377
|
+
# Determine action based on intervention level
|
|
378
|
+
action_taken = self._determine_action(intervention_level, triggered_rules)
|
|
379
|
+
|
|
380
|
+
# Execute intervention action
|
|
381
|
+
outcome = self._execute_intervention_action(
|
|
382
|
+
action_taken,
|
|
383
|
+
intervention_level,
|
|
384
|
+
triggered_rules,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Calculate duration
|
|
388
|
+
duration_ms = (datetime.now() - start_time).total_seconds() * 1000
|
|
389
|
+
|
|
390
|
+
# Create event record
|
|
391
|
+
event = InterventionEvent(
|
|
392
|
+
event_id=event_id,
|
|
393
|
+
timestamp=start_time,
|
|
394
|
+
triggered_rules=triggered_rules,
|
|
395
|
+
intervention_level=intervention_level,
|
|
396
|
+
metrics_snapshot=observation.derived_metrics.copy(),
|
|
397
|
+
context={
|
|
398
|
+
"anomalies": observation.anomalies_detected,
|
|
399
|
+
"graph_snapshot": observation.graph_snapshot,
|
|
400
|
+
},
|
|
401
|
+
action_taken=action_taken,
|
|
402
|
+
outcome=outcome,
|
|
403
|
+
duration_ms=duration_ms,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Store and notify
|
|
407
|
+
self._interventions.append(event)
|
|
408
|
+
self._intervention_count_this_minute += 1
|
|
409
|
+
|
|
410
|
+
for callback in self._intervention_callbacks:
|
|
411
|
+
try:
|
|
412
|
+
callback(event)
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
return event
|
|
417
|
+
|
|
418
|
+
def _determine_action(
|
|
419
|
+
self,
|
|
420
|
+
level: InterventionLevel,
|
|
421
|
+
rules: List[ThresholdRule],
|
|
422
|
+
) -> str:
|
|
423
|
+
"""Determine intervention action based on level and rules."""
|
|
424
|
+
if level == InterventionLevel.WARN:
|
|
425
|
+
return "emit_warning"
|
|
426
|
+
elif level == InterventionLevel.SOFT_BLOCK:
|
|
427
|
+
return "require_confirmation"
|
|
428
|
+
elif level == InterventionLevel.HARD_BLOCK:
|
|
429
|
+
return "block_pending_actions"
|
|
430
|
+
elif level == InterventionLevel.EMERGENCY:
|
|
431
|
+
return "emergency_halt"
|
|
432
|
+
else:
|
|
433
|
+
return "observe_only"
|
|
434
|
+
|
|
435
|
+
def _execute_intervention_action(
|
|
436
|
+
self,
|
|
437
|
+
action: str,
|
|
438
|
+
level: InterventionLevel,
|
|
439
|
+
rules: List[ThresholdRule],
|
|
440
|
+
) -> str:
|
|
441
|
+
"""
|
|
442
|
+
Execute the determined intervention action.
|
|
443
|
+
|
|
444
|
+
This is where we wire together the lower layers:
|
|
445
|
+
- Use iatp for security-related interventions
|
|
446
|
+
- Use caas to update context
|
|
447
|
+
- Use agent-control-plane for action blocking
|
|
448
|
+
"""
|
|
449
|
+
if action == "emit_warning":
|
|
450
|
+
# Log warning - in production, integrate with alerting system
|
|
451
|
+
return f"Warning emitted for {len(rules)} triggered rules"
|
|
452
|
+
|
|
453
|
+
elif action == "require_confirmation":
|
|
454
|
+
# Mark pending sessions as requiring confirmation
|
|
455
|
+
pending_blocked = 0
|
|
456
|
+
for session_id, session in self.protocol.sessions.items():
|
|
457
|
+
if session.state in [HandshakeState.VALIDATED, HandshakeState.ACCEPTED]:
|
|
458
|
+
session.metadata["requires_confirmation"] = True
|
|
459
|
+
session.metadata["confirmation_reason"] = (
|
|
460
|
+
f"Threshold breach: {[r.description for r in rules]}"
|
|
461
|
+
)
|
|
462
|
+
pending_blocked += 1
|
|
463
|
+
return f"Soft block applied to {pending_blocked} pending sessions"
|
|
464
|
+
|
|
465
|
+
elif action == "block_pending_actions":
|
|
466
|
+
# Reject all pending sessions
|
|
467
|
+
blocked = 0
|
|
468
|
+
for session_id, session in list(self.protocol.sessions.items()):
|
|
469
|
+
if session.state in [HandshakeState.INITIATED, HandshakeState.NEGOTIATING,
|
|
470
|
+
HandshakeState.VALIDATED, HandshakeState.ACCEPTED]:
|
|
471
|
+
self.protocol.reject_proposal(
|
|
472
|
+
session_id,
|
|
473
|
+
reason=f"Listener intervention: {level.value}"
|
|
474
|
+
)
|
|
475
|
+
blocked += 1
|
|
476
|
+
return f"Hard block applied, {blocked} sessions rejected"
|
|
477
|
+
|
|
478
|
+
elif action == "emergency_halt":
|
|
479
|
+
# Emergency halt - reject all and set protective state
|
|
480
|
+
halted = 0
|
|
481
|
+
for session_id, session in list(self.protocol.sessions.items()):
|
|
482
|
+
if session.state != HandshakeState.COMPLETED:
|
|
483
|
+
try:
|
|
484
|
+
self.protocol.reject_proposal(
|
|
485
|
+
session_id,
|
|
486
|
+
reason="EMERGENCY: System halt by Listener"
|
|
487
|
+
)
|
|
488
|
+
halted += 1
|
|
489
|
+
except ValueError:
|
|
490
|
+
pass # Session may already be in terminal state
|
|
491
|
+
|
|
492
|
+
# If security adapter available, notify it
|
|
493
|
+
if self._security_adapter:
|
|
494
|
+
try:
|
|
495
|
+
self._security_adapter.emergency_alert(
|
|
496
|
+
reason="Listener emergency halt",
|
|
497
|
+
triggered_rules=[r.description for r in rules],
|
|
498
|
+
)
|
|
499
|
+
except Exception:
|
|
500
|
+
pass
|
|
501
|
+
|
|
502
|
+
return f"Emergency halt: {halted} sessions terminated"
|
|
503
|
+
|
|
504
|
+
return "No action taken"
|
|
505
|
+
|
|
506
|
+
def _recovery_check(self) -> None:
|
|
507
|
+
"""
|
|
508
|
+
Check if system has recovered after intervention.
|
|
509
|
+
|
|
510
|
+
Performs additional observations to verify system stability
|
|
511
|
+
before returning to normal observation.
|
|
512
|
+
"""
|
|
513
|
+
success_count = 0
|
|
514
|
+
|
|
515
|
+
for _ in range(self.config.recovery_observation_count):
|
|
516
|
+
if self._stop_event.is_set():
|
|
517
|
+
break
|
|
518
|
+
|
|
519
|
+
observation = self.observer.observe()
|
|
520
|
+
triggered_rules, level = self.evaluate_thresholds(observation)
|
|
521
|
+
|
|
522
|
+
# Check if we're back to safe levels
|
|
523
|
+
if level in [InterventionLevel.OBSERVE, InterventionLevel.WARN]:
|
|
524
|
+
success_count += 1
|
|
525
|
+
|
|
526
|
+
time.sleep(self.config.observation_interval_seconds)
|
|
527
|
+
|
|
528
|
+
# Calculate recovery success rate
|
|
529
|
+
success_rate = success_count / self.config.recovery_observation_count
|
|
530
|
+
|
|
531
|
+
if success_rate >= self.config.recovery_success_threshold:
|
|
532
|
+
self._set_state(ListenerState.OBSERVING)
|
|
533
|
+
else:
|
|
534
|
+
# Still unstable - may need additional intervention
|
|
535
|
+
self._set_state(ListenerState.EVALUATING)
|
|
536
|
+
|
|
537
|
+
# === Public API for external integration ===
|
|
538
|
+
|
|
539
|
+
def register_intervention_callback(
|
|
540
|
+
self,
|
|
541
|
+
callback: Callable[[InterventionEvent], None]
|
|
542
|
+
) -> None:
|
|
543
|
+
"""Register a callback to be notified of interventions."""
|
|
544
|
+
self._intervention_callbacks.append(callback)
|
|
545
|
+
|
|
546
|
+
def register_state_change_callback(
|
|
547
|
+
self,
|
|
548
|
+
callback: Callable[[ListenerState, ListenerState], None]
|
|
549
|
+
) -> None:
|
|
550
|
+
"""Register a callback to be notified of state changes."""
|
|
551
|
+
self._state_change_callbacks.append(callback)
|
|
552
|
+
|
|
553
|
+
def get_intervention_history(
|
|
554
|
+
self,
|
|
555
|
+
count: Optional[int] = None
|
|
556
|
+
) -> List[InterventionEvent]:
|
|
557
|
+
"""Get recent intervention events."""
|
|
558
|
+
events = list(self._interventions)
|
|
559
|
+
if count is not None:
|
|
560
|
+
events = events[-count:]
|
|
561
|
+
return events
|
|
562
|
+
|
|
563
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
564
|
+
"""Get listener statistics."""
|
|
565
|
+
observation_history = self.observer.get_observation_history()
|
|
566
|
+
|
|
567
|
+
return {
|
|
568
|
+
"state": self._state.value,
|
|
569
|
+
"total_observations": len(observation_history),
|
|
570
|
+
"total_interventions": len(self._interventions),
|
|
571
|
+
"interventions_this_minute": self._intervention_count_this_minute,
|
|
572
|
+
"active_thresholds": len(self.config.thresholds.rules),
|
|
573
|
+
"enabled_thresholds": sum(
|
|
574
|
+
1 for r in self.config.thresholds.rules.values() if r.enabled
|
|
575
|
+
),
|
|
576
|
+
"observer_baselines": len(self.observer._baselines),
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
def update_threshold(
|
|
580
|
+
self,
|
|
581
|
+
threshold_type: ThresholdType,
|
|
582
|
+
new_value: float
|
|
583
|
+
) -> None:
|
|
584
|
+
"""Update a threshold value at runtime."""
|
|
585
|
+
rule = self.config.thresholds.get_rule(threshold_type)
|
|
586
|
+
if rule:
|
|
587
|
+
rule.value = new_value
|
|
588
|
+
|
|
589
|
+
def enable_threshold(self, threshold_type: ThresholdType) -> None:
|
|
590
|
+
"""Enable a threshold rule."""
|
|
591
|
+
self.config.thresholds.enable_rule(threshold_type)
|
|
592
|
+
|
|
593
|
+
def disable_threshold(self, threshold_type: ThresholdType) -> None:
|
|
594
|
+
"""Disable a threshold rule."""
|
|
595
|
+
self.config.thresholds.disable_rule(threshold_type)
|
|
596
|
+
|
|
597
|
+
def calibrate(self, observation_count: int = 10) -> None:
|
|
598
|
+
"""
|
|
599
|
+
Calibrate baselines during known-good operation.
|
|
600
|
+
|
|
601
|
+
Call this during normal operation to establish baseline
|
|
602
|
+
metrics for anomaly detection.
|
|
603
|
+
"""
|
|
604
|
+
# Collect observations
|
|
605
|
+
for _ in range(observation_count):
|
|
606
|
+
self.observer.observe()
|
|
607
|
+
time.sleep(self.config.observation_interval_seconds)
|
|
608
|
+
|
|
609
|
+
# Calibrate observer baselines
|
|
610
|
+
self.observer.calibrate_baselines(observation_count)
|