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.
Files changed (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. 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