Skip to content

Commit 1595419

Browse files
committed
feat(consent): add agent consent policy contracts
1 parent 15e4e0d commit 1595419

9 files changed

Lines changed: 568 additions & 0 deletions

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Agents API sits between tool/action discovery and product-specific automation. I
3030
- Iteration budget primitives for bounded execution across configurable dimensions.
3131
- Tool-call mediation contracts and runtime tool declaration value objects.
3232
- Conversation transcript store contracts.
33+
- Consent policy contracts for memory, transcripts, sharing, and escalation.
3334
- Tool source registration, parameter normalization, tool-call mediation, and execution result contracts.
3435
- Session and persistence contracts where they are provider-neutral.
3536

@@ -42,6 +43,7 @@ Agents API sits between tool/action discovery and product-specific automation. I
4243
- Public REST controllers in v1 unless they are separately designed.
4344
- Product runner adapters that assemble prompts, choose concrete tools, materialize storage, or decide product policy.
4445
- Concrete tool execution adapters, prompt assembly policy, or product storage/materialization policy.
46+
- Product-specific consent UX, support routing, escalation targets, or transcript-sharing policy.
4547

4648
Products can require Agents API because they build on the substrate. Agents API must not depend on any product plugin, import product classes, mirror a product source tree, or encode product vocabulary as generic runtime API.
4749

