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
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
|