agent_os_kernel 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_control_plane/__init__.py +662 -0
- agent_control_plane/a2a_adapter.py +543 -0
- agent_control_plane/adapter.py +417 -0
- agent_control_plane/agent_hibernation.py +394 -0
- agent_control_plane/agent_kernel.py +470 -0
- agent_control_plane/compliance.py +720 -0
- agent_control_plane/constraint_graphs.py +478 -0
- agent_control_plane/control_plane.py +854 -0
- agent_control_plane/example_executors.py +195 -0
- agent_control_plane/execution_engine.py +231 -0
- agent_control_plane/flight_recorder.py +846 -0
- agent_control_plane/governance_layer.py +435 -0
- agent_control_plane/hf_utils.py +563 -0
- agent_control_plane/interfaces/__init__.py +55 -0
- agent_control_plane/interfaces/kernel_interface.py +361 -0
- agent_control_plane/interfaces/plugin_interface.py +497 -0
- agent_control_plane/interfaces/protocol_interfaces.py +387 -0
- agent_control_plane/kernel_space.py +1009 -0
- agent_control_plane/langchain_adapter.py +424 -0
- agent_control_plane/lifecycle.py +3113 -0
- agent_control_plane/mcp_adapter.py +653 -0
- agent_control_plane/ml_safety.py +563 -0
- agent_control_plane/multimodal.py +727 -0
- agent_control_plane/mute_agent.py +422 -0
- agent_control_plane/observability.py +787 -0
- agent_control_plane/orchestrator.py +482 -0
- agent_control_plane/plugin_registry.py +750 -0
- agent_control_plane/policy_engine.py +954 -0
- agent_control_plane/process_isolation.py +777 -0
- agent_control_plane/shadow_mode.py +310 -0
- agent_control_plane/signals.py +493 -0
- agent_control_plane/supervisor_agents.py +430 -0
- agent_control_plane/time_travel_debugger.py +557 -0
- agent_control_plane/tool_registry.py +452 -0
- agent_control_plane/vfs.py +697 -0
- agent_kernel/__init__.py +69 -0
- agent_kernel/analyzer.py +435 -0
- agent_kernel/auditor.py +36 -0
- agent_kernel/completeness_auditor.py +237 -0
- agent_kernel/detector.py +203 -0
- agent_kernel/kernel.py +744 -0
- agent_kernel/memory_manager.py +85 -0
- agent_kernel/models.py +374 -0
- agent_kernel/nudge_mechanism.py +263 -0
- agent_kernel/outcome_analyzer.py +338 -0
- agent_kernel/patcher.py +582 -0
- agent_kernel/semantic_analyzer.py +316 -0
- agent_kernel/semantic_purge.py +349 -0
- agent_kernel/simulator.py +449 -0
- agent_kernel/teacher.py +85 -0
- agent_kernel/triage.py +152 -0
- agent_os/__init__.py +409 -0
- agent_os/_adversarial_impl.py +200 -0
- agent_os/_circuit_breaker_impl.py +232 -0
- agent_os/_mcp_metrics.py +193 -0
- agent_os/adversarial.py +20 -0
- agent_os/agents_compat.py +490 -0
- agent_os/audit_logger.py +135 -0
- agent_os/base_agent.py +651 -0
- agent_os/circuit_breaker.py +34 -0
- agent_os/cli/__init__.py +659 -0
- agent_os/cli/cmd_audit.py +128 -0
- agent_os/cli/cmd_init.py +152 -0
- agent_os/cli/cmd_policy.py +41 -0
- agent_os/cli/cmd_policy_gen.py +180 -0
- agent_os/cli/cmd_validate.py +258 -0
- agent_os/cli/mcp_scan.py +265 -0
- agent_os/cli/output.py +192 -0
- agent_os/cli/policy_checker.py +330 -0
- agent_os/compat.py +74 -0
- agent_os/constraint_graph.py +234 -0
- agent_os/content_governance.py +140 -0
- agent_os/context_budget.py +305 -0
- agent_os/credential_redactor.py +224 -0
- agent_os/diff_policy.py +89 -0
- agent_os/egress_policy.py +159 -0
- agent_os/escalation.py +276 -0
- agent_os/event_bus.py +124 -0
- agent_os/exceptions.py +180 -0
- agent_os/execution_context_policy.py +141 -0
- agent_os/github_enterprise.py +96 -0
- agent_os/health.py +20 -0
- agent_os/integrations/__init__.py +279 -0
- agent_os/integrations/a2a_adapter.py +279 -0
- agent_os/integrations/agent_lightning/__init__.py +30 -0
- agent_os/integrations/anthropic_adapter.py +420 -0
- agent_os/integrations/autogen_adapter.py +620 -0
- agent_os/integrations/base.py +1137 -0
- agent_os/integrations/compat.py +229 -0
- agent_os/integrations/config.py +98 -0
- agent_os/integrations/conversation_guardian.py +957 -0
- agent_os/integrations/crewai_adapter.py +467 -0
- agent_os/integrations/drift_detector.py +425 -0
- agent_os/integrations/dry_run.py +124 -0
- agent_os/integrations/escalation.py +582 -0
- agent_os/integrations/gemini_adapter.py +364 -0
- agent_os/integrations/google_adk_adapter.py +633 -0
- agent_os/integrations/guardrails_adapter.py +394 -0
- agent_os/integrations/health.py +197 -0
- agent_os/integrations/langchain_adapter.py +654 -0
- agent_os/integrations/llamafirewall.py +343 -0
- agent_os/integrations/llamaindex_adapter.py +188 -0
- agent_os/integrations/logging.py +191 -0
- agent_os/integrations/maf_adapter.py +631 -0
- agent_os/integrations/mistral_adapter.py +365 -0
- agent_os/integrations/openai_adapter.py +816 -0
- agent_os/integrations/openai_agents_sdk.py +406 -0
- agent_os/integrations/policy_compose.py +171 -0
- agent_os/integrations/profiling.py +144 -0
- agent_os/integrations/pydantic_ai_adapter.py +420 -0
- agent_os/integrations/rate_limiter.py +130 -0
- agent_os/integrations/rbac.py +143 -0
- agent_os/integrations/registry.py +113 -0
- agent_os/integrations/scope_guard.py +303 -0
- agent_os/integrations/semantic_kernel_adapter.py +769 -0
- agent_os/integrations/smolagents_adapter.py +629 -0
- agent_os/integrations/templates.py +178 -0
- agent_os/integrations/token_budget.py +134 -0
- agent_os/integrations/tool_aliases.py +190 -0
- agent_os/integrations/webhooks.py +177 -0
- agent_os/lite.py +208 -0
- agent_os/mcp_gateway.py +385 -0
- agent_os/mcp_message_signer.py +273 -0
- agent_os/mcp_protocols.py +161 -0
- agent_os/mcp_response_scanner.py +232 -0
- agent_os/mcp_security.py +924 -0
- agent_os/mcp_session_auth.py +231 -0
- agent_os/mcp_sliding_rate_limiter.py +184 -0
- agent_os/memory_guard.py +409 -0
- agent_os/metrics.py +134 -0
- agent_os/mute.py +428 -0
- agent_os/mute_agent.py +209 -0
- agent_os/policies/__init__.py +77 -0
- agent_os/policies/async_evaluator.py +275 -0
- agent_os/policies/backends.py +670 -0
- agent_os/policies/bridge.py +169 -0
- agent_os/policies/budget.py +85 -0
- agent_os/policies/cli.py +294 -0
- agent_os/policies/conflict_resolution.py +270 -0
- agent_os/policies/data_classification.py +252 -0
- agent_os/policies/evaluator.py +239 -0
- agent_os/policies/policy_schema.json +228 -0
- agent_os/policies/rate_limiting.py +145 -0
- agent_os/policies/schema.py +115 -0
- agent_os/policies/shared.py +331 -0
- agent_os/prompt_injection.py +694 -0
- agent_os/providers.py +182 -0
- agent_os/py.typed +0 -0
- agent_os/retry.py +81 -0
- agent_os/reversibility.py +251 -0
- agent_os/sandbox.py +432 -0
- agent_os/sandbox_provider.py +140 -0
- agent_os/secure_codegen.py +525 -0
- agent_os/security_skills.py +538 -0
- agent_os/semantic_policy.py +422 -0
- agent_os/server/__init__.py +15 -0
- agent_os/server/__main__.py +25 -0
- agent_os/server/app.py +277 -0
- agent_os/server/models.py +104 -0
- agent_os/shift_left_metrics.py +130 -0
- agent_os/stateless.py +742 -0
- agent_os/supervisor.py +148 -0
- agent_os/task_outcome.py +148 -0
- agent_os/transparency.py +181 -0
- agent_os/trust_root.py +128 -0
- agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
- agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
- agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
- agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
- agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
- agent_os_observability/__init__.py +27 -0
- agent_os_observability/dashboards.py +898 -0
- agent_os_observability/metrics.py +398 -0
- agent_os_observability/server.py +223 -0
- agent_os_observability/tracer.py +232 -0
- agent_primitives/__init__.py +24 -0
- agent_primitives/failures.py +84 -0
- agent_primitives/py.typed +0 -0
- amb_core/__init__.py +177 -0
- amb_core/adapters/__init__.py +57 -0
- amb_core/adapters/aws_sqs_broker.py +376 -0
- amb_core/adapters/azure_servicebus_broker.py +340 -0
- amb_core/adapters/kafka_broker.py +260 -0
- amb_core/adapters/nats_broker.py +285 -0
- amb_core/adapters/rabbitmq_broker.py +235 -0
- amb_core/adapters/redis_broker.py +262 -0
- amb_core/broker.py +145 -0
- amb_core/bus.py +481 -0
- amb_core/cloudevents.py +509 -0
- amb_core/dlq.py +345 -0
- amb_core/hf_utils.py +536 -0
- amb_core/memory_broker.py +410 -0
- amb_core/models.py +141 -0
- amb_core/persistence.py +529 -0
- amb_core/schema.py +294 -0
- amb_core/tracing.py +358 -0
- atr/__init__.py +640 -0
- atr/access.py +348 -0
- atr/composition.py +645 -0
- atr/decorator.py +357 -0
- atr/executor.py +384 -0
- atr/health.py +557 -0
- atr/hf_utils.py +449 -0
- atr/injection.py +422 -0
- atr/metrics.py +440 -0
- atr/policies.py +403 -0
- atr/py.typed +2 -0
- atr/registry.py +452 -0
- atr/schema.py +480 -0
- atr/tools/safe/__init__.py +75 -0
- atr/tools/safe/calculator.py +467 -0
- atr/tools/safe/datetime_tool.py +443 -0
- atr/tools/safe/file_reader.py +402 -0
- atr/tools/safe/http_client.py +316 -0
- atr/tools/safe/json_parser.py +374 -0
- atr/tools/safe/text_tool.py +537 -0
- atr/tools/safe/toolkit.py +175 -0
- caas/__init__.py +162 -0
- caas/api/__init__.py +7 -0
- caas/api/server.py +1328 -0
- caas/caching.py +834 -0
- caas/cli.py +210 -0
- caas/conversation.py +223 -0
- caas/decay.py +72 -0
- caas/detection/__init__.py +9 -0
- caas/detection/detector.py +238 -0
- caas/enrichment.py +130 -0
- caas/gateway/__init__.py +27 -0
- caas/gateway/trust_gateway.py +474 -0
- caas/hf_utils.py +479 -0
- caas/ingestion/__init__.py +23 -0
- caas/ingestion/processors.py +253 -0
- caas/ingestion/structure_parser.py +188 -0
- caas/models.py +356 -0
- caas/pragmatic_truth.py +444 -0
- caas/routing/__init__.py +10 -0
- caas/routing/heuristic_router.py +58 -0
- caas/storage/__init__.py +9 -0
- caas/storage/store.py +389 -0
- caas/triad.py +213 -0
- caas/tuning/__init__.py +9 -0
- caas/tuning/tuner.py +329 -0
- caas/vfs/__init__.py +14 -0
- caas/vfs/filesystem.py +452 -0
- cmvk/__init__.py +218 -0
- cmvk/audit.py +402 -0
- cmvk/benchmarks.py +478 -0
- cmvk/constitutional.py +904 -0
- cmvk/hf_utils.py +301 -0
- cmvk/metrics.py +473 -0
- cmvk/profiles.py +300 -0
- cmvk/py.typed +0 -0
- cmvk/types.py +12 -0
- cmvk/verification.py +956 -0
- emk/__init__.py +89 -0
- emk/causal.py +352 -0
- emk/hf_utils.py +421 -0
- emk/indexer.py +83 -0
- emk/py.typed +0 -0
- emk/schema.py +204 -0
- emk/sleep_cycle.py +347 -0
- emk/store.py +281 -0
- iatp/__init__.py +166 -0
- iatp/attestation.py +461 -0
- iatp/cli.py +317 -0
- iatp/hf_utils.py +472 -0
- iatp/ipc_pipes.py +580 -0
- iatp/main.py +412 -0
- iatp/models/__init__.py +447 -0
- iatp/policy_engine.py +337 -0
- iatp/py.typed +2 -0
- iatp/recovery.py +321 -0
- iatp/security/__init__.py +270 -0
- iatp/sidecar/__init__.py +519 -0
- iatp/telemetry/__init__.py +164 -0
- iatp/tests/__init__.py +1 -0
- iatp/tests/test_attestation.py +370 -0
- iatp/tests/test_cli.py +131 -0
- iatp/tests/test_ed25519_attestation.py +211 -0
- iatp/tests/test_models.py +130 -0
- iatp/tests/test_policy_engine.py +347 -0
- iatp/tests/test_recovery.py +281 -0
- iatp/tests/test_security.py +222 -0
- iatp/tests/test_sidecar.py +167 -0
- iatp/tests/test_telemetry.py +175 -0
- mcp_kernel_server/__init__.py +28 -0
- mcp_kernel_server/cli.py +274 -0
- mcp_kernel_server/resources.py +217 -0
- mcp_kernel_server/server.py +564 -0
- mcp_kernel_server/tools.py +1174 -0
- mute_agent/__init__.py +68 -0
- mute_agent/core/__init__.py +1 -0
- mute_agent/core/execution_agent.py +166 -0
- mute_agent/core/handshake_protocol.py +201 -0
- mute_agent/core/reasoning_agent.py +238 -0
- mute_agent/knowledge_graph/__init__.py +1 -0
- mute_agent/knowledge_graph/graph_elements.py +65 -0
- mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
- mute_agent/knowledge_graph/subgraph.py +224 -0
- mute_agent/listener/__init__.py +43 -0
- mute_agent/listener/adapters/__init__.py +31 -0
- mute_agent/listener/adapters/base_adapter.py +189 -0
- mute_agent/listener/adapters/caas_adapter.py +344 -0
- mute_agent/listener/adapters/control_plane_adapter.py +436 -0
- mute_agent/listener/adapters/iatp_adapter.py +332 -0
- mute_agent/listener/adapters/scak_adapter.py +251 -0
- mute_agent/listener/listener.py +610 -0
- mute_agent/listener/state_observer.py +436 -0
- mute_agent/listener/threshold_config.py +313 -0
- mute_agent/super_system/__init__.py +1 -0
- mute_agent/super_system/router.py +204 -0
- mute_agent/visualization/__init__.py +10 -0
- mute_agent/visualization/graph_debugger.py +502 -0
- nexus/README.md +60 -0
- nexus/__init__.py +51 -0
- nexus/arbiter.py +359 -0
- nexus/client.py +466 -0
- nexus/dmz.py +444 -0
- nexus/escrow.py +430 -0
- nexus/exceptions.py +286 -0
- nexus/pyproject.toml +36 -0
- nexus/registry.py +393 -0
- nexus/reputation.py +425 -0
- nexus/schemas/__init__.py +51 -0
- nexus/schemas/compliance.py +276 -0
- nexus/schemas/escrow.py +251 -0
- nexus/schemas/manifest.py +225 -0
- nexus/schemas/receipt.py +208 -0
- nexus/tests/__init__.py +0 -0
- nexus/tests/conftest.py +146 -0
- nexus/tests/test_arbiter.py +192 -0
- nexus/tests/test_dmz.py +194 -0
- nexus/tests/test_escrow.py +276 -0
- nexus/tests/test_exceptions.py +225 -0
- nexus/tests/test_registry.py +232 -0
- nexus/tests/test_reputation.py +328 -0
- nexus/tests/test_schemas.py +295 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
AGENTS.md Compatibility for Agent OS.
|
|
5
|
+
|
|
6
|
+
Parses OpenAI/Anthropic standard .agents/ directory structure
|
|
7
|
+
and maps to Agent OS kernel policies.
|
|
8
|
+
|
|
9
|
+
Also provides generation utilities for producing AGENTS.md files
|
|
10
|
+
from AgentMdConfig dataclasses (GitHub Copilot / Cursor / Codex format).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Optional
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
from agent_os.integrations.base import GovernancePolicy
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class AgentSkill:
|
|
25
|
+
"""Parsed agent skill/capability."""
|
|
26
|
+
name: str
|
|
27
|
+
description: str
|
|
28
|
+
allowed: bool = True
|
|
29
|
+
requires_approval: bool = False
|
|
30
|
+
read_only: bool = False
|
|
31
|
+
constraints: dict[str, Any] = field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class AgentConfig:
|
|
36
|
+
"""Parsed agent configuration from AGENTS.md."""
|
|
37
|
+
name: str
|
|
38
|
+
description: str
|
|
39
|
+
skills: list[AgentSkill]
|
|
40
|
+
policies: list[str]
|
|
41
|
+
instructions: str
|
|
42
|
+
security_config: dict[str, Any] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AgentsParser:
|
|
46
|
+
"""
|
|
47
|
+
Parse .agents/ directory structure.
|
|
48
|
+
|
|
49
|
+
Supports:
|
|
50
|
+
- agents.md (OpenAI/Anthropic standard)
|
|
51
|
+
- security.md (Agent OS extension)
|
|
52
|
+
- YAML front matter
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
parser = AgentsParser()
|
|
56
|
+
config = parser.parse_directory("./my-project/.agents")
|
|
57
|
+
|
|
58
|
+
# Convert to kernel policies
|
|
59
|
+
policies = parser.to_kernel_policies(config)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self.skill_patterns = [
|
|
64
|
+
r"^[-*]\s+(.+)$", # - skill or * skill
|
|
65
|
+
r"^(\d+)\.\s+(.+)$", # 1. skill
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
def parse_directory(self, path: str) -> AgentConfig:
|
|
69
|
+
"""Parse .agents/ directory."""
|
|
70
|
+
agents_dir = Path(path)
|
|
71
|
+
|
|
72
|
+
if not agents_dir.exists():
|
|
73
|
+
raise FileNotFoundError(f"Agents directory not found: {path}")
|
|
74
|
+
|
|
75
|
+
# Parse main agents.md
|
|
76
|
+
agents_md = agents_dir / "agents.md"
|
|
77
|
+
if not agents_md.exists():
|
|
78
|
+
agents_md = agents_dir / "AGENTS.md"
|
|
79
|
+
|
|
80
|
+
config = self._parse_agents_md(agents_md) if agents_md.exists() else AgentConfig(
|
|
81
|
+
name="default",
|
|
82
|
+
description="",
|
|
83
|
+
skills=[],
|
|
84
|
+
policies=[],
|
|
85
|
+
instructions=""
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Parse security.md (Agent OS extension)
|
|
89
|
+
security_md = agents_dir / "security.md"
|
|
90
|
+
if security_md.exists():
|
|
91
|
+
config.security_config = self._parse_security_md(security_md)
|
|
92
|
+
|
|
93
|
+
return config
|
|
94
|
+
|
|
95
|
+
def _parse_agents_md(self, path: Path) -> AgentConfig:
|
|
96
|
+
"""Parse agents.md file."""
|
|
97
|
+
content = path.read_text(encoding="utf-8")
|
|
98
|
+
|
|
99
|
+
# Extract YAML front matter if present
|
|
100
|
+
front_matter = {}
|
|
101
|
+
if content.startswith("---"):
|
|
102
|
+
end = content.find("---", 3)
|
|
103
|
+
if end != -1:
|
|
104
|
+
yaml_content = content[3:end]
|
|
105
|
+
front_matter = yaml.safe_load(yaml_content) or {}
|
|
106
|
+
content = content[end + 3:].strip()
|
|
107
|
+
|
|
108
|
+
# Parse sections
|
|
109
|
+
name = front_matter.get("name", "agent")
|
|
110
|
+
description = ""
|
|
111
|
+
skills = []
|
|
112
|
+
instructions = content
|
|
113
|
+
|
|
114
|
+
# Find "You can:" or "Capabilities:" section
|
|
115
|
+
can_match = re.search(r"(?:You can|Capabilities|Skills):\s*\n((?:[-*\d].*\n?)+)", content, re.IGNORECASE)
|
|
116
|
+
if can_match:
|
|
117
|
+
skills_text = can_match.group(1)
|
|
118
|
+
skills = self._parse_skills(skills_text)
|
|
119
|
+
|
|
120
|
+
# Find description (first paragraph)
|
|
121
|
+
lines = content.split("\n")
|
|
122
|
+
for line in lines:
|
|
123
|
+
line = line.strip()
|
|
124
|
+
if line and not line.startswith(("#", "-", "*", "You can", "Capabilities")):
|
|
125
|
+
description = line
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
return AgentConfig(
|
|
129
|
+
name=name,
|
|
130
|
+
description=description,
|
|
131
|
+
skills=skills,
|
|
132
|
+
policies=front_matter.get("policies", []),
|
|
133
|
+
instructions=instructions,
|
|
134
|
+
security_config=front_matter.get("security", {})
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _parse_skills(self, text: str) -> list[AgentSkill]:
|
|
138
|
+
"""Parse skills from bullet list."""
|
|
139
|
+
skills = []
|
|
140
|
+
|
|
141
|
+
for line in text.strip().split("\n"):
|
|
142
|
+
line = line.strip()
|
|
143
|
+
if not line:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
# Extract skill text
|
|
147
|
+
skill_text = re.sub(r"^[-*\d.]+\s*", "", line)
|
|
148
|
+
|
|
149
|
+
# Parse constraints from parentheses
|
|
150
|
+
constraints = {}
|
|
151
|
+
read_only = False
|
|
152
|
+
requires_approval = False
|
|
153
|
+
|
|
154
|
+
# Check for (read-only), (requires approval), etc.
|
|
155
|
+
if "(read-only)" in skill_text.lower() or "(read only)" in skill_text.lower():
|
|
156
|
+
read_only = True
|
|
157
|
+
skill_text = re.sub(r"\s*\(read[- ]?only\)", "", skill_text, flags=re.IGNORECASE)
|
|
158
|
+
|
|
159
|
+
if "(requires approval)" in skill_text.lower():
|
|
160
|
+
requires_approval = True
|
|
161
|
+
skill_text = re.sub(r"\s*\(requires approval\)", "", skill_text, flags=re.IGNORECASE)
|
|
162
|
+
|
|
163
|
+
skills.append(AgentSkill(
|
|
164
|
+
name=self._skill_to_action(skill_text),
|
|
165
|
+
description=skill_text.strip(),
|
|
166
|
+
read_only=read_only,
|
|
167
|
+
requires_approval=requires_approval,
|
|
168
|
+
constraints=constraints
|
|
169
|
+
))
|
|
170
|
+
|
|
171
|
+
return skills
|
|
172
|
+
|
|
173
|
+
def _skill_to_action(self, skill: str) -> str:
|
|
174
|
+
"""Convert skill description to action name."""
|
|
175
|
+
skill_lower = skill.lower()
|
|
176
|
+
|
|
177
|
+
# Map common patterns
|
|
178
|
+
mappings = {
|
|
179
|
+
"query database": "database_query",
|
|
180
|
+
"read database": "database_query",
|
|
181
|
+
"write to database": "database_write",
|
|
182
|
+
"send email": "send_email",
|
|
183
|
+
"write file": "file_write",
|
|
184
|
+
"read file": "file_read",
|
|
185
|
+
"call api": "api_call",
|
|
186
|
+
"execute code": "code_execution",
|
|
187
|
+
"search": "search",
|
|
188
|
+
"browse": "web_browse",
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for pattern, action in mappings.items():
|
|
192
|
+
if pattern in skill_lower:
|
|
193
|
+
return action
|
|
194
|
+
|
|
195
|
+
# Default: snake_case the skill
|
|
196
|
+
return re.sub(r"[^a-z0-9]+", "_", skill_lower).strip("_")
|
|
197
|
+
|
|
198
|
+
def _parse_security_md(self, path: Path) -> dict[str, Any]:
|
|
199
|
+
"""Parse security.md (Agent OS extension)."""
|
|
200
|
+
content = path.read_text(encoding="utf-8")
|
|
201
|
+
|
|
202
|
+
# Try YAML front matter first
|
|
203
|
+
if content.startswith("---"):
|
|
204
|
+
end = content.find("---", 3)
|
|
205
|
+
if end != -1:
|
|
206
|
+
yaml_content = content[3:end]
|
|
207
|
+
return yaml.safe_load(yaml_content) or {}
|
|
208
|
+
|
|
209
|
+
# Try full YAML
|
|
210
|
+
try:
|
|
211
|
+
return yaml.safe_load(content) or {}
|
|
212
|
+
except yaml.YAMLError:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
return {}
|
|
216
|
+
|
|
217
|
+
def to_kernel_policies(self, config: AgentConfig) -> dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Convert AgentConfig to Agent OS kernel policies.
|
|
220
|
+
|
|
221
|
+
Returns policy configuration for Control Plane.
|
|
222
|
+
"""
|
|
223
|
+
policies = {
|
|
224
|
+
"name": config.name,
|
|
225
|
+
"version": "1.0",
|
|
226
|
+
"rules": []
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Convert skills to rules
|
|
230
|
+
for skill in config.skills:
|
|
231
|
+
rule = {
|
|
232
|
+
"action": skill.name,
|
|
233
|
+
"effect": "allow" if skill.allowed else "deny",
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if skill.read_only:
|
|
237
|
+
rule["mode"] = "read_only"
|
|
238
|
+
|
|
239
|
+
if skill.requires_approval:
|
|
240
|
+
rule["requires_approval"] = True
|
|
241
|
+
|
|
242
|
+
if skill.constraints:
|
|
243
|
+
rule["constraints"] = skill.constraints
|
|
244
|
+
|
|
245
|
+
policies["rules"].append(rule)
|
|
246
|
+
|
|
247
|
+
# Add security config
|
|
248
|
+
if config.security_config:
|
|
249
|
+
sec = config.security_config
|
|
250
|
+
|
|
251
|
+
if "signals" in sec:
|
|
252
|
+
policies["allowed_signals"] = sec["signals"]
|
|
253
|
+
|
|
254
|
+
if "max_tokens" in sec:
|
|
255
|
+
policies["limits"] = {"max_tokens": sec["max_tokens"]}
|
|
256
|
+
|
|
257
|
+
return policies
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def discover_agents(root_dir: str = ".") -> list[AgentConfig]:
|
|
261
|
+
"""
|
|
262
|
+
Discover all agent configurations in a repository.
|
|
263
|
+
|
|
264
|
+
Looks for:
|
|
265
|
+
- .agents/agents.md
|
|
266
|
+
- .agents/AGENTS.md
|
|
267
|
+
- agents.md (root)
|
|
268
|
+
- AGENTS.md (root)
|
|
269
|
+
|
|
270
|
+
Returns list of parsed configurations.
|
|
271
|
+
"""
|
|
272
|
+
parser = AgentsParser()
|
|
273
|
+
configs = []
|
|
274
|
+
root = Path(root_dir)
|
|
275
|
+
|
|
276
|
+
# Check .agents/ directory
|
|
277
|
+
agents_dir = root / ".agents"
|
|
278
|
+
if agents_dir.exists():
|
|
279
|
+
try:
|
|
280
|
+
configs.append(parser.parse_directory(str(agents_dir)))
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
# Check root agents.md
|
|
285
|
+
for name in ["agents.md", "AGENTS.md"]:
|
|
286
|
+
agents_md = root / name
|
|
287
|
+
if agents_md.exists():
|
|
288
|
+
try:
|
|
289
|
+
config = parser._parse_agents_md(agents_md)
|
|
290
|
+
configs.append(config)
|
|
291
|
+
except Exception:
|
|
292
|
+
pass
|
|
293
|
+
|
|
294
|
+
return configs
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ── AGENTS.md Generator ──────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
_AGENTS_MD_VERSION = "1.0"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@dataclass
|
|
304
|
+
class AgentMdConfig:
|
|
305
|
+
"""Configuration for generating an AGENTS.md file.
|
|
306
|
+
|
|
307
|
+
Maps Agent OS concepts (governance policy, RBAC role, tools) into the
|
|
308
|
+
standard AGENTS.md format consumed by GitHub Copilot, Cursor, Codex, etc.
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
name: str
|
|
312
|
+
description: str = ""
|
|
313
|
+
tools: list[str] = field(default_factory=list)
|
|
314
|
+
policy: Optional[GovernancePolicy] = None
|
|
315
|
+
role: Optional[str] = None
|
|
316
|
+
build_commands: list[str] = field(default_factory=list)
|
|
317
|
+
test_commands: list[str] = field(default_factory=list)
|
|
318
|
+
lint_commands: list[str] = field(default_factory=list)
|
|
319
|
+
boundaries: list[str] = field(default_factory=list)
|
|
320
|
+
code_style: dict[str, str] = field(default_factory=dict)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def generate_agents_md(config: AgentMdConfig) -> str:
|
|
324
|
+
"""Generate a valid AGENTS.md string from *config*.
|
|
325
|
+
|
|
326
|
+
The output includes YAML frontmatter followed by Markdown sections for
|
|
327
|
+
project overview, build & test commands, code style, governance,
|
|
328
|
+
boundaries, and commit style.
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
parts: list[str] = []
|
|
332
|
+
|
|
333
|
+
# ── YAML frontmatter ─────────────────────────────────────────────────
|
|
334
|
+
fm: dict[str, Any] = {
|
|
335
|
+
"name": config.name,
|
|
336
|
+
"version": _AGENTS_MD_VERSION,
|
|
337
|
+
}
|
|
338
|
+
if config.description:
|
|
339
|
+
fm["description"] = config.description
|
|
340
|
+
if config.tools:
|
|
341
|
+
fm["tools"] = config.tools
|
|
342
|
+
if config.role:
|
|
343
|
+
fm["role"] = config.role
|
|
344
|
+
|
|
345
|
+
parts.append("---")
|
|
346
|
+
parts.append(yaml.dump(fm, default_flow_style=False, sort_keys=False).rstrip())
|
|
347
|
+
parts.append("---")
|
|
348
|
+
parts.append("")
|
|
349
|
+
|
|
350
|
+
# ── Title ────────────────────────────────────────────────────────────
|
|
351
|
+
parts.append(f"# {config.name} — Coding Agent Instructions")
|
|
352
|
+
parts.append("")
|
|
353
|
+
|
|
354
|
+
# ── Project Overview ─────────────────────────────────────────────────
|
|
355
|
+
if config.description:
|
|
356
|
+
parts.append("## Project Overview")
|
|
357
|
+
parts.append("")
|
|
358
|
+
parts.append(config.description)
|
|
359
|
+
parts.append("")
|
|
360
|
+
|
|
361
|
+
# ── Build & Test Commands ────────────────────────────────────────────
|
|
362
|
+
has_commands = config.build_commands or config.test_commands or config.lint_commands
|
|
363
|
+
if has_commands:
|
|
364
|
+
parts.append("## Build & Test Commands")
|
|
365
|
+
parts.append("")
|
|
366
|
+
parts.append("```bash")
|
|
367
|
+
for cmd in config.build_commands:
|
|
368
|
+
parts.append(cmd)
|
|
369
|
+
for cmd in config.test_commands:
|
|
370
|
+
parts.append(cmd)
|
|
371
|
+
for cmd in config.lint_commands:
|
|
372
|
+
parts.append(cmd)
|
|
373
|
+
parts.append("```")
|
|
374
|
+
parts.append("")
|
|
375
|
+
|
|
376
|
+
# ── Code Style ───────────────────────────────────────────────────────
|
|
377
|
+
if config.code_style:
|
|
378
|
+
parts.append("## Code Style")
|
|
379
|
+
parts.append("")
|
|
380
|
+
for key, value in config.code_style.items():
|
|
381
|
+
parts.append(f"- **{key}:** {value}")
|
|
382
|
+
parts.append("")
|
|
383
|
+
|
|
384
|
+
# ── Governance ───────────────────────────────────────────────────────
|
|
385
|
+
if config.policy is not None:
|
|
386
|
+
parts.append("## Governance")
|
|
387
|
+
parts.append("")
|
|
388
|
+
parts.append("```yaml")
|
|
389
|
+
parts.append(config.policy.to_yaml().rstrip())
|
|
390
|
+
parts.append("```")
|
|
391
|
+
parts.append("")
|
|
392
|
+
|
|
393
|
+
# ── Boundaries ───────────────────────────────────────────────────────
|
|
394
|
+
if config.boundaries:
|
|
395
|
+
parts.append("## Boundaries")
|
|
396
|
+
parts.append("")
|
|
397
|
+
for boundary in config.boundaries:
|
|
398
|
+
parts.append(f"- {boundary}")
|
|
399
|
+
parts.append("")
|
|
400
|
+
|
|
401
|
+
# ── Commit Style ─────────────────────────────────────────────────────
|
|
402
|
+
parts.append("## Commit Style")
|
|
403
|
+
parts.append("")
|
|
404
|
+
parts.append(
|
|
405
|
+
"Use conventional commits: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`"
|
|
406
|
+
)
|
|
407
|
+
parts.append("")
|
|
408
|
+
|
|
409
|
+
return "\n".join(parts)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def save_agents_md(config: AgentMdConfig, path: str) -> None:
|
|
413
|
+
"""Write the generated AGENTS.md to *path*."""
|
|
414
|
+
|
|
415
|
+
content = generate_agents_md(config)
|
|
416
|
+
Path(path).write_text(content, encoding="utf-8")
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def load_agents_md(path: str) -> AgentMdConfig:
|
|
420
|
+
"""Parse an AGENTS.md file back into an *AgentMdConfig*.
|
|
421
|
+
|
|
422
|
+
Extracts YAML frontmatter for metadata and scans Markdown sections for
|
|
423
|
+
build/test/lint commands, code style, governance policy, and boundaries.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
text = Path(path).read_text(encoding="utf-8")
|
|
427
|
+
|
|
428
|
+
# ── Parse YAML frontmatter ───────────────────────────────────────────
|
|
429
|
+
fm: dict[str, Any] = {}
|
|
430
|
+
body = text
|
|
431
|
+
if text.startswith("---"):
|
|
432
|
+
end = text.find("---", 3)
|
|
433
|
+
if end != -1:
|
|
434
|
+
fm = yaml.safe_load(text[3:end]) or {}
|
|
435
|
+
body = text[end + 3:].strip()
|
|
436
|
+
|
|
437
|
+
config = AgentMdConfig(
|
|
438
|
+
name=fm.get("name", "agent"),
|
|
439
|
+
description=fm.get("description", ""),
|
|
440
|
+
tools=fm.get("tools", []),
|
|
441
|
+
role=fm.get("role"),
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# ── Section regex (## Heading) ───────────────────────────────────────
|
|
445
|
+
section_re = re.compile(r"^##\s+(.+)$", re.MULTILINE)
|
|
446
|
+
sections: dict[str, str] = {}
|
|
447
|
+
matches = list(section_re.finditer(body))
|
|
448
|
+
for i, m in enumerate(matches):
|
|
449
|
+
heading = m.group(1).strip()
|
|
450
|
+
start = m.end()
|
|
451
|
+
end_pos = matches[i + 1].start() if i + 1 < len(matches) else len(body)
|
|
452
|
+
sections[heading] = body[start:end_pos].strip()
|
|
453
|
+
|
|
454
|
+
# ── Build & Test Commands ────────────────────────────────────────────
|
|
455
|
+
bt_section = sections.get("Build & Test Commands", "")
|
|
456
|
+
code_block = re.search(r"```(?:bash)?\s*\n(.*?)```", bt_section, re.DOTALL)
|
|
457
|
+
if code_block:
|
|
458
|
+
lines = [ln for ln in code_block.group(1).strip().splitlines() if ln.strip()]
|
|
459
|
+
# Simple heuristic: commands containing "test" → test, "lint"/"check"/"format" → lint, rest → build
|
|
460
|
+
for ln in lines:
|
|
461
|
+
low = ln.lower()
|
|
462
|
+
if "test" in low or "pytest" in low:
|
|
463
|
+
config.test_commands.append(ln)
|
|
464
|
+
elif any(kw in low for kw in ("lint", "check", "format", "ruff", "mypy")):
|
|
465
|
+
config.lint_commands.append(ln)
|
|
466
|
+
else:
|
|
467
|
+
config.build_commands.append(ln)
|
|
468
|
+
|
|
469
|
+
# ── Code Style ───────────────────────────────────────────────────────
|
|
470
|
+
cs_section = sections.get("Code Style", "")
|
|
471
|
+
for line in cs_section.splitlines():
|
|
472
|
+
# Matches both `**key:** value` and `**key**: value`
|
|
473
|
+
m = re.match(r"^-\s+\*\*(.+?):?\*\*:?\s*(.+)$", line.strip())
|
|
474
|
+
if m:
|
|
475
|
+
config.code_style[m.group(1).rstrip(":")] = m.group(2)
|
|
476
|
+
|
|
477
|
+
# ── Governance ───────────────────────────────────────────────────────
|
|
478
|
+
gov_section = sections.get("Governance", "")
|
|
479
|
+
gov_block = re.search(r"```(?:yaml)?\s*\n(.*?)```", gov_section, re.DOTALL)
|
|
480
|
+
if gov_block:
|
|
481
|
+
config.policy = GovernancePolicy.from_yaml(gov_block.group(1))
|
|
482
|
+
|
|
483
|
+
# ── Boundaries ───────────────────────────────────────────────────────
|
|
484
|
+
bd_section = sections.get("Boundaries", "")
|
|
485
|
+
for line in bd_section.splitlines():
|
|
486
|
+
stripped = line.strip()
|
|
487
|
+
if stripped.startswith("- "):
|
|
488
|
+
config.boundaries.append(stripped[2:])
|
|
489
|
+
|
|
490
|
+
return config
|
agent_os/audit_logger.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""Standard governance audit logger with pluggable backends."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import asdict, dataclass, field
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Protocol
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AuditEntry:
|
|
20
|
+
"""A governance audit log entry."""
|
|
21
|
+
|
|
22
|
+
timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
23
|
+
event_type: str = ""
|
|
24
|
+
agent_id: str = ""
|
|
25
|
+
action: str = ""
|
|
26
|
+
decision: str = ""
|
|
27
|
+
reason: str = ""
|
|
28
|
+
latency_ms: float = 0.0
|
|
29
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> dict[str, Any]:
|
|
32
|
+
return asdict(self)
|
|
33
|
+
|
|
34
|
+
def to_json(self) -> str:
|
|
35
|
+
return json.dumps(self.to_dict(), default=str)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AuditBackend(Protocol):
|
|
39
|
+
"""Protocol for audit log backends."""
|
|
40
|
+
|
|
41
|
+
def write(self, entry: AuditEntry) -> None: ...
|
|
42
|
+
def flush(self) -> None: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class JsonlFileBackend:
|
|
46
|
+
"""Writes audit entries as JSONL to a file."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, path: str | Path) -> None:
|
|
49
|
+
self.path = Path(path)
|
|
50
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
self._file = open(self.path, "a", encoding="utf-8")
|
|
52
|
+
|
|
53
|
+
def write(self, entry: AuditEntry) -> None:
|
|
54
|
+
self._file.write(entry.to_json() + "\n")
|
|
55
|
+
|
|
56
|
+
def flush(self) -> None:
|
|
57
|
+
self._file.flush()
|
|
58
|
+
|
|
59
|
+
def close(self) -> None:
|
|
60
|
+
self._file.close()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InMemoryBackend:
|
|
64
|
+
"""Stores audit entries in memory (useful for testing)."""
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
self.entries: list[AuditEntry] = []
|
|
68
|
+
|
|
69
|
+
def write(self, entry: AuditEntry) -> None:
|
|
70
|
+
self.entries.append(entry)
|
|
71
|
+
|
|
72
|
+
def flush(self) -> None:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class LoggingBackend:
|
|
77
|
+
"""Writes audit entries via Python logging."""
|
|
78
|
+
|
|
79
|
+
def __init__(self, logger_name: str = "agent_os.audit") -> None:
|
|
80
|
+
self._logger = logging.getLogger(logger_name)
|
|
81
|
+
|
|
82
|
+
def write(self, entry: AuditEntry) -> None:
|
|
83
|
+
self._logger.info(
|
|
84
|
+
"[%s] agent=%s action=%s decision=%s latency=%.1fms",
|
|
85
|
+
entry.event_type, entry.agent_id, entry.action,
|
|
86
|
+
entry.decision, entry.latency_ms,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def flush(self) -> None:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class GovernanceAuditLogger:
|
|
94
|
+
"""Standard audit logger with pluggable backends.
|
|
95
|
+
|
|
96
|
+
Example::
|
|
97
|
+
|
|
98
|
+
audit = GovernanceAuditLogger()
|
|
99
|
+
audit.add_backend(InMemoryBackend())
|
|
100
|
+
audit.log_decision(agent_id="a1", action="search", decision="allow")
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(self) -> None:
|
|
104
|
+
self._backends: list[Any] = []
|
|
105
|
+
|
|
106
|
+
def add_backend(self, backend: Any) -> None:
|
|
107
|
+
self._backends.append(backend)
|
|
108
|
+
|
|
109
|
+
def log(self, entry: AuditEntry) -> None:
|
|
110
|
+
for backend in self._backends:
|
|
111
|
+
backend.write(entry)
|
|
112
|
+
|
|
113
|
+
def log_decision(
|
|
114
|
+
self,
|
|
115
|
+
agent_id: str,
|
|
116
|
+
action: str,
|
|
117
|
+
decision: str,
|
|
118
|
+
reason: str = "",
|
|
119
|
+
latency_ms: float = 0.0,
|
|
120
|
+
**metadata: Any,
|
|
121
|
+
) -> None:
|
|
122
|
+
entry = AuditEntry(
|
|
123
|
+
event_type="governance_decision",
|
|
124
|
+
agent_id=agent_id,
|
|
125
|
+
action=action,
|
|
126
|
+
decision=decision,
|
|
127
|
+
reason=reason,
|
|
128
|
+
latency_ms=latency_ms,
|
|
129
|
+
metadata=metadata,
|
|
130
|
+
)
|
|
131
|
+
self.log(entry)
|
|
132
|
+
|
|
133
|
+
def flush(self) -> None:
|
|
134
|
+
for backend in self._backends:
|
|
135
|
+
backend.flush()
|