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,557 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Time-Travel Debugger - Replay Agent History
|
|
6
|
+
|
|
7
|
+
Feature: "Time-Travel Debugging"
|
|
8
|
+
Problem: Need to understand and debug agent behavior after the fact.
|
|
9
|
+
Solution: Since all communication is on amb (message bus) and all history is in emk (event kernel),
|
|
10
|
+
build a "Replay" tool. "Re-run the last 5 minutes of Agent A's life exactly as it happened."
|
|
11
|
+
Result: Complete observability and debugging capability for agent behavior.
|
|
12
|
+
|
|
13
|
+
This module provides infrastructure to replay agent execution history from
|
|
14
|
+
the audit logs and message history.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from datetime import datetime, timedelta
|
|
20
|
+
from enum import Enum
|
|
21
|
+
import logging
|
|
22
|
+
import json
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ReplayMode(Enum):
|
|
26
|
+
"""Mode for replaying agent history"""
|
|
27
|
+
STEP_BY_STEP = "step_by_step"
|
|
28
|
+
CONTINUOUS = "continuous"
|
|
29
|
+
FAST_FORWARD = "fast_forward"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ReplayEventType(Enum):
|
|
33
|
+
"""Types of events that can be replayed"""
|
|
34
|
+
MESSAGE_SENT = "message_sent"
|
|
35
|
+
MESSAGE_RECEIVED = "message_received"
|
|
36
|
+
TOOL_EXECUTION = "tool_execution"
|
|
37
|
+
POLICY_CHECK = "policy_check"
|
|
38
|
+
STATE_CHANGE = "state_change"
|
|
39
|
+
ERROR = "error"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ReplayEvent:
|
|
44
|
+
"""An event in the replay timeline"""
|
|
45
|
+
event_id: str
|
|
46
|
+
event_type: ReplayEventType
|
|
47
|
+
timestamp: datetime
|
|
48
|
+
agent_id: str
|
|
49
|
+
data: Dict[str, Any]
|
|
50
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ReplaySession:
|
|
55
|
+
"""A replay session for an agent"""
|
|
56
|
+
session_id: str
|
|
57
|
+
agent_id: str
|
|
58
|
+
start_time: datetime
|
|
59
|
+
end_time: datetime
|
|
60
|
+
events: List[ReplayEvent]
|
|
61
|
+
mode: ReplayMode
|
|
62
|
+
current_index: int = 0
|
|
63
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class TimeTravelConfig:
|
|
68
|
+
"""Configuration for time-travel debugging"""
|
|
69
|
+
enabled: bool = True
|
|
70
|
+
max_replay_duration_minutes: int = 60
|
|
71
|
+
enable_state_snapshots: bool = True
|
|
72
|
+
snapshot_interval_seconds: int = 60
|
|
73
|
+
max_stored_snapshots: int = 100
|
|
74
|
+
enable_replay_cache: bool = True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TimeTravelDebugger:
|
|
78
|
+
"""
|
|
79
|
+
Time-Travel Debugger for replaying agent execution history.
|
|
80
|
+
|
|
81
|
+
This class provides:
|
|
82
|
+
- Replay of agent actions and decisions over a time period
|
|
83
|
+
- Step-by-step or continuous replay modes
|
|
84
|
+
- Integration with FlightRecorder for complete audit trails
|
|
85
|
+
- State snapshot capture for point-in-time restoration
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
flight_recorder: Optional[Any] = None,
|
|
91
|
+
config: Optional[TimeTravelConfig] = None
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
Initialize the time-travel debugger.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
flight_recorder: FlightRecorder instance for accessing audit logs
|
|
98
|
+
config: Configuration for time-travel behavior
|
|
99
|
+
"""
|
|
100
|
+
self.config = config or TimeTravelConfig()
|
|
101
|
+
self.flight_recorder = flight_recorder
|
|
102
|
+
self.logger = logging.getLogger("TimeTravelDebugger")
|
|
103
|
+
|
|
104
|
+
# Store replay sessions
|
|
105
|
+
self.active_sessions: Dict[str, ReplaySession] = {}
|
|
106
|
+
|
|
107
|
+
# Store state snapshots for time-travel
|
|
108
|
+
self.state_snapshots: Dict[str, List[Dict[str, Any]]] = {}
|
|
109
|
+
|
|
110
|
+
# Cache for replay events
|
|
111
|
+
self.event_cache: Dict[str, List[ReplayEvent]] = {}
|
|
112
|
+
|
|
113
|
+
self.logger.info("TimeTravelDebugger initialized")
|
|
114
|
+
|
|
115
|
+
def capture_state_snapshot(
|
|
116
|
+
self,
|
|
117
|
+
agent_id: str,
|
|
118
|
+
agent_state: Dict[str, Any],
|
|
119
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
120
|
+
):
|
|
121
|
+
"""
|
|
122
|
+
Capture a point-in-time snapshot of agent state.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
agent_id: Agent identifier
|
|
126
|
+
agent_state: Complete agent state to snapshot
|
|
127
|
+
metadata: Optional metadata about the snapshot
|
|
128
|
+
"""
|
|
129
|
+
if not self.config.enable_state_snapshots:
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
snapshot = {
|
|
133
|
+
"agent_id": agent_id,
|
|
134
|
+
"timestamp": datetime.now().isoformat(),
|
|
135
|
+
"state": agent_state,
|
|
136
|
+
"metadata": metadata or {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if agent_id not in self.state_snapshots:
|
|
140
|
+
self.state_snapshots[agent_id] = []
|
|
141
|
+
|
|
142
|
+
self.state_snapshots[agent_id].append(snapshot)
|
|
143
|
+
|
|
144
|
+
# Limit number of snapshots
|
|
145
|
+
if len(self.state_snapshots[agent_id]) > self.config.max_stored_snapshots:
|
|
146
|
+
self.state_snapshots[agent_id] = self.state_snapshots[agent_id][-self.config.max_stored_snapshots:]
|
|
147
|
+
|
|
148
|
+
self.logger.debug(f"Captured state snapshot for agent {agent_id}")
|
|
149
|
+
|
|
150
|
+
def get_state_at_time(
|
|
151
|
+
self,
|
|
152
|
+
agent_id: str,
|
|
153
|
+
target_time: datetime
|
|
154
|
+
) -> Optional[Dict[str, Any]]:
|
|
155
|
+
"""
|
|
156
|
+
Get agent state at a specific point in time.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
agent_id: Agent identifier
|
|
160
|
+
target_time: Target timestamp
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Agent state snapshot closest to the target time, or None
|
|
164
|
+
"""
|
|
165
|
+
if agent_id not in self.state_snapshots:
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
snapshots = self.state_snapshots[agent_id]
|
|
169
|
+
|
|
170
|
+
# Find closest snapshot before or at target time
|
|
171
|
+
closest_snapshot = None
|
|
172
|
+
min_diff = None
|
|
173
|
+
|
|
174
|
+
for snapshot in snapshots:
|
|
175
|
+
snapshot_time = datetime.fromisoformat(snapshot["timestamp"])
|
|
176
|
+
if snapshot_time <= target_time:
|
|
177
|
+
diff = (target_time - snapshot_time).total_seconds()
|
|
178
|
+
if min_diff is None or diff < min_diff:
|
|
179
|
+
min_diff = diff
|
|
180
|
+
closest_snapshot = snapshot
|
|
181
|
+
|
|
182
|
+
return closest_snapshot
|
|
183
|
+
|
|
184
|
+
def collect_events_from_flight_recorder(
|
|
185
|
+
self,
|
|
186
|
+
agent_id: str,
|
|
187
|
+
start_time: datetime,
|
|
188
|
+
end_time: datetime
|
|
189
|
+
) -> List[ReplayEvent]:
|
|
190
|
+
"""
|
|
191
|
+
Collect replay events from FlightRecorder audit logs.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
agent_id: Agent identifier
|
|
195
|
+
start_time: Start of time range
|
|
196
|
+
end_time: End of time range
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List of replay events in chronological order
|
|
200
|
+
"""
|
|
201
|
+
if not self.flight_recorder:
|
|
202
|
+
self.logger.warning("No FlightRecorder available for event collection")
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
events = []
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Use the FlightRecorder's get_events_in_time_range method
|
|
209
|
+
if hasattr(self.flight_recorder, 'get_events_in_time_range'):
|
|
210
|
+
audit_entries = self.flight_recorder.get_events_in_time_range(
|
|
211
|
+
start_time, end_time, agent_id
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
# Fallback to get_log method
|
|
215
|
+
audit_log = self.flight_recorder.get_log() if hasattr(self.flight_recorder, 'get_log') else []
|
|
216
|
+
|
|
217
|
+
# Filter manually
|
|
218
|
+
audit_entries = []
|
|
219
|
+
for entry in audit_log:
|
|
220
|
+
entry_time = datetime.fromisoformat(entry.get("timestamp", ""))
|
|
221
|
+
agent_match = entry.get("agent_id") == agent_id
|
|
222
|
+
|
|
223
|
+
if start_time <= entry_time <= end_time and agent_match:
|
|
224
|
+
audit_entries.append(entry)
|
|
225
|
+
|
|
226
|
+
# Convert to ReplayEvents
|
|
227
|
+
for entry in audit_entries:
|
|
228
|
+
event = self._convert_audit_entry_to_replay_event(entry)
|
|
229
|
+
if event:
|
|
230
|
+
events.append(event)
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.logger.error(f"Error collecting events from FlightRecorder: {e}")
|
|
234
|
+
|
|
235
|
+
# Sort by timestamp
|
|
236
|
+
events.sort(key=lambda e: e.timestamp)
|
|
237
|
+
|
|
238
|
+
return events
|
|
239
|
+
|
|
240
|
+
def _convert_audit_entry_to_replay_event(
|
|
241
|
+
self,
|
|
242
|
+
audit_entry: Dict[str, Any]
|
|
243
|
+
) -> Optional[ReplayEvent]:
|
|
244
|
+
"""Convert an audit log entry to a ReplayEvent"""
|
|
245
|
+
try:
|
|
246
|
+
# FlightRecorder format has: trace_id, timestamp, agent_id, tool_name,
|
|
247
|
+
# tool_args, input_prompt, policy_verdict, violation_reason, result
|
|
248
|
+
|
|
249
|
+
# Determine event type based on policy verdict and tool name
|
|
250
|
+
policy_verdict = audit_entry.get("policy_verdict", "")
|
|
251
|
+
tool_name = audit_entry.get("tool_name", "")
|
|
252
|
+
|
|
253
|
+
# Map to replay event type
|
|
254
|
+
if policy_verdict == "blocked":
|
|
255
|
+
event_type = ReplayEventType.POLICY_CHECK
|
|
256
|
+
elif policy_verdict == "error":
|
|
257
|
+
event_type = ReplayEventType.ERROR
|
|
258
|
+
elif tool_name:
|
|
259
|
+
event_type = ReplayEventType.TOOL_EXECUTION
|
|
260
|
+
else:
|
|
261
|
+
event_type = ReplayEventType.STATE_CHANGE
|
|
262
|
+
|
|
263
|
+
# Parse tool_args if it's JSON string
|
|
264
|
+
tool_args = audit_entry.get("tool_args")
|
|
265
|
+
if tool_args and isinstance(tool_args, str):
|
|
266
|
+
try:
|
|
267
|
+
tool_args = json.loads(tool_args)
|
|
268
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
269
|
+
self.logger.debug("Could not parse tool_args as JSON: %s", e)
|
|
270
|
+
|
|
271
|
+
# Parse result if it's JSON string
|
|
272
|
+
result = audit_entry.get("result")
|
|
273
|
+
if result and isinstance(result, str):
|
|
274
|
+
try:
|
|
275
|
+
result = json.loads(result)
|
|
276
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
277
|
+
self.logger.debug("Could not parse result as JSON: %s", e)
|
|
278
|
+
|
|
279
|
+
return ReplayEvent(
|
|
280
|
+
event_id=audit_entry.get("trace_id", str(id(audit_entry))),
|
|
281
|
+
event_type=event_type,
|
|
282
|
+
timestamp=datetime.fromisoformat(audit_entry["timestamp"]),
|
|
283
|
+
agent_id=audit_entry.get("agent_id", ""),
|
|
284
|
+
data={
|
|
285
|
+
"tool_name": tool_name,
|
|
286
|
+
"tool_args": tool_args,
|
|
287
|
+
"input_prompt": audit_entry.get("input_prompt"),
|
|
288
|
+
"policy_verdict": policy_verdict,
|
|
289
|
+
"violation_reason": audit_entry.get("violation_reason"),
|
|
290
|
+
"result": result,
|
|
291
|
+
"execution_time_ms": audit_entry.get("execution_time_ms"),
|
|
292
|
+
},
|
|
293
|
+
metadata={
|
|
294
|
+
"db_id": audit_entry.get("id"),
|
|
295
|
+
"trace_id": audit_entry.get("trace_id")
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
self.logger.error(f"Error converting audit entry: {e}")
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
def create_replay_session(
|
|
303
|
+
self,
|
|
304
|
+
agent_id: str,
|
|
305
|
+
start_time: datetime,
|
|
306
|
+
end_time: datetime,
|
|
307
|
+
mode: ReplayMode = ReplayMode.CONTINUOUS
|
|
308
|
+
) -> ReplaySession:
|
|
309
|
+
"""
|
|
310
|
+
Create a new replay session for an agent.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
agent_id: Agent identifier
|
|
314
|
+
start_time: Start of replay period
|
|
315
|
+
end_time: End of replay period
|
|
316
|
+
mode: Replay mode (step-by-step, continuous, fast-forward)
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
Created ReplaySession
|
|
320
|
+
"""
|
|
321
|
+
# Validate time range
|
|
322
|
+
duration = (end_time - start_time).total_seconds() / 60
|
|
323
|
+
if duration > self.config.max_replay_duration_minutes:
|
|
324
|
+
raise ValueError(
|
|
325
|
+
f"Replay duration ({duration} min) exceeds maximum "
|
|
326
|
+
f"({self.config.max_replay_duration_minutes} min)"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Collect events
|
|
330
|
+
events = self.collect_events_from_flight_recorder(agent_id, start_time, end_time)
|
|
331
|
+
|
|
332
|
+
# Create session
|
|
333
|
+
session = ReplaySession(
|
|
334
|
+
session_id=f"replay_{agent_id}_{int(datetime.now().timestamp())}",
|
|
335
|
+
agent_id=agent_id,
|
|
336
|
+
start_time=start_time,
|
|
337
|
+
end_time=end_time,
|
|
338
|
+
events=events,
|
|
339
|
+
mode=mode
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
self.active_sessions[session.session_id] = session
|
|
343
|
+
|
|
344
|
+
self.logger.info(
|
|
345
|
+
f"Created replay session {session.session_id} for agent {agent_id} "
|
|
346
|
+
f"with {len(events)} events from {start_time} to {end_time}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
return session
|
|
350
|
+
|
|
351
|
+
def replay_time_window(
|
|
352
|
+
self,
|
|
353
|
+
agent_id: str,
|
|
354
|
+
minutes: int,
|
|
355
|
+
mode: ReplayMode = ReplayMode.CONTINUOUS
|
|
356
|
+
) -> ReplaySession:
|
|
357
|
+
"""
|
|
358
|
+
Replay the last N minutes of an agent's life.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
agent_id: Agent identifier
|
|
362
|
+
minutes: Number of minutes to replay
|
|
363
|
+
mode: Replay mode
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Created ReplaySession
|
|
367
|
+
"""
|
|
368
|
+
end_time = datetime.now()
|
|
369
|
+
start_time = end_time - timedelta(minutes=minutes)
|
|
370
|
+
|
|
371
|
+
return self.create_replay_session(agent_id, start_time, end_time, mode)
|
|
372
|
+
|
|
373
|
+
def replay_agent_history(
|
|
374
|
+
self,
|
|
375
|
+
agent_id: str,
|
|
376
|
+
session_id: Optional[str] = None,
|
|
377
|
+
callback: Optional[callable] = None
|
|
378
|
+
) -> List[ReplayEvent]:
|
|
379
|
+
"""
|
|
380
|
+
Replay agent history, optionally with a callback for each event.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
agent_id: Agent identifier
|
|
384
|
+
session_id: Specific session to replay (uses most recent if None)
|
|
385
|
+
callback: Optional callback function called for each event
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
List of replayed events
|
|
389
|
+
"""
|
|
390
|
+
# Find session
|
|
391
|
+
if session_id:
|
|
392
|
+
if session_id not in self.active_sessions:
|
|
393
|
+
raise ValueError(f"Replay session {session_id} not found")
|
|
394
|
+
session = self.active_sessions[session_id]
|
|
395
|
+
else:
|
|
396
|
+
# Find most recent session for this agent
|
|
397
|
+
agent_sessions = [
|
|
398
|
+
s for s in self.active_sessions.values()
|
|
399
|
+
if s.agent_id == agent_id
|
|
400
|
+
]
|
|
401
|
+
if not agent_sessions:
|
|
402
|
+
raise ValueError(f"No replay sessions found for agent {agent_id}")
|
|
403
|
+
session = max(agent_sessions, key=lambda s: s.created_at)
|
|
404
|
+
|
|
405
|
+
replayed_events = []
|
|
406
|
+
|
|
407
|
+
# Replay based on mode
|
|
408
|
+
if session.mode == ReplayMode.STEP_BY_STEP:
|
|
409
|
+
# Return events one at a time (user must call next_step)
|
|
410
|
+
if session.current_index < len(session.events):
|
|
411
|
+
if callback:
|
|
412
|
+
callback(session.events[session.current_index])
|
|
413
|
+
replayed_events.append(session.events[session.current_index])
|
|
414
|
+
session.current_index += 1
|
|
415
|
+
else:
|
|
416
|
+
# Continuous or fast-forward: replay all events
|
|
417
|
+
for event in session.events:
|
|
418
|
+
replayed_events.append(event)
|
|
419
|
+
if callback:
|
|
420
|
+
callback(event)
|
|
421
|
+
session.current_index += 1
|
|
422
|
+
|
|
423
|
+
return replayed_events
|
|
424
|
+
|
|
425
|
+
def next_step(self, session_id: str) -> Optional[ReplayEvent]:
|
|
426
|
+
"""
|
|
427
|
+
Advance to the next event in a step-by-step replay session.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
session_id: Replay session identifier
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Next event, or None if at end
|
|
434
|
+
"""
|
|
435
|
+
if session_id not in self.active_sessions:
|
|
436
|
+
raise ValueError(f"Replay session {session_id} not found")
|
|
437
|
+
|
|
438
|
+
session = self.active_sessions[session_id]
|
|
439
|
+
|
|
440
|
+
if session.current_index >= len(session.events):
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
event = session.events[session.current_index]
|
|
444
|
+
session.current_index += 1
|
|
445
|
+
|
|
446
|
+
return event
|
|
447
|
+
|
|
448
|
+
def get_session_progress(self, session_id: str) -> Dict[str, Any]:
|
|
449
|
+
"""Get progress information for a replay session"""
|
|
450
|
+
if session_id not in self.active_sessions:
|
|
451
|
+
raise ValueError(f"Replay session {session_id} not found")
|
|
452
|
+
|
|
453
|
+
session = self.active_sessions[session_id]
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
"session_id": session.session_id,
|
|
457
|
+
"agent_id": session.agent_id,
|
|
458
|
+
"total_events": len(session.events),
|
|
459
|
+
"current_index": session.current_index,
|
|
460
|
+
"completed": session.current_index >= len(session.events),
|
|
461
|
+
"progress_percent": (session.current_index / len(session.events) * 100) if session.events else 0,
|
|
462
|
+
"mode": session.mode.value,
|
|
463
|
+
"time_range": {
|
|
464
|
+
"start": session.start_time.isoformat(),
|
|
465
|
+
"end": session.end_time.isoformat()
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
def get_replay_summary(self, session_id: str) -> Dict[str, Any]:
|
|
470
|
+
"""Get a summary of a replay session"""
|
|
471
|
+
if session_id not in self.active_sessions:
|
|
472
|
+
raise ValueError(f"Replay session {session_id} not found")
|
|
473
|
+
|
|
474
|
+
session = self.active_sessions[session_id]
|
|
475
|
+
|
|
476
|
+
# Aggregate statistics
|
|
477
|
+
event_type_counts = {}
|
|
478
|
+
for event in session.events:
|
|
479
|
+
event_type = event.event_type.value
|
|
480
|
+
event_type_counts[event_type] = event_type_counts.get(event_type, 0) + 1
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
"session_id": session.session_id,
|
|
484
|
+
"agent_id": session.agent_id,
|
|
485
|
+
"time_range": {
|
|
486
|
+
"start": session.start_time.isoformat(),
|
|
487
|
+
"end": session.end_time.isoformat(),
|
|
488
|
+
"duration_seconds": (session.end_time - session.start_time).total_seconds()
|
|
489
|
+
},
|
|
490
|
+
"total_events": len(session.events),
|
|
491
|
+
"event_type_breakdown": event_type_counts,
|
|
492
|
+
"mode": session.mode.value,
|
|
493
|
+
"created_at": session.created_at.isoformat()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
def export_replay_session(
|
|
497
|
+
self,
|
|
498
|
+
session_id: str,
|
|
499
|
+
format: str = "json"
|
|
500
|
+
) -> str:
|
|
501
|
+
"""
|
|
502
|
+
Export a replay session for external analysis.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
session_id: Replay session identifier
|
|
506
|
+
format: Export format (currently only 'json')
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Serialized replay session
|
|
510
|
+
"""
|
|
511
|
+
if session_id not in self.active_sessions:
|
|
512
|
+
raise ValueError(f"Replay session {session_id} not found")
|
|
513
|
+
|
|
514
|
+
session = self.active_sessions[session_id]
|
|
515
|
+
|
|
516
|
+
export_data = {
|
|
517
|
+
"session_id": session.session_id,
|
|
518
|
+
"agent_id": session.agent_id,
|
|
519
|
+
"start_time": session.start_time.isoformat(),
|
|
520
|
+
"end_time": session.end_time.isoformat(),
|
|
521
|
+
"mode": session.mode.value,
|
|
522
|
+
"events": [
|
|
523
|
+
{
|
|
524
|
+
"event_id": e.event_id,
|
|
525
|
+
"event_type": e.event_type.value,
|
|
526
|
+
"timestamp": e.timestamp.isoformat(),
|
|
527
|
+
"agent_id": e.agent_id,
|
|
528
|
+
"data": e.data,
|
|
529
|
+
"metadata": e.metadata
|
|
530
|
+
}
|
|
531
|
+
for e in session.events
|
|
532
|
+
],
|
|
533
|
+
"summary": self.get_replay_summary(session_id)
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return json.dumps(export_data, indent=2)
|
|
537
|
+
|
|
538
|
+
def close_session(self, session_id: str):
|
|
539
|
+
"""Close and remove a replay session"""
|
|
540
|
+
if session_id in self.active_sessions:
|
|
541
|
+
del self.active_sessions[session_id]
|
|
542
|
+
self.logger.info(f"Closed replay session {session_id}")
|
|
543
|
+
|
|
544
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
545
|
+
"""Get statistics about time-travel debugging"""
|
|
546
|
+
total_snapshots = sum(len(snapshots) for snapshots in self.state_snapshots.values())
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
"active_replay_sessions": len(self.active_sessions),
|
|
550
|
+
"total_state_snapshots": total_snapshots,
|
|
551
|
+
"agents_with_snapshots": len(self.state_snapshots),
|
|
552
|
+
"config": {
|
|
553
|
+
"enabled": self.config.enabled,
|
|
554
|
+
"max_replay_duration_minutes": self.config.max_replay_duration_minutes,
|
|
555
|
+
"enable_state_snapshots": self.config.enable_state_snapshots
|
|
556
|
+
}
|
|
557
|
+
}
|