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
mute_agent/__init__.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Mute Agent - Decoupling Reasoning from Execution
|
|
5
|
+
|
|
6
|
+
Layer 5 Reference Implementation: A Listener agent that monitors graph states
|
|
7
|
+
without interfering until configured thresholds are exceeded.
|
|
8
|
+
|
|
9
|
+
Consolidated Stack:
|
|
10
|
+
- agent-control-plane: Base orchestration
|
|
11
|
+
- scak: Intelligence/Knowledge layer
|
|
12
|
+
- iatp: Security/Trust layer
|
|
13
|
+
- caas: Context-as-a-Service layer
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "3.1.0"
|
|
17
|
+
|
|
18
|
+
# Core components
|
|
19
|
+
from .core.reasoning_agent import ReasoningAgent
|
|
20
|
+
from .core.execution_agent import ExecutionAgent
|
|
21
|
+
from .core.handshake_protocol import HandshakeProtocol
|
|
22
|
+
from .knowledge_graph.multidimensional_graph import MultidimensionalKnowledgeGraph
|
|
23
|
+
from .super_system.router import SuperSystemRouter
|
|
24
|
+
|
|
25
|
+
# Layer 5: Listener Agent
|
|
26
|
+
from .listener import (
|
|
27
|
+
ListenerAgent,
|
|
28
|
+
ListenerState,
|
|
29
|
+
InterventionEvent,
|
|
30
|
+
ThresholdConfig,
|
|
31
|
+
ThresholdType,
|
|
32
|
+
InterventionLevel,
|
|
33
|
+
DEFAULT_THRESHOLDS,
|
|
34
|
+
StateObserver,
|
|
35
|
+
ObservationResult,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Layer adapters
|
|
39
|
+
from .listener.adapters import (
|
|
40
|
+
ControlPlaneAdapter,
|
|
41
|
+
IntelligenceAdapter,
|
|
42
|
+
SecurityAdapter,
|
|
43
|
+
ContextAdapter,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# Core
|
|
48
|
+
"ReasoningAgent",
|
|
49
|
+
"ExecutionAgent",
|
|
50
|
+
"HandshakeProtocol",
|
|
51
|
+
"MultidimensionalKnowledgeGraph",
|
|
52
|
+
"SuperSystemRouter",
|
|
53
|
+
# Layer 5: Listener
|
|
54
|
+
"ListenerAgent",
|
|
55
|
+
"ListenerState",
|
|
56
|
+
"InterventionEvent",
|
|
57
|
+
"ThresholdConfig",
|
|
58
|
+
"ThresholdType",
|
|
59
|
+
"InterventionLevel",
|
|
60
|
+
"DEFAULT_THRESHOLDS",
|
|
61
|
+
"StateObserver",
|
|
62
|
+
"ObservationResult",
|
|
63
|
+
# Adapters
|
|
64
|
+
"ControlPlaneAdapter",
|
|
65
|
+
"IntelligenceAdapter",
|
|
66
|
+
"SecurityAdapter",
|
|
67
|
+
"ContextAdapter",
|
|
68
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core components of the Mute Agent system."""
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
The Hands - The Execution Agent
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional, Any, Callable
|
|
8
|
+
from .handshake_protocol import HandshakeProtocol, HandshakeSession, HandshakeState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExecutionAgent:
|
|
12
|
+
"""
|
|
13
|
+
The Hands - The Execution Agent
|
|
14
|
+
|
|
15
|
+
This agent is responsible for executing actions that have been negotiated
|
|
16
|
+
and validated through the Handshake Protocol. It does not reason about
|
|
17
|
+
actions but simply executes them when properly authorized.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, protocol: HandshakeProtocol):
|
|
21
|
+
self.protocol = protocol
|
|
22
|
+
self.execution_handlers: Dict[str, Callable] = {}
|
|
23
|
+
self.execution_history: List[Dict[str, Any]] = []
|
|
24
|
+
|
|
25
|
+
def register_action_handler(
|
|
26
|
+
self,
|
|
27
|
+
action_id: str,
|
|
28
|
+
handler: Callable[[Dict[str, Any]], Dict[str, Any]]
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Register a handler function for a specific action.
|
|
32
|
+
The handler receives parameters and returns results.
|
|
33
|
+
"""
|
|
34
|
+
self.execution_handlers[action_id] = handler
|
|
35
|
+
|
|
36
|
+
def execute(self, session_id: str) -> HandshakeSession:
|
|
37
|
+
"""
|
|
38
|
+
Execute an action from an accepted handshake session.
|
|
39
|
+
This is the main execution entry point.
|
|
40
|
+
"""
|
|
41
|
+
session = self.protocol.get_session(session_id)
|
|
42
|
+
|
|
43
|
+
if not session:
|
|
44
|
+
raise ValueError(f"Session {session_id} not found")
|
|
45
|
+
|
|
46
|
+
if session.state != HandshakeState.ACCEPTED:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"Cannot execute session in state {session.state}. "
|
|
49
|
+
f"Session must be in ACCEPTED state."
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if not session.proposal:
|
|
53
|
+
raise ValueError("Session has no proposal")
|
|
54
|
+
|
|
55
|
+
# Mark as executing
|
|
56
|
+
self.protocol.start_execution(session_id)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
# Execute the action
|
|
60
|
+
result = self._execute_action(
|
|
61
|
+
session.proposal.action_id,
|
|
62
|
+
session.proposal.parameters,
|
|
63
|
+
session.proposal.context
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Mark as completed
|
|
67
|
+
self.protocol.complete_execution(session_id, result)
|
|
68
|
+
|
|
69
|
+
# Store in history
|
|
70
|
+
self.execution_history.append({
|
|
71
|
+
"session_id": session_id,
|
|
72
|
+
"action_id": session.proposal.action_id,
|
|
73
|
+
"result": result,
|
|
74
|
+
"success": True
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
# Mark as failed
|
|
79
|
+
self.protocol.fail_execution(session_id, str(e))
|
|
80
|
+
|
|
81
|
+
# Store in history
|
|
82
|
+
self.execution_history.append({
|
|
83
|
+
"session_id": session_id,
|
|
84
|
+
"action_id": session.proposal.action_id,
|
|
85
|
+
"error": str(e),
|
|
86
|
+
"success": False
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
raise
|
|
90
|
+
|
|
91
|
+
return self.protocol.get_session(session_id)
|
|
92
|
+
|
|
93
|
+
def _execute_action(
|
|
94
|
+
self,
|
|
95
|
+
action_id: str,
|
|
96
|
+
parameters: Dict[str, Any],
|
|
97
|
+
context: Dict[str, Any]
|
|
98
|
+
) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Execute a specific action using its registered handler.
|
|
101
|
+
"""
|
|
102
|
+
handler = self.execution_handlers.get(action_id)
|
|
103
|
+
|
|
104
|
+
if not handler:
|
|
105
|
+
# If no handler registered, return a default result
|
|
106
|
+
return {
|
|
107
|
+
"status": "no_handler",
|
|
108
|
+
"message": f"No handler registered for action {action_id}",
|
|
109
|
+
"action_id": action_id,
|
|
110
|
+
"parameters": parameters
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Call the handler
|
|
114
|
+
result = handler(parameters)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"status": "success",
|
|
118
|
+
"action_id": action_id,
|
|
119
|
+
"result": result
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def can_execute(self, session_id: str) -> bool:
|
|
123
|
+
"""Check if a session can be executed."""
|
|
124
|
+
session = self.protocol.get_session(session_id)
|
|
125
|
+
|
|
126
|
+
if not session:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
if session.state != HandshakeState.ACCEPTED:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
if not session.proposal:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Check if handler is registered (optional)
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def get_execution_statistics(self) -> Dict[str, Any]:
|
|
139
|
+
"""Get statistics about execution operations."""
|
|
140
|
+
if not self.execution_history:
|
|
141
|
+
return {
|
|
142
|
+
"total_executions": 0,
|
|
143
|
+
"successful_executions": 0,
|
|
144
|
+
"failed_executions": 0,
|
|
145
|
+
"success_rate": 0.0
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
total = len(self.execution_history)
|
|
149
|
+
successful = sum(1 for exec in self.execution_history if exec["success"])
|
|
150
|
+
failed = total - successful
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"total_executions": total,
|
|
154
|
+
"successful_executions": successful,
|
|
155
|
+
"failed_executions": failed,
|
|
156
|
+
"success_rate": successful / total if total > 0 else 0.0,
|
|
157
|
+
"actions_executed": self._get_action_counts()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
def _get_action_counts(self) -> Dict[str, int]:
|
|
161
|
+
"""Get counts of how many times each action was executed."""
|
|
162
|
+
counts = {}
|
|
163
|
+
for exec in self.execution_history:
|
|
164
|
+
action_id = exec["action_id"]
|
|
165
|
+
counts[action_id] = counts.get(action_id, 0) + 1
|
|
166
|
+
return counts
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
Dynamic Semantic Handshake Protocol - The negotiation mechanism between
|
|
5
|
+
The Face (Reasoning) and The Hands (Execution).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional, Any
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HandshakeState(Enum):
|
|
15
|
+
"""States in the handshake protocol."""
|
|
16
|
+
INITIATED = "initiated"
|
|
17
|
+
NEGOTIATING = "negotiating"
|
|
18
|
+
VALIDATED = "validated"
|
|
19
|
+
ACCEPTED = "accepted"
|
|
20
|
+
REJECTED = "rejected"
|
|
21
|
+
EXECUTING = "executing"
|
|
22
|
+
COMPLETED = "completed"
|
|
23
|
+
FAILED = "failed"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ActionProposal:
|
|
28
|
+
"""A proposed action from the Reasoning Agent."""
|
|
29
|
+
action_id: str
|
|
30
|
+
parameters: Dict[str, Any]
|
|
31
|
+
context: Dict[str, Any]
|
|
32
|
+
justification: str
|
|
33
|
+
priority: float = 1.0
|
|
34
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ValidationResult:
|
|
39
|
+
"""Result of validating an action proposal."""
|
|
40
|
+
is_valid: bool
|
|
41
|
+
errors: List[str] = field(default_factory=list)
|
|
42
|
+
warnings: List[str] = field(default_factory=list)
|
|
43
|
+
constraints_met: List[str] = field(default_factory=list)
|
|
44
|
+
constraints_violated: List[str] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class HandshakeSession:
|
|
49
|
+
"""A session tracking the handshake process."""
|
|
50
|
+
session_id: str
|
|
51
|
+
state: HandshakeState
|
|
52
|
+
proposal: Optional[ActionProposal] = None
|
|
53
|
+
validation_result: Optional[ValidationResult] = None
|
|
54
|
+
execution_result: Optional[Dict[str, Any]] = None
|
|
55
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
56
|
+
updated_at: datetime = field(default_factory=datetime.now)
|
|
57
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class HandshakeProtocol:
|
|
61
|
+
"""
|
|
62
|
+
The Dynamic Semantic Handshake Protocol manages the negotiation
|
|
63
|
+
between the Reasoning Agent (The Face) and the Execution Agent (The Hands).
|
|
64
|
+
|
|
65
|
+
Instead of free-text tool invocation, actions must be negotiated against
|
|
66
|
+
the knowledge graph constraints.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self):
|
|
70
|
+
self.sessions: Dict[str, HandshakeSession] = {}
|
|
71
|
+
self._session_counter = 0
|
|
72
|
+
|
|
73
|
+
def initiate_handshake(
|
|
74
|
+
self,
|
|
75
|
+
proposal: ActionProposal
|
|
76
|
+
) -> HandshakeSession:
|
|
77
|
+
"""
|
|
78
|
+
Initiate a new handshake session with an action proposal.
|
|
79
|
+
This is called by the Reasoning Agent.
|
|
80
|
+
"""
|
|
81
|
+
session_id = self._generate_session_id()
|
|
82
|
+
session = HandshakeSession(
|
|
83
|
+
session_id=session_id,
|
|
84
|
+
state=HandshakeState.INITIATED,
|
|
85
|
+
proposal=proposal
|
|
86
|
+
)
|
|
87
|
+
self.sessions[session_id] = session
|
|
88
|
+
return session
|
|
89
|
+
|
|
90
|
+
def validate_proposal(
|
|
91
|
+
self,
|
|
92
|
+
session_id: str,
|
|
93
|
+
validation_result: ValidationResult
|
|
94
|
+
) -> HandshakeSession:
|
|
95
|
+
"""
|
|
96
|
+
Validate the proposal in a session.
|
|
97
|
+
This is typically called after checking against the knowledge graph.
|
|
98
|
+
"""
|
|
99
|
+
session = self.sessions.get(session_id)
|
|
100
|
+
if not session:
|
|
101
|
+
raise ValueError(f"Session {session_id} not found")
|
|
102
|
+
|
|
103
|
+
session.validation_result = validation_result
|
|
104
|
+
session.state = HandshakeState.VALIDATED if validation_result.is_valid else HandshakeState.REJECTED
|
|
105
|
+
session.updated_at = datetime.now()
|
|
106
|
+
|
|
107
|
+
return session
|
|
108
|
+
|
|
109
|
+
def accept_proposal(self, session_id: str) -> HandshakeSession:
|
|
110
|
+
"""
|
|
111
|
+
Accept a validated proposal for execution.
|
|
112
|
+
This transitions from validation to acceptance.
|
|
113
|
+
"""
|
|
114
|
+
session = self.sessions.get(session_id)
|
|
115
|
+
if not session:
|
|
116
|
+
raise ValueError(f"Session {session_id} not found")
|
|
117
|
+
|
|
118
|
+
if session.state != HandshakeState.VALIDATED:
|
|
119
|
+
raise ValueError(f"Cannot accept proposal in state {session.state}")
|
|
120
|
+
|
|
121
|
+
if not session.validation_result or not session.validation_result.is_valid:
|
|
122
|
+
raise ValueError("Cannot accept invalid proposal")
|
|
123
|
+
|
|
124
|
+
session.state = HandshakeState.ACCEPTED
|
|
125
|
+
session.updated_at = datetime.now()
|
|
126
|
+
|
|
127
|
+
return session
|
|
128
|
+
|
|
129
|
+
def reject_proposal(
|
|
130
|
+
self,
|
|
131
|
+
session_id: str,
|
|
132
|
+
reason: str
|
|
133
|
+
) -> HandshakeSession:
|
|
134
|
+
"""Reject a proposal with a reason."""
|
|
135
|
+
session = self.sessions.get(session_id)
|
|
136
|
+
if not session:
|
|
137
|
+
raise ValueError(f"Session {session_id} not found")
|
|
138
|
+
|
|
139
|
+
session.state = HandshakeState.REJECTED
|
|
140
|
+
session.metadata["rejection_reason"] = reason
|
|
141
|
+
session.updated_at = datetime.now()
|
|
142
|
+
|
|
143
|
+
return session
|
|
144
|
+
|
|
145
|
+
def start_execution(self, session_id: str) -> HandshakeSession:
|
|
146
|
+
"""
|
|
147
|
+
Start executing an accepted proposal.
|
|
148
|
+
This is called by the Execution Agent.
|
|
149
|
+
"""
|
|
150
|
+
session = self.sessions.get(session_id)
|
|
151
|
+
if not session:
|
|
152
|
+
raise ValueError(f"Session {session_id} not found")
|
|
153
|
+
|
|
154
|
+
if session.state != HandshakeState.ACCEPTED:
|
|
155
|
+
raise ValueError(f"Cannot execute proposal in state {session.state}")
|
|
156
|
+
|
|
157
|
+
session.state = HandshakeState.EXECUTING
|
|
158
|
+
session.updated_at = datetime.now()
|
|
159
|
+
|
|
160
|
+
return session
|
|
161
|
+
|
|
162
|
+
def complete_execution(
|
|
163
|
+
self,
|
|
164
|
+
session_id: str,
|
|
165
|
+
result: Dict[str, Any]
|
|
166
|
+
) -> HandshakeSession:
|
|
167
|
+
"""Complete execution with results."""
|
|
168
|
+
session = self.sessions.get(session_id)
|
|
169
|
+
if not session:
|
|
170
|
+
raise ValueError(f"Session {session_id} not found")
|
|
171
|
+
|
|
172
|
+
session.execution_result = result
|
|
173
|
+
session.state = HandshakeState.COMPLETED
|
|
174
|
+
session.updated_at = datetime.now()
|
|
175
|
+
|
|
176
|
+
return session
|
|
177
|
+
|
|
178
|
+
def fail_execution(
|
|
179
|
+
self,
|
|
180
|
+
session_id: str,
|
|
181
|
+
error: str
|
|
182
|
+
) -> HandshakeSession:
|
|
183
|
+
"""Mark execution as failed."""
|
|
184
|
+
session = self.sessions.get(session_id)
|
|
185
|
+
if not session:
|
|
186
|
+
raise ValueError(f"Session {session_id} not found")
|
|
187
|
+
|
|
188
|
+
session.state = HandshakeState.FAILED
|
|
189
|
+
session.metadata["error"] = error
|
|
190
|
+
session.updated_at = datetime.now()
|
|
191
|
+
|
|
192
|
+
return session
|
|
193
|
+
|
|
194
|
+
def get_session(self, session_id: str) -> Optional[HandshakeSession]:
|
|
195
|
+
"""Get a session by ID."""
|
|
196
|
+
return self.sessions.get(session_id)
|
|
197
|
+
|
|
198
|
+
def _generate_session_id(self) -> str:
|
|
199
|
+
"""Generate a unique session ID."""
|
|
200
|
+
self._session_counter += 1
|
|
201
|
+
return f"session_{self._session_counter}_{datetime.now().timestamp()}"
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
"""
|
|
4
|
+
The Face - The Reasoning Agent
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, List, Optional, Any
|
|
8
|
+
from ..knowledge_graph.multidimensional_graph import MultidimensionalKnowledgeGraph
|
|
9
|
+
from ..knowledge_graph.graph_elements import Node, NodeType
|
|
10
|
+
from ..super_system.router import SuperSystemRouter, RoutingResult
|
|
11
|
+
from .handshake_protocol import (
|
|
12
|
+
HandshakeProtocol,
|
|
13
|
+
ActionProposal,
|
|
14
|
+
ValidationResult,
|
|
15
|
+
HandshakeSession,
|
|
16
|
+
HandshakeState
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ReasoningAgent:
|
|
21
|
+
"""
|
|
22
|
+
The Face - The Reasoning Agent
|
|
23
|
+
|
|
24
|
+
This agent is responsible for reasoning about actions and negotiating
|
|
25
|
+
with the Execution Agent through the Handshake Protocol. It does not
|
|
26
|
+
execute actions directly but proposes them based on graph constraints.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# Maximum size for reasoning history to prevent memory bloat
|
|
30
|
+
MAX_HISTORY_SIZE = 1000
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
knowledge_graph: MultidimensionalKnowledgeGraph,
|
|
35
|
+
router: SuperSystemRouter,
|
|
36
|
+
protocol: HandshakeProtocol
|
|
37
|
+
):
|
|
38
|
+
self.knowledge_graph = knowledge_graph
|
|
39
|
+
self.router = router
|
|
40
|
+
self.protocol = protocol
|
|
41
|
+
self.reasoning_history: List[Dict[str, Any]] = []
|
|
42
|
+
|
|
43
|
+
def reason(self, context: Dict[str, Any]) -> RoutingResult:
|
|
44
|
+
"""
|
|
45
|
+
Reason about the context and determine available actions.
|
|
46
|
+
This uses the Super System Router to prune the action space.
|
|
47
|
+
"""
|
|
48
|
+
routing_result = self.router.route(context)
|
|
49
|
+
|
|
50
|
+
# Store reasoning step (with size limit to prevent memory bloat)
|
|
51
|
+
self.reasoning_history.append({
|
|
52
|
+
"context": context,
|
|
53
|
+
"selected_dimensions": routing_result.selected_dimensions,
|
|
54
|
+
"available_actions": len(routing_result.pruned_action_space)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
# Trim history if it exceeds maximum size
|
|
58
|
+
if len(self.reasoning_history) > self.MAX_HISTORY_SIZE:
|
|
59
|
+
self.reasoning_history = self.reasoning_history[-self.MAX_HISTORY_SIZE:]
|
|
60
|
+
|
|
61
|
+
return routing_result
|
|
62
|
+
|
|
63
|
+
def propose_action(
|
|
64
|
+
self,
|
|
65
|
+
action_id: str,
|
|
66
|
+
parameters: Dict[str, Any],
|
|
67
|
+
context: Dict[str, Any],
|
|
68
|
+
justification: str
|
|
69
|
+
) -> HandshakeSession:
|
|
70
|
+
"""
|
|
71
|
+
Propose an action for execution.
|
|
72
|
+
This initiates the handshake protocol.
|
|
73
|
+
"""
|
|
74
|
+
# Create proposal
|
|
75
|
+
proposal = ActionProposal(
|
|
76
|
+
action_id=action_id,
|
|
77
|
+
parameters=parameters,
|
|
78
|
+
context=context,
|
|
79
|
+
justification=justification
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Initiate handshake
|
|
83
|
+
session = self.protocol.initiate_handshake(proposal)
|
|
84
|
+
|
|
85
|
+
# Validate the proposal against the knowledge graph
|
|
86
|
+
validation_result = self._validate_proposal(proposal)
|
|
87
|
+
|
|
88
|
+
# Update session with validation
|
|
89
|
+
self.protocol.validate_proposal(session.session_id, validation_result)
|
|
90
|
+
|
|
91
|
+
return self.protocol.get_session(session.session_id)
|
|
92
|
+
|
|
93
|
+
def _validate_proposal(self, proposal: ActionProposal) -> ValidationResult:
|
|
94
|
+
"""
|
|
95
|
+
Validate a proposal against the knowledge graph constraints.
|
|
96
|
+
This is the core constraint checking mechanism with deep dependency resolution.
|
|
97
|
+
"""
|
|
98
|
+
errors = []
|
|
99
|
+
warnings = []
|
|
100
|
+
constraints_met = []
|
|
101
|
+
constraints_violated = []
|
|
102
|
+
|
|
103
|
+
# Get routing result for the context
|
|
104
|
+
routing_result = self.router.route(proposal.context)
|
|
105
|
+
|
|
106
|
+
# Check if action exists in pruned action space
|
|
107
|
+
action_available = any(
|
|
108
|
+
node.id == proposal.action_id
|
|
109
|
+
for node in routing_result.pruned_action_space
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
if not action_available:
|
|
113
|
+
errors.append(
|
|
114
|
+
f"Action {proposal.action_id} not available in pruned action space"
|
|
115
|
+
)
|
|
116
|
+
return ValidationResult(
|
|
117
|
+
is_valid=False,
|
|
118
|
+
errors=errors,
|
|
119
|
+
warnings=warnings,
|
|
120
|
+
constraints_met=constraints_met,
|
|
121
|
+
constraints_violated=constraints_violated
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Deep dependency checking - find all missing dependencies across dimensions
|
|
125
|
+
all_missing_deps = self.knowledge_graph.find_all_missing_dependencies(
|
|
126
|
+
proposal.action_id,
|
|
127
|
+
routing_result.selected_dimensions,
|
|
128
|
+
proposal.context
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# If there are missing dependencies, provide detailed error messages
|
|
132
|
+
if all_missing_deps:
|
|
133
|
+
for dim_name, missing_deps in all_missing_deps.items():
|
|
134
|
+
for dep_id in missing_deps:
|
|
135
|
+
errors.append(
|
|
136
|
+
f"Missing dependency '{dep_id}' in dimension '{dim_name}'. "
|
|
137
|
+
f"Please satisfy this requirement first."
|
|
138
|
+
)
|
|
139
|
+
constraints_violated.append(dim_name)
|
|
140
|
+
|
|
141
|
+
# Early return if dependencies are missing - no need for further validation
|
|
142
|
+
is_valid = False
|
|
143
|
+
else:
|
|
144
|
+
# Only validate constraints if no missing dependencies
|
|
145
|
+
for dim_name in routing_result.selected_dimensions:
|
|
146
|
+
# Check if action is valid in this dimension
|
|
147
|
+
subgraph = self.knowledge_graph.get_subgraph(dim_name)
|
|
148
|
+
if not subgraph:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
if not subgraph.validate_action(proposal.action_id, proposal.context):
|
|
152
|
+
if dim_name not in constraints_violated:
|
|
153
|
+
errors.append(
|
|
154
|
+
f"Action {proposal.action_id} fails validation in dimension {dim_name}"
|
|
155
|
+
)
|
|
156
|
+
constraints_violated.append(dim_name)
|
|
157
|
+
else:
|
|
158
|
+
if dim_name not in constraints_violated:
|
|
159
|
+
constraints_met.append(dim_name)
|
|
160
|
+
|
|
161
|
+
# Check specific constraints
|
|
162
|
+
constraints = self.knowledge_graph.get_action_constraints(
|
|
163
|
+
proposal.action_id,
|
|
164
|
+
dim_name
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
for constraint in constraints:
|
|
168
|
+
if not self._check_constraint(constraint, proposal):
|
|
169
|
+
warnings.append(
|
|
170
|
+
f"Constraint {constraint.id} may not be fully satisfied"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
is_valid = len(errors) == 0
|
|
174
|
+
|
|
175
|
+
return ValidationResult(
|
|
176
|
+
is_valid=is_valid,
|
|
177
|
+
errors=errors,
|
|
178
|
+
warnings=warnings,
|
|
179
|
+
constraints_met=constraints_met,
|
|
180
|
+
constraints_violated=constraints_violated
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _check_constraint(
|
|
184
|
+
self,
|
|
185
|
+
constraint: Node,
|
|
186
|
+
proposal: ActionProposal
|
|
187
|
+
) -> bool:
|
|
188
|
+
"""Check if a specific constraint is satisfied."""
|
|
189
|
+
# Check if proposal parameters satisfy constraint attributes
|
|
190
|
+
for key, value in constraint.attributes.items():
|
|
191
|
+
if key in proposal.parameters:
|
|
192
|
+
if proposal.parameters[key] != value:
|
|
193
|
+
return False
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
def select_best_action(
|
|
197
|
+
self,
|
|
198
|
+
context: Dict[str, Any],
|
|
199
|
+
selection_criteria: Optional[Dict[str, Any]] = None
|
|
200
|
+
) -> Optional[Node]:
|
|
201
|
+
"""
|
|
202
|
+
Select the best action from the available action space.
|
|
203
|
+
This can use various selection criteria.
|
|
204
|
+
"""
|
|
205
|
+
routing_result = self.router.route(context)
|
|
206
|
+
|
|
207
|
+
if not routing_result.pruned_action_space:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
# If no criteria specified, return first action
|
|
211
|
+
if not selection_criteria:
|
|
212
|
+
return routing_result.pruned_action_space[0]
|
|
213
|
+
|
|
214
|
+
# Apply selection criteria
|
|
215
|
+
scored_actions = []
|
|
216
|
+
for action in routing_result.pruned_action_space:
|
|
217
|
+
score = self._score_action(action, selection_criteria)
|
|
218
|
+
scored_actions.append((score, action))
|
|
219
|
+
|
|
220
|
+
# Sort by score and return best
|
|
221
|
+
scored_actions.sort(reverse=True, key=lambda x: x[0])
|
|
222
|
+
return scored_actions[0][1] if scored_actions else None
|
|
223
|
+
|
|
224
|
+
def _score_action(
|
|
225
|
+
self,
|
|
226
|
+
action: Node,
|
|
227
|
+
criteria: Dict[str, Any]
|
|
228
|
+
) -> float:
|
|
229
|
+
"""Score an action based on selection criteria."""
|
|
230
|
+
score = 0.0
|
|
231
|
+
|
|
232
|
+
# Match attributes
|
|
233
|
+
for key, value in criteria.items():
|
|
234
|
+
if key in action.attributes:
|
|
235
|
+
if action.attributes[key] == value:
|
|
236
|
+
score += 1.0
|
|
237
|
+
|
|
238
|
+
return score
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Knowledge Graph components."""
|