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
agent_os/mute.py ADDED
@@ -0,0 +1,428 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Mute Agent Primitives — Face/Hands Architecture as Kernel-Level Primitives.
5
+
6
+ Separates reasoning from execution with a kernel-enforced trust boundary:
7
+ - Face agent: reasons, plans — never executes actions
8
+ - Mute (Hands) agent: executes — never calls LLMs or produces text
9
+
10
+ This is the agent equivalent of Unix privilege separation (OpenSSH privsep).
11
+
12
+ Example:
13
+ >>> from agent_os.mute import face_agent, mute_agent, ExecutionPlan, pipe
14
+ >>>
15
+ >>> @face_agent(capabilities=["db.read", "file.write"])
16
+ ... async def planner(task: str) -> ExecutionPlan:
17
+ ... # Can call LLM, reason, plan — but cannot execute
18
+ ... return ExecutionPlan(steps=[
19
+ ... ActionStep(action="db.read", params={"query": "SELECT 1"})
20
+ ... ])
21
+ >>>
22
+ >>> @mute_agent(capabilities=["db.read", "file.write"])
23
+ ... async def executor(step: ActionStep) -> dict:
24
+ ... # Can execute actions — but cannot call LLM or produce text
25
+ ... return {"rows": [1]}
26
+ >>>
27
+ >>> result = await pipe(planner, executor, "get me the count")
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import functools
33
+ import time
34
+ from collections.abc import Awaitable
35
+ from dataclasses import dataclass, field
36
+ from datetime import datetime, timezone
37
+ from enum import Enum
38
+ from typing import (
39
+ Any,
40
+ Callable,
41
+ )
42
+ from uuid import uuid4
43
+
44
+ # =============================================================================
45
+ # Core Data Types
46
+ # =============================================================================
47
+
48
+
49
+ class ActionStatus(Enum):
50
+ """Status of an individual action step."""
51
+ PENDING = "pending"
52
+ APPROVED = "approved"
53
+ DENIED = "denied"
54
+ EXECUTED = "executed"
55
+ FAILED = "failed"
56
+
57
+
58
+ @dataclass
59
+ class ActionStep:
60
+ """A single atomic action in an execution plan.
61
+
62
+ Each step maps to exactly one capability. The kernel validates
63
+ that the step's action is within the agent's granted capabilities
64
+ before allowing execution.
65
+
66
+ Attributes:
67
+ action: Capability-scoped action name (e.g. "db.read", "file.write")
68
+ params: Parameters for the action
69
+ description: Human-readable description of what this step does
70
+ depends_on: Indices of steps that must complete before this one
71
+ """
72
+ action: str
73
+ params: dict[str, Any] = field(default_factory=dict)
74
+ description: str = ""
75
+ depends_on: list[int] = field(default_factory=list)
76
+
77
+
78
+ @dataclass
79
+ class ExecutionPlan:
80
+ """Structured output from a Face agent — the contract between Face and Hands.
81
+
82
+ The plan is fully enumerable: every step has a named action from a
83
+ known capability set. The kernel validates the entire plan before
84
+ any step executes.
85
+
86
+ Attributes:
87
+ steps: Ordered list of action steps
88
+ metadata: Optional metadata from the reasoning phase
89
+ plan_id: Unique identifier for this plan
90
+ """
91
+ steps: list[ActionStep] = field(default_factory=list)
92
+ metadata: dict[str, Any] = field(default_factory=dict)
93
+ plan_id: str = field(default_factory=lambda: str(uuid4())[:12])
94
+
95
+ def __post_init__(self):
96
+ if not isinstance(self.steps, list):
97
+ raise TypeError("steps must be a list of ActionStep")
98
+
99
+ @property
100
+ def actions_used(self) -> set[str]:
101
+ """Set of distinct action names used in this plan."""
102
+ return {step.action for step in self.steps}
103
+
104
+
105
+ @dataclass
106
+ class StepResult:
107
+ """Result of executing a single ActionStep."""
108
+ step_index: int
109
+ action: str
110
+ status: ActionStatus
111
+ data: Any = None
112
+ error: str | None = None
113
+ duration_ms: float = 0.0
114
+
115
+
116
+ @dataclass
117
+ class PipelineResult:
118
+ """Full result of a Face→Hands pipeline execution."""
119
+ plan: ExecutionPlan
120
+ step_results: list[StepResult] = field(default_factory=list)
121
+ success: bool = False
122
+ total_duration_ms: float = 0.0
123
+ denied_steps: list[int] = field(default_factory=list)
124
+ audit_log: list[dict[str, Any]] = field(default_factory=list)
125
+
126
+ @property
127
+ def data(self) -> list[Any]:
128
+ """Convenience: collect data from all successful steps."""
129
+ return [r.data for r in self.step_results if r.status == ActionStatus.EXECUTED]
130
+
131
+
132
+ # =============================================================================
133
+ # Capability Enforcement
134
+ # =============================================================================
135
+
136
+
137
+ class CapabilityViolation(Exception):
138
+ """Raised when an agent tries to use an action outside its capabilities."""
139
+
140
+ def __init__(self, agent_role: str, action: str, allowed: set[str]):
141
+ self.agent_role = agent_role
142
+ self.action = action
143
+ self.allowed = allowed
144
+ super().__init__(
145
+ f"{agent_role} agent attempted '{action}' but only has "
146
+ f"capabilities: {sorted(allowed)}"
147
+ )
148
+
149
+
150
+ def _validate_plan_capabilities(
151
+ plan: ExecutionPlan, capabilities: set[str]
152
+ ) -> list[int]:
153
+ """Validate every step in a plan against allowed capabilities.
154
+
155
+ Returns list of step indices that are denied.
156
+ """
157
+ denied: list[int] = []
158
+ for i, step in enumerate(plan.steps):
159
+ if step.action not in capabilities:
160
+ denied.append(i)
161
+ return denied
162
+
163
+
164
+ # =============================================================================
165
+ # Decorators — @face_agent and @mute_agent
166
+ # =============================================================================
167
+
168
+
169
+ def face_agent(
170
+ capabilities: list[str] | None = None,
171
+ ):
172
+ """Decorator that marks a function as a Face (reasoning) agent.
173
+
174
+ The decorated function:
175
+ - CAN call LLMs, reason, and produce plans
176
+ - MUST return an ExecutionPlan
177
+ - CANNOT execute side-effects (enforced by convention + audit)
178
+
179
+ Args:
180
+ capabilities: List of capabilities the plan is allowed to use.
181
+ If provided, the returned plan is validated against this set.
182
+ """
183
+ cap_set = set(capabilities) if capabilities else None
184
+
185
+ def decorator(fn: Callable[..., Awaitable[ExecutionPlan]]):
186
+ @functools.wraps(fn)
187
+ async def wrapper(*args, **kwargs) -> ExecutionPlan:
188
+ plan = await fn(*args, **kwargs)
189
+ if not isinstance(plan, ExecutionPlan):
190
+ raise TypeError(
191
+ f"@face_agent function must return ExecutionPlan, "
192
+ f"got {type(plan).__name__}"
193
+ )
194
+ # Validate plan capabilities if specified
195
+ if cap_set:
196
+ denied = _validate_plan_capabilities(plan, cap_set)
197
+ if denied:
198
+ bad_actions = {plan.steps[i].action for i in denied}
199
+ raise CapabilityViolation("face", str(bad_actions), cap_set)
200
+ return plan
201
+
202
+ wrapper._agent_role = "face"
203
+ wrapper._capabilities = cap_set
204
+ return wrapper
205
+
206
+ return decorator
207
+
208
+
209
+ def mute_agent(
210
+ capabilities: list[str] | None = None,
211
+ ):
212
+ """Decorator that marks a function as a Mute (Hands/execution) agent.
213
+
214
+ The decorated function:
215
+ - CAN execute actions (DB queries, file I/O, API calls)
216
+ - CANNOT call LLMs or produce unstructured text
217
+ - Receives a single ActionStep and returns structured data
218
+
219
+ Args:
220
+ capabilities: List of capabilities this executor handles.
221
+ """
222
+ cap_set = set(capabilities) if capabilities else None
223
+
224
+ def decorator(fn: Callable[..., Awaitable[Any]]):
225
+ @functools.wraps(fn)
226
+ async def wrapper(step: ActionStep, **kwargs) -> Any:
227
+ if not isinstance(step, ActionStep):
228
+ raise TypeError(
229
+ f"@mute_agent function receives ActionStep, "
230
+ f"got {type(step).__name__}"
231
+ )
232
+ # Enforce capability boundary
233
+ if cap_set and step.action not in cap_set:
234
+ raise CapabilityViolation("mute", step.action, cap_set)
235
+ return await fn(step, **kwargs)
236
+
237
+ wrapper._agent_role = "mute"
238
+ wrapper._capabilities = cap_set
239
+ return wrapper
240
+
241
+ return decorator
242
+
243
+
244
+ # =============================================================================
245
+ # Pipeline — kernel.pipe(face, hands, input)
246
+ # =============================================================================
247
+
248
+
249
+ async def pipe(
250
+ face_fn: Callable[..., Awaitable[ExecutionPlan]],
251
+ mute_fn: Callable[[ActionStep], Awaitable[Any]],
252
+ task: Any,
253
+ *,
254
+ face_args: dict[str, Any] | None = None,
255
+ halt_on_deny: bool = True,
256
+ halt_on_error: bool = False,
257
+ ) -> PipelineResult:
258
+ """Execute a Face→Hands pipeline with kernel-level validation.
259
+
260
+ 1. Face agent produces an ExecutionPlan from the task
261
+ 2. Kernel validates plan capabilities
262
+ 3. Hands agent executes each step sequentially
263
+ 4. Full audit trail is recorded
264
+
265
+ Args:
266
+ face_fn: A @face_agent decorated function
267
+ mute_fn: A @mute_agent decorated function
268
+ task: Input to pass to the face agent
269
+ face_args: Extra keyword args for the face agent
270
+ halt_on_deny: Stop pipeline if any step is denied (default True)
271
+ halt_on_error: Stop pipeline if any step fails (default False)
272
+
273
+ Returns:
274
+ PipelineResult with step results, audit log, and success status
275
+ """
276
+ start = time.perf_counter()
277
+ audit: list[dict[str, Any]] = []
278
+ result = PipelineResult(plan=ExecutionPlan(), audit_log=audit)
279
+
280
+ # Validate decorator roles
281
+ if not getattr(face_fn, "_agent_role", None) == "face":
282
+ raise TypeError("First argument to pipe() must be a @face_agent function")
283
+ if not getattr(mute_fn, "_agent_role", None) == "mute":
284
+ raise TypeError("Second argument to pipe() must be a @mute_agent function")
285
+
286
+ # --- Phase 1: Reasoning (Face) ---
287
+ audit.append({
288
+ "phase": "face",
289
+ "event": "start",
290
+ "timestamp": datetime.now(timezone.utc).isoformat(),
291
+ "task_preview": str(task)[:200],
292
+ })
293
+
294
+ try:
295
+ kw = face_args or {}
296
+ plan = await face_fn(task, **kw)
297
+ except (CapabilityViolation, TypeError) as e:
298
+ audit.append({"phase": "face", "event": "error", "error": str(e)})
299
+ result.success = False
300
+ result.total_duration_ms = (time.perf_counter() - start) * 1000
301
+ return result
302
+
303
+ result.plan = plan
304
+ audit.append({
305
+ "phase": "face",
306
+ "event": "plan_produced",
307
+ "plan_id": plan.plan_id,
308
+ "step_count": len(plan.steps),
309
+ "actions": sorted(plan.actions_used),
310
+ })
311
+
312
+ # --- Phase 2: Capability Validation (Kernel) ---
313
+ mute_caps = getattr(mute_fn, "_capabilities", None) or set()
314
+ denied_indices = _validate_plan_capabilities(plan, mute_caps) if mute_caps else []
315
+ result.denied_steps = denied_indices
316
+
317
+ if denied_indices:
318
+ denied_actions = {plan.steps[i].action for i in denied_indices}
319
+ audit.append({
320
+ "phase": "kernel",
321
+ "event": "capability_denied",
322
+ "denied_steps": denied_indices,
323
+ "denied_actions": sorted(denied_actions),
324
+ "allowed": sorted(mute_caps),
325
+ })
326
+ if halt_on_deny:
327
+ result.success = False
328
+ result.total_duration_ms = (time.perf_counter() - start) * 1000
329
+ for i in denied_indices:
330
+ result.step_results.append(StepResult(
331
+ step_index=i,
332
+ action=plan.steps[i].action,
333
+ status=ActionStatus.DENIED,
334
+ error=f"Capability '{plan.steps[i].action}' not granted to mute agent",
335
+ ))
336
+ return result
337
+
338
+ # --- Phase 3: Execution (Hands) ---
339
+ all_ok = True
340
+ for i, step in enumerate(plan.steps):
341
+ if i in denied_indices:
342
+ result.step_results.append(StepResult(
343
+ step_index=i, action=step.action, status=ActionStatus.DENIED,
344
+ ))
345
+ continue
346
+
347
+ # Check dependencies
348
+ deps_met = all(
349
+ result.step_results[d].status == ActionStatus.EXECUTED
350
+ for d in step.depends_on
351
+ if d < len(result.step_results)
352
+ )
353
+ if not deps_met:
354
+ result.step_results.append(StepResult(
355
+ step_index=i, action=step.action, status=ActionStatus.FAILED,
356
+ error="Dependency not met",
357
+ ))
358
+ all_ok = False
359
+ if halt_on_error:
360
+ break
361
+ continue
362
+
363
+ step_start = time.perf_counter()
364
+ try:
365
+ data = await mute_fn(step)
366
+ duration = (time.perf_counter() - step_start) * 1000
367
+ result.step_results.append(StepResult(
368
+ step_index=i, action=step.action,
369
+ status=ActionStatus.EXECUTED, data=data, duration_ms=duration,
370
+ ))
371
+ audit.append({
372
+ "phase": "mute",
373
+ "event": "step_executed",
374
+ "step": i,
375
+ "action": step.action,
376
+ "duration_ms": round(duration, 2),
377
+ })
378
+ except CapabilityViolation:
379
+ raise # Never swallow capability violations
380
+ except Exception as exc:
381
+ duration = (time.perf_counter() - step_start) * 1000
382
+ result.step_results.append(StepResult(
383
+ step_index=i, action=step.action,
384
+ status=ActionStatus.FAILED,
385
+ error=str(exc), duration_ms=duration,
386
+ ))
387
+ audit.append({
388
+ "phase": "mute",
389
+ "event": "step_failed",
390
+ "step": i,
391
+ "action": step.action,
392
+ "error": str(exc),
393
+ })
394
+ all_ok = False
395
+ if halt_on_error:
396
+ break
397
+
398
+ result.success = all_ok and len(denied_indices) == 0
399
+ result.total_duration_ms = (time.perf_counter() - start) * 1000
400
+ audit.append({
401
+ "phase": "pipeline",
402
+ "event": "complete",
403
+ "success": result.success,
404
+ "total_ms": round(result.total_duration_ms, 2),
405
+ })
406
+
407
+ return result
408
+
409
+
410
+ # =============================================================================
411
+ # Public API
412
+ # =============================================================================
413
+
414
+ __all__ = [
415
+ # Data types
416
+ "ActionStep",
417
+ "ActionStatus",
418
+ "ExecutionPlan",
419
+ "StepResult",
420
+ "PipelineResult",
421
+ # Decorators
422
+ "face_agent",
423
+ "mute_agent",
424
+ # Pipeline
425
+ "pipe",
426
+ # Errors
427
+ "CapabilityViolation",
428
+ ]
agent_os/mute_agent.py ADDED
@@ -0,0 +1,209 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Mute Agent — post-execution gate for output filtering.
4
+
5
+ The mute agent sits between the execution engine and the caller, inspecting
6
+ and sanitising execution results before they leave the kernel. It removes
7
+ or redacts sensitive data (PII, credentials, internal metadata) according to
8
+ configurable ``MutePolicy`` rules.
9
+
10
+ Architecture:
11
+ ExecutionEngine ──▶ MuteAgent.mute(result) ──▶ sanitised result ──▶ Caller
12
+
13
+ Built-in pattern categories:
14
+ - **email**: RFC-5322-style email addresses
15
+ - **phone**: North-American and international phone numbers
16
+ - **ssn**: US Social Security Numbers
17
+ - **credit_card**: Major card number formats (Visa, MC, Amex, Discover)
18
+ - **api_key**: Common API-key / secret-key patterns
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ import os
25
+ import re
26
+ import warnings
27
+ from dataclasses import dataclass, field
28
+ from typing import Any
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ _SAMPLE_DISCLAIMER = (
33
+ "\u26a0\ufe0f These are SAMPLE PII detection patterns provided as a starting "
34
+ "point. You MUST review, customise, and extend them for your specific use "
35
+ "case before deploying to production."
36
+ )
37
+
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Built-in PII / sensitive-data patterns
41
+ # ---------------------------------------------------------------------------
42
+
43
+ BUILTIN_PATTERNS: dict[str, str] = {
44
+ "email": r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+",
45
+ "phone": r"(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}",
46
+ "ssn": r"\b\d{3}-\d{2}-\d{4}\b",
47
+ "credit_card": r"\b(?:\d[ -]*?){13,19}\b",
48
+ "api_key": (
49
+ r"(?:api[_-]?key|secret[_-]?key|access[_-]?token|bearer)"
50
+ r"[\s:=]+['\"]?[A-Za-z0-9_\-]{16,}['\"]?"
51
+ ),
52
+ }
53
+
54
+ # Pre-compiled versions for performance
55
+ _COMPILED: dict[str, re.Pattern[str]] = {
56
+ name: re.compile(pattern, re.IGNORECASE)
57
+ for name, pattern in BUILTIN_PATTERNS.items()
58
+ }
59
+
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Externalised configuration dataclass
63
+ # ---------------------------------------------------------------------------
64
+
65
+ @dataclass
66
+ class PIIDetectionConfig:
67
+ """Structured configuration for PII detection patterns, loadable from YAML.
68
+
69
+ Attributes:
70
+ builtin_patterns: Mapping of pattern name to regex string.
71
+ disclaimer: Disclaimer text shown in logs.
72
+ """
73
+
74
+ builtin_patterns: dict[str, str] = field(default_factory=lambda: dict(BUILTIN_PATTERNS))
75
+ disclaimer: str = ""
76
+
77
+
78
+ def load_pii_config(path: str) -> PIIDetectionConfig:
79
+ """Load PII detection configuration from a YAML file.
80
+
81
+ Args:
82
+ path: Path to a YAML file with a ``builtin_patterns`` section.
83
+
84
+ Returns:
85
+ PIIDetectionConfig populated from the YAML data.
86
+
87
+ Raises:
88
+ FileNotFoundError: If the config file does not exist.
89
+ ValueError: If the YAML is missing the ``builtin_patterns`` section.
90
+ """
91
+ import yaml
92
+
93
+ if not os.path.exists(path):
94
+ raise FileNotFoundError(f"PII detection config not found: {path}")
95
+
96
+ with open(path, "r", encoding="utf-8") as fh:
97
+ data = yaml.safe_load(fh.read())
98
+
99
+ if not isinstance(data, dict) or "builtin_patterns" not in data:
100
+ raise ValueError(f"YAML file must contain a 'builtin_patterns' section: {path}")
101
+
102
+ return PIIDetectionConfig(
103
+ builtin_patterns=data["builtin_patterns"],
104
+ disclaimer=data.get("disclaimer", ""),
105
+ )
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Data models
110
+ # ---------------------------------------------------------------------------
111
+
112
+ @dataclass
113
+ class MutePolicy:
114
+ """Rules for what to mute/redact in execution output.
115
+
116
+ Attributes:
117
+ enabled_builtins: Names of built-in patterns to apply
118
+ (e.g. ``["email", "ssn"]``). An empty list disables builtins.
119
+ custom_patterns: Additional regex patterns (raw strings).
120
+ sensitive_keywords: Exact substring keywords to redact.
121
+ replacement: The string used to replace redacted content.
122
+ """
123
+ enabled_builtins: list[str] = field(default_factory=lambda: list(BUILTIN_PATTERNS.keys()))
124
+ custom_patterns: list[str] = field(default_factory=list)
125
+ sensitive_keywords: list[str] = field(default_factory=list)
126
+ replacement: str = "[REDACTED]"
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Mute Agent
131
+ # ---------------------------------------------------------------------------
132
+
133
+ class MuteAgent:
134
+ """Post-execution gate that redacts sensitive content from results.
135
+
136
+ Args:
137
+ policy: A ``MutePolicy`` describing what to redact.
138
+ """
139
+
140
+ def __init__(self, policy: MutePolicy | None = None) -> None:
141
+ if policy is None:
142
+ warnings.warn(
143
+ "MuteAgent() uses built-in sample rules that may not "
144
+ "cover all PII patterns. For production use, load an "
145
+ "explicit config with load_pii_config(). "
146
+ "See examples/policies/pii-detection.yaml for a sample configuration.",
147
+ stacklevel=2,
148
+ )
149
+ self.policy = policy or MutePolicy()
150
+ self._custom_compiled: list[re.Pattern[str]] = [
151
+ re.compile(p, re.IGNORECASE) for p in self.policy.custom_patterns
152
+ ]
153
+
154
+ # -- public API ---------------------------------------------------------
155
+
156
+ def mute(self, result: Any) -> Any:
157
+ """Filter *result*, redacting sensitive content in-place.
158
+
159
+ Accepts an ``ExecutionResult`` (from ``agent_os.stateless``) or any
160
+ object with a ``data`` attribute. The ``data`` field is walked
161
+ recursively and string values are scrubbed.
162
+
163
+ Returns:
164
+ The same result object with sensitive strings replaced.
165
+ """
166
+ if hasattr(result, "data") and result.data is not None:
167
+ result.data = self._scrub(result.data)
168
+
169
+ if hasattr(result, "metadata") and isinstance(result.metadata, dict):
170
+ result.metadata = self._scrub(result.metadata)
171
+
172
+ return result
173
+
174
+ def scrub_text(self, text: str) -> str:
175
+ """Redact sensitive content from a plain string."""
176
+ return self._scrub_string(text)
177
+
178
+ # -- internals ----------------------------------------------------------
179
+
180
+ def _scrub(self, value: Any) -> Any:
181
+ """Recursively scrub strings inside dicts, lists, and scalars."""
182
+ if isinstance(value, str):
183
+ return self._scrub_string(value)
184
+ if isinstance(value, dict):
185
+ return {k: self._scrub(v) for k, v in value.items()}
186
+ if isinstance(value, (list, tuple)):
187
+ scrubbed = [self._scrub(item) for item in value]
188
+ return type(value)(scrubbed)
189
+ return value
190
+
191
+ def _scrub_string(self, text: str) -> str:
192
+ replacement = self.policy.replacement
193
+
194
+ # Built-in patterns
195
+ for name in self.policy.enabled_builtins:
196
+ compiled = _COMPILED.get(name)
197
+ if compiled:
198
+ text = compiled.sub(replacement, text)
199
+
200
+ # Custom regex patterns
201
+ for pattern in self._custom_compiled:
202
+ text = pattern.sub(replacement, text)
203
+
204
+ # Keyword substring replacement
205
+ for keyword in self.policy.sensitive_keywords:
206
+ if keyword in text:
207
+ text = text.replace(keyword, replacement)
208
+
209
+ return text