From 133e9e813d40018c4bdc9819a797d7b05e3857a9 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 21 Jun 2026 18:48:37 -0400 Subject: [PATCH] Neutralize runtime tool policy vocabulary --- README.md | 2 +- docs/runtime-and-tools.md | 2 ++ src/Channels/register-agents-chat-ability.php | 2 +- src/Tools/class-wp-agent-runtime-tool-policy.php | 7 ++++++- tests/agents-chat-ability-smoke.php | 6 +++--- tests/run-result-envelope-smoke.php | 2 +- tests/runtime-tool-policy-smoke.php | 7 +++++-- 7 files changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a79719f..7c3d72b 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Agents API sits between tool/action discovery and product-specific automation. I - Product CLI commands beyond generic substrate needs. - Public REST controllers in v1 unless they are separately designed. - Product runner adapters that assemble prompts, choose concrete tools, materialize storage, or decide product policy. -- Concrete runtime package materialization, package source checkout, sandbox provisioning, provider mapping, run polling, or evidence artifact upload. The package run contract only defines the request/result envelope and dispatcher seam. +- Concrete runtime package materialization, package source checkout, delegated-runtime provisioning, provider mapping, run polling, or evidence artifact upload. The package run contract only defines the request/result envelope and dispatcher seam. - Concrete tool execution adapters, prompt assembly policy, or product storage/materialization policy. - Product-specific consent UX, support routing, escalation targets, or transcript-sharing policy. - Concrete memory retrieval, file projection, convention-path writing, or filesystem layout adapters. diff --git a/docs/runtime-and-tools.md b/docs/runtime-and-tools.md index 27e2712..6267d42 100644 --- a/docs/runtime-and-tools.md +++ b/docs/runtime-and-tools.md @@ -398,6 +398,8 @@ The substrate treats `runtime` as a JSON-friendly associative array. It preserve Delegated runtime consumers should advertise runtime-local tools with `capability_scope: runtime_local` and `environment: runtime_local`. Control-plane tools such as repository mutation, deployment, approval, or parent orchestration actions should use `capability_scope: control_plane` and stay out of runtime-local declarations. Agents API records and propagates this vocabulary; hosts still own the concrete allow/deny policy and execution adapter. +Runtime tool policy envelopes use the canonical schema `agents-api/runtime-tool-policy/v1`. Each projected tool emits neutral top-level `environment` and `capability_scope` fields alongside the nested `runtime` metadata. The compatibility fields `execution_location` and `transport_visibility` may still appear for older consumers; treat them as legacy aliases and prefer `runtime_local`/`control_plane` values from the canonical fields. Policy entries list those aliases in `legacy_fields` when emitted. + Delegated runtimes can also resolve an execution principal with `WP_Agent_Execution_Principal::runtime()`. The helper produces a non-user runtime principal with `auth_source: runtime`, `request_context: runtime`, a host-owned runtime `audience_id`, and an isolated runtime conversation owner key. Hosts remain responsible for attesting the runtime, choosing the owner key, supplying any `runtime_type` claim, and enforcing authorization policy. When the conversation loop mediates tool calls, declaration runtime metadata is propagated into the normalized tool result and exposed on the corresponding `tool_execution_results[]` entry: diff --git a/src/Channels/register-agents-chat-ability.php b/src/Channels/register-agents-chat-ability.php index 593ca28..51b84b5 100644 --- a/src/Channels/register-agents-chat-ability.php +++ b/src/Channels/register-agents-chat-ability.php @@ -355,7 +355,7 @@ function agents_chat_input_schema(): array { ), 'tool_policy' => array( 'type' => array( 'object', 'null' ), - 'description' => 'Optional caller-owned tool policy for this turn. Runtimes may use this to narrow tool visibility for peer-agent or sandbox invocations.', + 'description' => 'Optional caller-owned tool policy for this turn. Runtimes may use this to narrow tool visibility for peer-agent or delegated-runtime invocations.', 'properties' => array( 'mode' => array( 'type' => 'string', diff --git a/src/Tools/class-wp-agent-runtime-tool-policy.php b/src/Tools/class-wp-agent-runtime-tool-policy.php index 120ee88..3e61f25 100644 --- a/src/Tools/class-wp-agent-runtime-tool-policy.php +++ b/src/Tools/class-wp-agent-runtime-tool-policy.php @@ -37,14 +37,19 @@ public static function fromTools( array $tools, array $context = array() ): arra continue; } - $runtime = self::runtimeMetadata( $tool ); + $runtime = self::runtimeMetadata( $tool ); + $environment = $runtime[ WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT ]; + $capability_scope = $runtime[ WP_Agent_Tool_Declaration::RUNTIME_CAPABILITY_SCOPE ]; $policy_tool = array( 'id' => $id, 'runtime_tool_id' => self::runtimeToolId( $id, $tool ), 'allowed' => self::isRuntimeLocal( $runtime ), + 'environment' => $environment, + 'capability_scope' => $capability_scope, 'runtime' => $runtime, 'execution_location' => self::legacyExecutionLocation( $runtime ), 'transport_visibility' => self::legacyTransportVisibility( $runtime ), + 'legacy_fields' => array( 'execution_location', 'transport_visibility' ), ); if ( is_string( $tool['source'] ?? null ) && '' !== $tool['source'] ) { $policy_tool['source'] = $tool['source']; diff --git a/tests/agents-chat-ability-smoke.php b/tests/agents-chat-ability-smoke.php index f7fbaf0..1e0826c 100644 --- a/tests/agents-chat-ability-smoke.php +++ b/tests/agents-chat-ability-smoke.php @@ -188,7 +188,7 @@ function smoke_assert( $expected, $actual, string $name, array &$failures, int & smoke_reset_chat_filters(); $runtime_principal = AgentsAPI\AI\WP_Agent_Execution_Principal::runtime( 'runtime-session-1', - 'sandbox-agent', + 'runtime-local-agent', array( 'source' => 'example-runtime' ), 'workspace:demo', 'example-runtime-cli', @@ -199,7 +199,7 @@ function smoke_assert( $expected, $actual, string $name, array &$failures, int & $captured_principal = is_array( $input['principal'] ?? null ) ? $input['principal'] : array(); return array( 'session_id' => 'runtime-s-1', 'reply' => 'runtime ok', 'completed' => true ); } ); -$runtime_result = agents_chat_dispatch( array( 'agent' => 'sandbox-agent', 'message' => 'go', 'principal' => $runtime_principal ) ); +$runtime_result = agents_chat_dispatch( array( 'agent' => 'runtime-local-agent', 'message' => 'go', 'principal' => $runtime_principal ) ); smoke_assert( 'runtime ok', $runtime_result['reply'] ?? null, 'runtime_principal_dispatch_succeeds', $failures, $passes ); smoke_assert( AgentsAPI\AI\WP_Agent_Execution_Principal::AUTH_SOURCE_RUNTIME, $captured_principal['auth_source'] ?? null, 'runtime_principal_normalized_for_handler', $failures, $passes ); smoke_assert( array( 'type' => AgentsAPI\AI\WP_Agent_Execution_Principal::OWNER_TYPE_RUNTIME, 'key' => 'runtime-session-1' ), AgentsAPI\AI\WP_Agent_Execution_Principal::from_array( $captured_principal )->conversation_owner(), 'runtime_principal_preserves_owner', $failures, $passes ); @@ -211,7 +211,7 @@ function smoke_assert( $expected, $actual, string $name, array &$failures, int & }, 10, 2 ); smoke_assert( true, agents_chat_permission( array( 'principal' => $runtime_principal ) ), 'runtime_principal_permission_filter_allows', $failures, $passes ); -$invalid_principal_result = agents_chat_dispatch( array( 'agent' => 'sandbox-agent', 'message' => 'go', 'principal' => 'not-object' ) ); +$invalid_principal_result = agents_chat_dispatch( array( 'agent' => 'runtime-local-agent', 'message' => 'go', 'principal' => 'not-object' ) ); smoke_assert( true, $invalid_principal_result instanceof WP_Error, 'invalid_principal_returns_wp_error', $failures, $passes ); smoke_assert( 'agents_chat_invalid_principal', $invalid_principal_result->get_error_code(), 'invalid_principal_error_code', $failures, $passes ); diff --git a/tests/run-result-envelope-smoke.php b/tests/run-result-envelope-smoke.php index 1085bdc..49bd6d2 100644 --- a/tests/run-result-envelope-smoke.php +++ b/tests/run-result-envelope-smoke.php @@ -67,7 +67,7 @@ 'artifact_refs' => array( array( 'type' => 'package', 'label' => 'bundle' ) ), 'evidence_refs' => array( array( 'type' => 'trace', 'label' => 'trace' ) ), 'replay' => array( 'recipe' => 'site' ), - 'metadata' => array( 'runtime' => 'codebox' ), + 'metadata' => array( 'runtime' => 'runtime_local' ), ) ); $runtime_array = $runtime->to_array(); diff --git a/tests/runtime-tool-policy-smoke.php b/tests/runtime-tool-policy-smoke.php index de2be9f..b32a509 100644 --- a/tests/runtime-tool-policy-smoke.php +++ b/tests/runtime-tool-policy-smoke.php @@ -83,11 +83,14 @@ agents_api_smoke_assert_equals( true, $by_id['filesystem-write']['allowed'] ?? false, 'runtime-local tool is allowed', $failures, $passes ); agents_api_smoke_assert_equals( 'filesystem_write', $by_id['filesystem-write']['runtime_tool_id'] ?? '', 'runtime tool id can be declared explicitly', $failures, $passes ); +agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::ENVIRONMENT_RUNTIME_LOCAL, $by_id['filesystem-write'][ WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT ] ?? '', 'runtime-local tool emits canonical execution environment', $failures, $passes ); +agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::CAPABILITY_SCOPE_RUNTIME_LOCAL, $by_id['filesystem-write'][ WP_Agent_Tool_Declaration::RUNTIME_CAPABILITY_SCOPE ] ?? '', 'runtime-local tool emits canonical capability scope', $failures, $passes ); agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::ENVIRONMENT_RUNTIME_LOCAL, $by_id['filesystem-write']['runtime'][ WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT ] ?? '', 'runtime-local tool records execution environment', $failures, $passes ); -agents_api_smoke_assert_equals( 'sandbox', $by_id['filesystem-write']['execution_location'] ?? '', 'runtime-local tool projects legacy sandbox location for consumers', $failures, $passes ); +agents_api_smoke_assert_equals( array( 'execution_location', 'transport_visibility' ), $by_id['filesystem-write']['legacy_fields'] ?? array(), 'runtime-local tool marks compatibility-only fields as legacy', $failures, $passes ); agents_api_smoke_assert_equals( false, $by_id['control-plane/workspace-git-push']['allowed'] ?? true, 'control-plane tool is denied to runtime-local agent', $failures, $passes ); -agents_api_smoke_assert_equals( 'parent', $by_id['control-plane/workspace-git-push']['transport_visibility'] ?? '', 'control-plane tool projects parent visibility', $failures, $passes ); +agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::ENVIRONMENT_CONTROL_PLANE, $by_id['control-plane/workspace-git-push'][ WP_Agent_Tool_Declaration::RUNTIME_ENVIRONMENT ] ?? '', 'control-plane tool emits canonical execution environment', $failures, $passes ); +agents_api_smoke_assert_equals( WP_Agent_Tool_Declaration::CAPABILITY_SCOPE_CONTROL_PLANE, $by_id['control-plane/workspace-git-push'][ WP_Agent_Tool_Declaration::RUNTIME_CAPABILITY_SCOPE ] ?? '', 'control-plane tool emits canonical capability scope', $failures, $passes ); agents_api_smoke_assert_equals( 'control_plane_workspace_read', $by_id['control-plane/workspace-read']['runtime_tool_id'] ?? '', 'runtime tool id defaults from tool name', $failures, $passes ); agents_api_smoke_assert_equals( false, $by_id['control-plane/workspace-read']['allowed'] ?? true, 'tools without runtime metadata default closed', $failures, $passes );