@@ -107,6 +109,10 @@ wp_register_agent(
107109
- `AgentsAPI\AI\IterationBudget`
108110
- `AgentsAPI\AI\AgentConversationResult`
109111
- `AgentsAPI\AI\AgentConversationLoop`
112+
- `WP_Agent_Consent_Policy_Interface`
113+
- `WP_Agent_Default_Consent_Policy`
114+
- `AgentsAPI\AI\Consent\AgentConsentOperation`
115+
- `AgentsAPI\AI\Consent\AgentConsentDecision`
110116
- `AgentsAPI\AI\Tools\RuntimeToolDeclaration`
111117
- `AgentsAPI\AI\Tools\ToolCall`
112118
- `AgentsAPI\AI\Tools\ToolSourceRegistry`
@@ -142,6 +148,42 @@ add_filter(
142148
);
143149
```
144150

151+
## Consent Policy Boundary
152+
153+
Agents API owns a generic consent contract for runtime operations that carry different user expectations:
154+
155+
- `store_memory` — store consolidated agent memory.
156+
- `use_memory` — use existing agent memory during a run.
157+
- `store_transcript` — store a raw conversation transcript.
158+
- `share_transcript` — share a raw transcript outside its owning context.
159+
- `escalate_to_human` — escalate a run or transcript to a human/support adapter.
160+
161+
Memory consent and transcript consent are intentionally separate. Allowing an agent to store or use consolidated memory does not imply consent to persist or share raw transcripts. Escalation is also separate so support-mode adapters can ask their own product questions without adding support-product logic to Agents API.
162+
163+
Policies implement `WP_Agent_Consent_Policy_Interface` and return `AgentConsentDecision` values with `allowed`, `operation`, `reason`, and `audit_metadata` fields. Consumers should store those decision arrays alongside any memory write, transcript persistence/share event, or escalation event they apply.
164+
165+
```php
166+
$policy = new WP_Agent_Default_Consent_Policy();
167+
168+
$decision = $policy->can_store_transcript(
169+
array(
170+
'mode' => 'chat',
171+
'user_id' => get_current_user_id(),
172+
'agent_id' => 'example-agent',
173+
'consent' => array(
174+
'store_transcript' => true,
175+
),
176+
)
177+
);
178+
179+
if ( $decision->is_allowed() ) {
180+
$transcript_id = $transcript_persister->persist( $messages, $request, $result );
181+
$audit_store->record( $decision->to_array() + array( 'transcript_id' => $transcript_id ) );
182+
}
183+
```
184+
185+
`WP_Agent_Default_Consent_Policy` is conservative: non-interactive modes are denied by default, and interactive modes still require explicit per-operation consent. Products can supply adapter-specific policies for their own UX, authorization ceilings, support routing, retention rules, and audit stores.
186+
145187
## Conversation Compaction
146188

147189
Agents can declare support for runtime conversation compaction without tying Agents API to a provider or model executor:

agents-api.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
require_once AGENTS_API_PATH . 'src/Approvals/ApprovalDecision.php';
4646
require_once AGENTS_API_PATH . 'src/Approvals/PendingActionHandlerInterface.php';
4747
require_once AGENTS_API_PATH . 'src/Approvals/PendingActionResolverInterface.php';
48+
require_once AGENTS_API_PATH . 'src/Consent/AgentConsentOperation.php';
49+
require_once AGENTS_API_PATH . 'src/Consent/AgentConsentDecision.php';
50+
require_once AGENTS_API_PATH . 'src/Consent/class-wp-agent-consent-policy-interface.php';
51+
require_once AGENTS_API_PATH . 'src/Consent/class-wp-agent-default-consent-policy.php';
4852
require_once AGENTS_API_PATH . 'src/Runtime/AgentMessageEnvelope.php';
4953
require_once AGENTS_API_PATH . 'src/Runtime/AgentExecutionPrincipal.php';
5054
require_once AGENTS_API_PATH . 'src/Runtime/AgentCompactionItem.php';

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"php tests/registry-smoke.php",
1919
"php tests/execution-principal-smoke.php",
2020
"php tests/action-policy-values-smoke.php",
21+
"php tests/consent-policy-smoke.php",
2122
"php tests/tool-runtime-smoke.php",
2223
"php tests/pending-action-store-contract-smoke.php",
2324
"php tests/approval-resolver-contract-smoke.php",
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
/**
3+
* Agent consent decision value object.
4+
*
5+
* @package AgentsAPI
6+
*/
7+
8+
namespace AgentsAPI\AI\Consent;
9+
10+
defined( 'ABSPATH' ) || exit;
11+
12+
/**
13+
* Represents a consent policy result with audit-safe metadata.
14+
*/
15+
final class AgentConsentDecision {
16+
17+
/** @var bool */
18+
private $allowed;
19+
20+
/** @var string */
21+
private $operation;
22+
23+
/** @var string */
24+
private $reason;
25+
26+
/** @var array */
27+
private $audit_metadata;
28+
29+
/**
30+
* @param bool $allowed Whether the operation is allowed.
31+
* @param string $operation Consent operation value.
32+
* @param string $reason Stable reason code.
33+
* @param array $audit_metadata JSON-friendly audit metadata.
34+
*/
35+
private function __construct( bool $allowed, string $operation, string $reason, array $audit_metadata = array() ) {
36+
$normalized_operation = AgentConsentOperation::normalize( $operation );
37+
38+
$this->allowed = $allowed;
39+
$this->operation = null === $normalized_operation ? '' : $normalized_operation;
40+
$this->reason = self::normalize_key( $reason );
41+
$this->audit_metadata = self::normalize_metadata( $audit_metadata );
42+
}
43+
44+
/**
45+
* Build an allowed decision.
46+
*
47+
* @param string $operation Consent operation value.
48+
* @param string $reason Stable reason code.
49+
* @param array $audit_metadata JSON-friendly audit metadata.
50+
* @return self
51+
*/
52+
public static function allowed( string $operation, string $reason = 'allowed', array $audit_metadata = array() ): self {
53+
return new self( true, $operation, $reason, $audit_metadata );
54+
}
55+
56+
/**
57+
* Build a denied decision.
58+
*
59+
* @param string $operation Consent operation value.
60+
* @param string $reason Stable reason code.
61+
* @param array $audit_metadata JSON-friendly audit metadata.
62+
* @return self
63+
*/
64+
public static function denied( string $operation, string $reason = 'denied', array $audit_metadata = array() ): self {
65+
return new self( false, $operation, $reason, $audit_metadata );
66+
}
67+
68+
/**
69+
* Whether the operation is allowed.
70+
*
71+
* @return bool
72+
*/
73+
public function is_allowed(): bool {
74+
return $this->allowed;
75+
}
76+
77+
/**
78+
* Consent operation value.
79+
*
80+
* @return string
81+
*/
82+
public function operation(): string {
83+
return $this->operation;
84+
}
85+
86+
/**
87+
* Stable reason code.
88+
*
89+
* @return string
90+
*/
91+
public function reason(): string {
92+
return $this->reason;
93+
}
94+
95+
/**
96+
* JSON-friendly audit metadata.
97+
*
98+
* @return array
99+
*/
100+
public function audit_metadata(): array {
101+
return $this->audit_metadata;
102+
}
103+
104+
/**
105+
* Return JSON-friendly shape.
106+
*
107+
* @return array
108+
*/
109+
public function to_array(): array {
110+
return array(
111+
'allowed' => $this->allowed,
112+
'operation' => $this->operation,
113+
'reason' => $this->reason,
114+
'audit_metadata' => $this->audit_metadata,
115+
);
116+
}
117+
118+
/**
119+
* Normalize arbitrary metadata to JSON-friendly scalar/array values.
120+
*
121+
* @param array $metadata Raw metadata.
122+
* @return array
123+
*/
124+
private static function normalize_metadata( array $metadata ): array {
125+
$normalized = array();
126+
127+
foreach ( $metadata as $key => $value ) {
128+
if ( is_scalar( $value ) || null === $value ) {
129+
$normalized[ self::normalize_key( (string) $key ) ] = $value;
130+
} elseif ( is_array( $value ) ) {
131+
$normalized[ self::normalize_key( (string) $key ) ] = self::normalize_metadata( $value );
132+
}
133+
}
134+
135+
return $normalized;
136+
}
137+
138+
/**
139+
* Normalize a string to a stable machine key without requiring WordPress helpers.
140+
*
141+
* @param string $value Raw key.
142+
* @return string
143+
*/
144+
private static function normalize_key( string $value ): string {
145+
$value = strtolower( $value );
146+
$value = preg_replace( '/[^a-z0-9_\-]+/', '_', $value );
147+
148+
return trim( is_string( $value ) ? $value : '', '_-' );
149+
}
150+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
/**
3+
* Agent consent operation vocabulary.
4+
*
5+
* @package AgentsAPI
6+
*/
7+
8+
namespace AgentsAPI\AI\Consent;
9+
10+
defined( 'ABSPATH' ) || exit;
11+
12+
/**
13+
* Normalizes generic consent operation names.
14+
*/
15+
final class AgentConsentOperation {
16+
17+
/** Store consolidated agent memory. */
18+
public const STORE_MEMORY = 'store_memory';
19+
20+
/** Use existing agent memory during a run. */
21+
public const USE_MEMORY = 'use_memory';
22+
23+
/** Store a raw conversation transcript. */
24+
public const STORE_TRANSCRIPT = 'store_transcript';
25+
26+
/** Share a raw conversation transcript outside its owning context. */
27+
public const SHARE_TRANSCRIPT = 'share_transcript';
28+
29+
/** Escalate a run or transcript to a human/support adapter. */
30+
public const ESCALATE_TO_HUMAN = 'escalate_to_human';
31+
32+
/**
33+
* Return all valid operation values.
34+
*
35+
* @return string[]
36+
*/
37+
public static function all(): array {
38+
return array(
39+
self::STORE_MEMORY,
40+
self::USE_MEMORY,
41+
self::STORE_TRANSCRIPT,
42+
self::SHARE_TRANSCRIPT,
43+
self::ESCALATE_TO_HUMAN,
44+
);
45+
}
46+
47+
/**
48+
* Determine whether a raw value is a recognized operation.
49+
*
50+
* @param mixed $value Raw operation value.
51+
* @return bool
52+
*/
53+
public static function isValid( $value ): bool {
54+
return null !== self::normalize( $value );
55+
}
56+
57+
/**
58+
* Normalize a raw operation value.
59+
*
60+
* @param mixed $value Raw operation value.
61+
* @param string|null $fallback Optional fallback used when value is invalid.
62+
* @return string|null One of the operation constants, or null when invalid.
63+
*/
64+
public static function normalize( $value, ?string $fallback = null ): ?string {
65+
if ( is_string( $value ) ) {
66+
$normalized = strtolower( trim( $value ) );
67+
if ( in_array( $normalized, self::all(), true ) ) {
68+
return $normalized;
69+
}
70+
}
71+
72+
if ( null === $fallback ) {
73+
return null;
74+
}
75+
76+
return self::normalize( $fallback );
77+
}
78+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Agent consent policy interface.
4+
*
5+
* @package AgentsAPI
6+
*/
7+
8+
use AgentsAPI\AI\Consent\AgentConsentDecision;
9+
10+
defined( 'ABSPATH' ) || exit;
11+
12+
/**
13+
* Generic consent policy contract for agent memory, transcripts, and escalation.
14+
*/
15+
interface WP_Agent_Consent_Policy_Interface {
16+
17+
/**
18+
* Whether consolidated agent memory may be stored.
19+
*
20+
* @param array $context JSON-friendly request, principal, adapter, and UX context.
21+
* @return AgentConsentDecision
22+
*/
23+
public function can_store_memory( array $context = array() ): AgentConsentDecision;
24+
25+
/**
26+
* Whether existing agent memory may be used for a run.
27+
*
28+
* @param array $context JSON-friendly request, principal, adapter, and UX context.
29+
* @return AgentConsentDecision
30+
*/
31+
public function can_use_memory( array $context = array() ): AgentConsentDecision;
32+
33+
/**
34+
* Whether a raw conversation transcript may be stored.
35+
*
36+
* @param array $context JSON-friendly request, principal, adapter, and UX context.
37+
* @return AgentConsentDecision
38+
*/
39+
public function can_store_transcript( array $context = array() ): AgentConsentDecision;
40+
41+
/**
42+
* Whether a raw conversation transcript may be shared outside its owning context.
43+
*
44+
* @param array $context JSON-friendly request, principal, adapter, and UX context.
45+
* @return AgentConsentDecision
46+
*/
47+
public function can_share_transcript( array $context = array() ): AgentConsentDecision;
48+
49+
/**
50+
* Whether a run or transcript may be escalated to a human/support adapter.
51+
*
52+
* @param array $context JSON-friendly request, principal, adapter, and UX context.
53+
* @return AgentConsentDecision
54+
*/
55+
public function can_escalate_to_human( array $context = array() ): AgentConsentDecision;
56+
}

0 commit comments

Comments
 (0)