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
emk/schema.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
# Public Preview — basic context/memory management
|
|
4
|
+
"""
|
|
5
|
+
Episode Schema — core data structures for episodic memory.
|
|
6
|
+
|
|
7
|
+
Defines mutable Episode and SemanticRule models.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Any, Dict, Optional, List
|
|
12
|
+
from pydantic import BaseModel, Field, model_validator
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Episode(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
An immutable episode representing a single agent experience.
|
|
20
|
+
|
|
21
|
+
Episodes follow the pattern: Goal -> Action -> Result -> Reflection
|
|
22
|
+
and are stored in an append-only manner with no modifications allowed.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
goal: The agent's intended objective
|
|
26
|
+
action: The action taken to achieve the goal
|
|
27
|
+
result: The outcome of the action
|
|
28
|
+
reflection: Agent's analysis or learning from the experience
|
|
29
|
+
timestamp: When the episode was created (auto-generated)
|
|
30
|
+
metadata: Additional context or tags for indexing
|
|
31
|
+
episode_id: Unique hash-based identifier (auto-generated)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
goal: str = Field(..., description="The agent's intended objective")
|
|
35
|
+
action: str = Field(..., description="The action taken to achieve the goal")
|
|
36
|
+
result: str = Field(..., description="The outcome of the action")
|
|
37
|
+
reflection: str = Field(..., description="Agent's analysis or learning from the experience")
|
|
38
|
+
timestamp: datetime = Field(
|
|
39
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
40
|
+
description="When the episode was created"
|
|
41
|
+
)
|
|
42
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional context or tags")
|
|
43
|
+
episode_id: str = Field(default="", description="Unique hash-based identifier")
|
|
44
|
+
|
|
45
|
+
# Public Preview — basic context/memory management
|
|
46
|
+
model_config = {
|
|
47
|
+
"json_schema_extra": {
|
|
48
|
+
"example": {
|
|
49
|
+
"goal": "Retrieve user preferences",
|
|
50
|
+
"action": "Query database for user_id=123",
|
|
51
|
+
"result": "Successfully retrieved preferences",
|
|
52
|
+
"reflection": "Database query was efficient and returned expected data",
|
|
53
|
+
"metadata": {"user_id": "123", "query_time_ms": 45}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@model_validator(mode='before')
|
|
59
|
+
@classmethod
|
|
60
|
+
def generate_episode_id(cls, data: Any) -> Any:
|
|
61
|
+
"""Generate episode_id if not provided."""
|
|
62
|
+
if isinstance(data, dict):
|
|
63
|
+
if not data.get('episode_id'):
|
|
64
|
+
content = {
|
|
65
|
+
"goal": data.get('goal', ''),
|
|
66
|
+
"action": data.get('action', ''),
|
|
67
|
+
"result": data.get('result', ''),
|
|
68
|
+
"reflection": data.get('reflection', ''),
|
|
69
|
+
"timestamp": data.get('timestamp', datetime.now(timezone.utc)).isoformat()
|
|
70
|
+
if isinstance(data.get('timestamp'), datetime)
|
|
71
|
+
else data.get('timestamp', datetime.now(timezone.utc).isoformat()),
|
|
72
|
+
}
|
|
73
|
+
content_str = json.dumps(content, sort_keys=True)
|
|
74
|
+
data['episode_id'] = hashlib.sha256(content_str.encode()).hexdigest()
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
78
|
+
"""Convert episode to dictionary format."""
|
|
79
|
+
return self.model_dump()
|
|
80
|
+
|
|
81
|
+
def to_json(self) -> str:
|
|
82
|
+
"""Convert episode to JSON string."""
|
|
83
|
+
return self.model_dump_json()
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def from_dict(cls, data: Dict[str, Any]) -> "Episode":
|
|
87
|
+
"""Create episode from dictionary."""
|
|
88
|
+
return cls(**data)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_json(cls, json_str: str) -> "Episode":
|
|
92
|
+
"""Create episode from JSON string."""
|
|
93
|
+
return cls.model_validate_json(json_str)
|
|
94
|
+
|
|
95
|
+
def is_failure(self) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Check if this episode represents a failure/anti-pattern.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if the episode is marked as a failure
|
|
101
|
+
"""
|
|
102
|
+
return self.metadata.get("is_failure", False)
|
|
103
|
+
|
|
104
|
+
def mark_as_failure(self, reason: Optional[str] = None) -> "Episode":
|
|
105
|
+
"""
|
|
106
|
+
Create a new episode marked as a failure (immutable pattern).
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
reason: Optional reason for the failure
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
A new Episode instance with failure metadata
|
|
113
|
+
"""
|
|
114
|
+
new_metadata = {**self.metadata, "is_failure": True}
|
|
115
|
+
if reason:
|
|
116
|
+
new_metadata["failure_reason"] = reason
|
|
117
|
+
|
|
118
|
+
return Episode(
|
|
119
|
+
goal=self.goal,
|
|
120
|
+
action=self.action,
|
|
121
|
+
result=self.result,
|
|
122
|
+
reflection=self.reflection,
|
|
123
|
+
timestamp=self.timestamp,
|
|
124
|
+
metadata=new_metadata
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class SemanticRule(BaseModel):
|
|
129
|
+
"""
|
|
130
|
+
A compressed semantic rule derived from multiple episodes.
|
|
131
|
+
|
|
132
|
+
Semantic rules represent distilled knowledge from the "sleep cycle"
|
|
133
|
+
where old episodes are summarized and compressed to reduce memory overhead.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
rule: The compressed semantic knowledge
|
|
137
|
+
source_episode_ids: IDs of episodes that contributed to this rule
|
|
138
|
+
created_at: When the rule was created
|
|
139
|
+
context: Optional context about when/how this rule applies
|
|
140
|
+
confidence: Confidence score for the rule (0.0 to 1.0)
|
|
141
|
+
metadata: Additional context or tags
|
|
142
|
+
rule_id: Unique hash-based identifier (auto-generated)
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
rule: str = Field(..., description="The compressed semantic knowledge")
|
|
146
|
+
source_episode_ids: List[str] = Field(
|
|
147
|
+
default_factory=list,
|
|
148
|
+
description="IDs of episodes that contributed to this rule"
|
|
149
|
+
)
|
|
150
|
+
created_at: datetime = Field(
|
|
151
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
152
|
+
description="When the rule was created"
|
|
153
|
+
)
|
|
154
|
+
context: Optional[str] = Field(None, description="Context about when/how this rule applies")
|
|
155
|
+
confidence: float = Field(default=1.0, ge=0.0, le=1.0, description="Confidence score")
|
|
156
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional context or tags")
|
|
157
|
+
rule_id: str = Field(default="", description="Unique hash-based identifier")
|
|
158
|
+
|
|
159
|
+
# Public Preview — basic context/memory management
|
|
160
|
+
model_config = {
|
|
161
|
+
"json_schema_extra": {
|
|
162
|
+
"example": {
|
|
163
|
+
"rule": "When querying user preferences, use indexed user_id for optimal performance",
|
|
164
|
+
"source_episode_ids": ["abc123", "def456", "ghi789"],
|
|
165
|
+
"context": "Database query optimization",
|
|
166
|
+
"confidence": 0.95,
|
|
167
|
+
"metadata": {"pattern_type": "optimization", "frequency": 15}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@model_validator(mode='before')
|
|
173
|
+
@classmethod
|
|
174
|
+
def generate_rule_id(cls, data: Any) -> Any:
|
|
175
|
+
"""Generate rule_id if not provided."""
|
|
176
|
+
if isinstance(data, dict):
|
|
177
|
+
if not data.get('rule_id'):
|
|
178
|
+
content = {
|
|
179
|
+
"rule": data.get('rule', ''),
|
|
180
|
+
"created_at": data.get('created_at', datetime.now(timezone.utc)).isoformat()
|
|
181
|
+
if isinstance(data.get('created_at'), datetime)
|
|
182
|
+
else data.get('created_at', datetime.now(timezone.utc).isoformat()),
|
|
183
|
+
}
|
|
184
|
+
content_str = json.dumps(content, sort_keys=True)
|
|
185
|
+
data['rule_id'] = hashlib.sha256(content_str.encode()).hexdigest()
|
|
186
|
+
return data
|
|
187
|
+
|
|
188
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
189
|
+
"""Convert rule to dictionary format."""
|
|
190
|
+
return self.model_dump()
|
|
191
|
+
|
|
192
|
+
def to_json(self) -> str:
|
|
193
|
+
"""Convert rule to JSON string."""
|
|
194
|
+
return self.model_dump_json()
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def from_dict(cls, data: Dict[str, Any]) -> "SemanticRule":
|
|
198
|
+
"""Create rule from dictionary."""
|
|
199
|
+
return cls(**data)
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def from_json(cls, json_str: str) -> "SemanticRule":
|
|
203
|
+
"""Create rule from JSON string."""
|
|
204
|
+
return cls.model_validate_json(json_str)
|
emk/sleep_cycle.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Sleep Cycle - Memory decay and compression utilities.
|
|
5
|
+
|
|
6
|
+
This module implements the "sleep cycle" for agent memory management,
|
|
7
|
+
where old episodes are summarized into semantic rules and raw logs are archived.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from datetime import datetime, timezone, timedelta
|
|
11
|
+
from typing import List, Optional, Dict, Any, Callable
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from emk.schema import Episode, SemanticRule
|
|
15
|
+
from emk.store import VectorStoreAdapter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MemoryCompressor:
|
|
19
|
+
"""
|
|
20
|
+
Handles memory decay and compression through sleep cycles.
|
|
21
|
+
|
|
22
|
+
The compressor identifies old episodes, summarizes them into semantic rules,
|
|
23
|
+
and optionally archives/deletes the raw episodes to reduce memory overhead.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
store: VectorStoreAdapter,
|
|
29
|
+
age_threshold_days: int = 30,
|
|
30
|
+
compression_batch_size: int = 50,
|
|
31
|
+
rules_filepath: Optional[str] = None
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the memory compressor.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
store: The vector store containing episodes
|
|
38
|
+
age_threshold_days: Episodes older than this are candidates for compression
|
|
39
|
+
compression_batch_size: Number of episodes to compress at once
|
|
40
|
+
rules_filepath: Path to store compressed semantic rules (JSONL format)
|
|
41
|
+
"""
|
|
42
|
+
self.store = store
|
|
43
|
+
self.age_threshold_days = age_threshold_days
|
|
44
|
+
self.compression_batch_size = compression_batch_size
|
|
45
|
+
self.rules_filepath = Path(rules_filepath or "semantic_rules.jsonl")
|
|
46
|
+
|
|
47
|
+
# Ensure rules file parent directory exists
|
|
48
|
+
self.rules_filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
if not self.rules_filepath.exists():
|
|
50
|
+
self.rules_filepath.touch()
|
|
51
|
+
|
|
52
|
+
def identify_old_episodes(self, episodes: List[Episode]) -> List[Episode]:
|
|
53
|
+
"""
|
|
54
|
+
Identify episodes that are candidates for compression based on age.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
episodes: List of episodes to filter
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of old episodes that should be compressed
|
|
61
|
+
"""
|
|
62
|
+
threshold = datetime.now(timezone.utc) - timedelta(days=self.age_threshold_days)
|
|
63
|
+
old_episodes = [
|
|
64
|
+
ep for ep in episodes
|
|
65
|
+
if ep.timestamp < threshold
|
|
66
|
+
]
|
|
67
|
+
return old_episodes
|
|
68
|
+
|
|
69
|
+
def summarize_episodes(
|
|
70
|
+
self,
|
|
71
|
+
episodes: List[Episode],
|
|
72
|
+
summarizer: Optional[Callable[[List[Episode]], str]] = None
|
|
73
|
+
) -> SemanticRule:
|
|
74
|
+
"""
|
|
75
|
+
Summarize a batch of episodes into a semantic rule.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
episodes: List of episodes to summarize
|
|
79
|
+
summarizer: Optional custom summarization function
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A SemanticRule representing the compressed knowledge
|
|
83
|
+
"""
|
|
84
|
+
if not episodes:
|
|
85
|
+
raise ValueError("Cannot summarize empty episode list")
|
|
86
|
+
|
|
87
|
+
# Default summarization: extract common patterns
|
|
88
|
+
if summarizer is None:
|
|
89
|
+
rule_text = self._default_summarize(episodes)
|
|
90
|
+
else:
|
|
91
|
+
rule_text = summarizer(episodes)
|
|
92
|
+
|
|
93
|
+
# Create semantic rule
|
|
94
|
+
source_ids = [ep.episode_id for ep in episodes]
|
|
95
|
+
|
|
96
|
+
# Calculate confidence based on episode count
|
|
97
|
+
confidence = min(1.0, len(episodes) / 10.0) # More episodes = higher confidence
|
|
98
|
+
|
|
99
|
+
# Extract common metadata
|
|
100
|
+
common_metadata = self._extract_common_metadata(episodes)
|
|
101
|
+
|
|
102
|
+
semantic_rule = SemanticRule(
|
|
103
|
+
rule=rule_text,
|
|
104
|
+
source_episode_ids=source_ids,
|
|
105
|
+
context=self._extract_context(episodes),
|
|
106
|
+
confidence=confidence,
|
|
107
|
+
metadata=common_metadata
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return semantic_rule
|
|
111
|
+
|
|
112
|
+
def _default_summarize(self, episodes: List[Episode]) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Default summarization strategy: extract common patterns.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
episodes: List of episodes
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A summary string
|
|
121
|
+
"""
|
|
122
|
+
# Group episodes by similar goals
|
|
123
|
+
goal_patterns = {}
|
|
124
|
+
action_patterns = {}
|
|
125
|
+
|
|
126
|
+
for ep in episodes:
|
|
127
|
+
# Simple word-based grouping
|
|
128
|
+
goal_words = set(ep.goal.lower().split())
|
|
129
|
+
action_words = set(ep.action.lower().split())
|
|
130
|
+
|
|
131
|
+
# Track patterns
|
|
132
|
+
for word in goal_words:
|
|
133
|
+
goal_patterns[word] = goal_patterns.get(word, 0) + 1
|
|
134
|
+
for word in action_words:
|
|
135
|
+
action_patterns[word] = action_patterns.get(word, 0) + 1
|
|
136
|
+
|
|
137
|
+
# Find most common patterns
|
|
138
|
+
top_goals = sorted(goal_patterns.items(), key=lambda x: x[1], reverse=True)[:3]
|
|
139
|
+
top_actions = sorted(action_patterns.items(), key=lambda x: x[1], reverse=True)[:3]
|
|
140
|
+
|
|
141
|
+
# Build summary
|
|
142
|
+
summary_parts = []
|
|
143
|
+
|
|
144
|
+
if top_goals:
|
|
145
|
+
goal_words = [word for word, _ in top_goals]
|
|
146
|
+
summary_parts.append(f"Common goals involve: {', '.join(goal_words)}")
|
|
147
|
+
|
|
148
|
+
if top_actions:
|
|
149
|
+
action_words = [word for word, _ in top_actions]
|
|
150
|
+
summary_parts.append(f"Typical actions include: {', '.join(action_words)}")
|
|
151
|
+
|
|
152
|
+
# Check for failures
|
|
153
|
+
failures = [ep for ep in episodes if ep.is_failure()]
|
|
154
|
+
if failures:
|
|
155
|
+
summary_parts.append(
|
|
156
|
+
f"Warning: {len(failures)}/{len(episodes)} attempts failed"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return ". ".join(summary_parts) if summary_parts else "General agent activity"
|
|
160
|
+
|
|
161
|
+
def _extract_context(self, episodes: List[Episode]) -> str:
|
|
162
|
+
"""Extract context from episodes."""
|
|
163
|
+
if not episodes:
|
|
164
|
+
return "General"
|
|
165
|
+
|
|
166
|
+
# Use metadata tags if available
|
|
167
|
+
all_tags = set()
|
|
168
|
+
for ep in episodes:
|
|
169
|
+
if "tags" in ep.metadata:
|
|
170
|
+
tags = ep.metadata["tags"]
|
|
171
|
+
if isinstance(tags, list):
|
|
172
|
+
all_tags.update(tags)
|
|
173
|
+
|
|
174
|
+
if all_tags:
|
|
175
|
+
return f"Context: {', '.join(list(all_tags)[:5])}"
|
|
176
|
+
|
|
177
|
+
return "General agent activity"
|
|
178
|
+
|
|
179
|
+
def _extract_common_metadata(self, episodes: List[Episode]) -> Dict[str, Any]:
|
|
180
|
+
"""Extract common metadata patterns from episodes."""
|
|
181
|
+
metadata = {
|
|
182
|
+
"episode_count": len(episodes),
|
|
183
|
+
"time_span_days": self._calculate_time_span(episodes),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Count failures
|
|
187
|
+
failures = [ep for ep in episodes if ep.is_failure()]
|
|
188
|
+
if failures:
|
|
189
|
+
metadata["failure_count"] = len(failures)
|
|
190
|
+
metadata["success_rate"] = (len(episodes) - len(failures)) / len(episodes)
|
|
191
|
+
|
|
192
|
+
return metadata
|
|
193
|
+
|
|
194
|
+
def _calculate_time_span(self, episodes: List[Episode]) -> int:
|
|
195
|
+
"""Calculate the time span covered by episodes in days."""
|
|
196
|
+
if not episodes:
|
|
197
|
+
return 0
|
|
198
|
+
|
|
199
|
+
timestamps = [ep.timestamp for ep in episodes]
|
|
200
|
+
min_time = min(timestamps)
|
|
201
|
+
max_time = max(timestamps)
|
|
202
|
+
|
|
203
|
+
return (max_time - min_time).days
|
|
204
|
+
|
|
205
|
+
def store_rule(self, rule: SemanticRule) -> str:
|
|
206
|
+
"""
|
|
207
|
+
Store a semantic rule to the rules file.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
rule: The semantic rule to store
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
The rule_id of the stored rule
|
|
214
|
+
"""
|
|
215
|
+
with open(self.rules_filepath, 'a') as f:
|
|
216
|
+
f.write(rule.to_json() + '\n')
|
|
217
|
+
|
|
218
|
+
return rule.rule_id
|
|
219
|
+
|
|
220
|
+
def retrieve_rules(
|
|
221
|
+
self,
|
|
222
|
+
filters: Optional[Dict[str, Any]] = None,
|
|
223
|
+
limit: int = 100
|
|
224
|
+
) -> List[SemanticRule]:
|
|
225
|
+
"""
|
|
226
|
+
Retrieve semantic rules from storage.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
filters: Optional metadata filters
|
|
230
|
+
limit: Maximum number of rules to return
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of matching semantic rules (most recent first)
|
|
234
|
+
"""
|
|
235
|
+
rules = []
|
|
236
|
+
|
|
237
|
+
if not self.rules_filepath.exists() or self.rules_filepath.stat().st_size == 0:
|
|
238
|
+
return rules
|
|
239
|
+
|
|
240
|
+
with open(self.rules_filepath, 'r') as f:
|
|
241
|
+
for line in f:
|
|
242
|
+
line = line.strip()
|
|
243
|
+
if not line:
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
rule = SemanticRule.from_json(line)
|
|
248
|
+
|
|
249
|
+
# Apply filters if provided
|
|
250
|
+
if filters:
|
|
251
|
+
match = all(
|
|
252
|
+
rule.metadata.get(key) == value
|
|
253
|
+
for key, value in filters.items()
|
|
254
|
+
)
|
|
255
|
+
if not match:
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
rules.append(rule)
|
|
259
|
+
except (ValueError, KeyError) as e:
|
|
260
|
+
# Skip invalid lines but log the issue
|
|
261
|
+
import logging
|
|
262
|
+
logging.debug(f"Skipping invalid rule line: {e}")
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
# Return most recent rules first
|
|
266
|
+
rules.reverse()
|
|
267
|
+
return rules[:limit]
|
|
268
|
+
|
|
269
|
+
def compress_old_episodes(
|
|
270
|
+
self,
|
|
271
|
+
summarizer: Optional[Callable[[List[Episode]], str]] = None,
|
|
272
|
+
dry_run: bool = False,
|
|
273
|
+
max_episodes: int = 10000
|
|
274
|
+
) -> Dict[str, Any]:
|
|
275
|
+
"""
|
|
276
|
+
Execute a compression cycle: identify old episodes, summarize, and optionally archive.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
summarizer: Optional custom summarization function
|
|
280
|
+
dry_run: If True, only report what would be compressed without making changes
|
|
281
|
+
max_episodes: Maximum number of episodes to process (default: 10000)
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dictionary with compression statistics
|
|
285
|
+
"""
|
|
286
|
+
# Retrieve episodes up to max limit
|
|
287
|
+
all_episodes = self.store.retrieve(limit=max_episodes)
|
|
288
|
+
|
|
289
|
+
if len(all_episodes) == max_episodes:
|
|
290
|
+
# Log warning that we may have hit the limit
|
|
291
|
+
import logging
|
|
292
|
+
logging.warning(
|
|
293
|
+
f"Retrieved {max_episodes} episodes (limit reached). "
|
|
294
|
+
"Some episodes may not be processed. Consider increasing max_episodes."
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Identify old episodes
|
|
298
|
+
old_episodes = self.identify_old_episodes(all_episodes)
|
|
299
|
+
|
|
300
|
+
if not old_episodes:
|
|
301
|
+
return {
|
|
302
|
+
"compressed_count": 0,
|
|
303
|
+
"rules_created": 0,
|
|
304
|
+
"message": "No old episodes found for compression"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Batch compress
|
|
308
|
+
rules_created = 0
|
|
309
|
+
compressed_count = 0
|
|
310
|
+
errors = []
|
|
311
|
+
|
|
312
|
+
for i in range(0, len(old_episodes), self.compression_batch_size):
|
|
313
|
+
batch = old_episodes[i:i + self.compression_batch_size]
|
|
314
|
+
|
|
315
|
+
# Summarize batch
|
|
316
|
+
try:
|
|
317
|
+
rule = self.summarize_episodes(batch, summarizer)
|
|
318
|
+
|
|
319
|
+
if not dry_run:
|
|
320
|
+
self.store_rule(rule)
|
|
321
|
+
|
|
322
|
+
rules_created += 1
|
|
323
|
+
compressed_count += len(batch)
|
|
324
|
+
except Exception as e:
|
|
325
|
+
# Collect errors but continue with next batch
|
|
326
|
+
import logging
|
|
327
|
+
logging.error(f"Error compressing batch starting at index {i}: {e}")
|
|
328
|
+
errors.append(str(e))
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
result = {
|
|
332
|
+
"compressed_count": compressed_count,
|
|
333
|
+
"rules_created": rules_created,
|
|
334
|
+
"total_episodes": len(all_episodes),
|
|
335
|
+
"old_episodes": len(old_episodes),
|
|
336
|
+
"dry_run": dry_run
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if errors:
|
|
340
|
+
result["errors"] = errors
|
|
341
|
+
result["message"] = f"Completed with {len(errors)} error(s)"
|
|
342
|
+
elif dry_run:
|
|
343
|
+
result["message"] = f"Dry run: Would compress {compressed_count} episodes into {rules_created} rules"
|
|
344
|
+
else:
|
|
345
|
+
result["message"] = f"Compressed {compressed_count} episodes into {rules_created} rules"
|
|
346
|
+
|
|
347
|
+
return result
|