Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion docs/auth-consent-context-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,40 @@ Missing headers produce a top-of-chain context. Malformed headers throw `Invalid

## Execution principals and workspace scope

`AgentsAPI\AI\WP_Agent_Execution_Principal` represents one runtime actor: acting user id, effective agent id/slug, auth source, request context, optional token id, workspace id, client id, capability ceiling, caller context, and JSON-friendly metadata.
`AgentsAPI\AI\WP_Agent_Execution_Principal` represents one runtime actor: acting user id, effective agent id/slug, auth source, request context, optional token id, workspace id, client id, capability ceiling, caller context, non-user audience id/claims, optional transcript owner, and JSON-friendly metadata.

Generic principal fields:

| Field | Purpose |
| --- | --- |
| `acting_user_id` | WordPress user ID on whose behalf execution happens, or `0` for non-user/runtime/system principals. Hosts still enforce capabilities with WordPress-native auth, caps, and REST permission callbacks. |
| `effective_agent_id` | Registered agent id/slug effective for this run. This is routing identity, not a permission grant by itself. |
| `auth_source` | Product-neutral source such as `user`, `application_password`, `agent_token`, `audience`, `runtime`, or `system`; hosts may register additional sources with `wp_agent_known_auth_sources`. |
| `request_context` | Entry context such as `rest`, `cli`, `cron`, `chat`, or `runtime`. |
| `workspace_id` | Host-owned scope id for persistence, tool policy, audit, or result filtering. |
| `client_id` | Host/client surface identifier, such as a frontend, bridge, runtime, or API client. |
| `audience_id` / `audience_claims` | Host-resolved non-user audience context. Claims are private runtime/audit data, not a display contract. |
| `owner_type` / `owner_key` | Optional transcript/session owner. Non-user principals must use opaque host-owned owner keys; audience access alone is not a transcript owner. |
| `capability_ceiling` | Optional ceiling intersected by authorization policy with WordPress capabilities. |
| `caller_context` | Cross-agent/cross-host chain context for delegation and loop prevention. |
| `request_metadata` / `binding` | Private host audit/runtime data. These fields are not safe citation or frontend metadata. |

Permission-aware tools and retrieval surfaces should attach only safe principal metadata to user-visible citations, diagnostics, or frontend result objects. `to_safe_metadata()` returns the generic safe shape: schema version, effective agent, auth source, request context, acting user id, workspace id, client id, audience id, owner type, and boolean flags for conversation-owner/capability/caller-context presence. It intentionally omits token ids, owner keys, request metadata, audience claims, capability details, and cryptographic binding claims.

Hosts that need richer audit trails should persist the full `to_array()` shape in private storage they control. Frontend clients, citations, and source diagnostics should use the safe metadata shape plus result-level status fields instead of receiving raw credentials, tokens, opaque session ids, or authorization internals.

Client context is caller-owned runtime context carried on conversation requests and frontend channel payloads. It may describe selected UI state, host context, explicit routing hints, or opaque client metadata, but Agents API does not infer tool arguments from it. Tool declarations must opt in with `client_context_bindings`, either as `array( 'parameter_name' )` or `array( 'parameter_name' => 'context_key' )`. Sensitive ambient keys such as `api_key`, `token`, `authorization`, `cookie`, `nonce`, or `password` must be passed only through explicit bindings/defaults and host-owned authorization policy; matching key names alone never satisfies required parameters.

Permission-aware retrieval results should use product-neutral status vocabulary so frontend clients can explain restricted output without learning product-specific policy:

| Status | Meaning |
| --- | --- |
| `allowed` | The source/result was available to the current principal. |
| `denied` | The source/result exists but the current principal cannot access it. |
| `partial` | Some matching items were withheld or redacted. |
| `source_restricted` | The source itself is unavailable in the current workspace/client/principal context. |

Result and citation metadata may include `access_status`, `restriction_reason`, `source`, `source_id`, `item_id`, `fragment_id`, `source_title`, `source_url`, `score`, `excerpt`, and `principal` using `to_safe_metadata()`. Concrete auth checks remain downstream: WordPress users, roles, capabilities, REST permission callbacks, and host policy decide access; Agents API only defines the transportable, product-neutral context shape.

`AgentsAPI\Core\Workspace\WP_Agent_Workspace_Scope` is the generic workspace identity shared by memory, transcripts, persistence, and audit adapters. It is deliberately `(workspace_type, workspace_id)` rather than a WordPress site id so consumers can map sites, networks, code workspaces, pull requests, or ephemeral environments without changing generic contracts.

Expand Down
12 changes: 12 additions & 0 deletions docs/runtime-and-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,18 @@ If a concrete executor returns its own `runtime` metadata, the normalized result

Retrieval tools place canonical citations in result metadata under `metadata['citations']`. The substrate citation shape is intentionally small and generic: each citation may include `source`, `source_id`, `item_id`, `fragment_id`, `source_title`, `source_url`, `score`, and `excerpt`. Agents API normalizes this citation list for mediated tool results and delegated runtime tool results while preserving unrelated caller-owned metadata and additional caller-owned fields inside each citation.

Permission-aware citations and source diagnostics may add product-neutral access metadata:

| Key | Meaning |
| --- | --- |
| `access_status` | `allowed`, `denied`, `partial`, or `source_restricted`. |
| `restriction_reason` | Optional generic reason such as `capability`, `workspace`, `audience`, `source_policy`, or `redacted`. |
| `principal` | Optional safe principal metadata from `WP_Agent_Execution_Principal::to_safe_metadata()`. |

Do not attach raw request metadata, token ids, owner keys, audience claims, capability lists, cookies, nonces, credentials, or cryptographic binding claims to citation metadata. If a host needs those details for audits, it should write a private audit record and expose only safe principal/source metadata to model-visible or frontend-visible result surfaces.

Frontend clients should treat citation access metadata as explanation and display context, not as authorization. Downstream WordPress auth, capabilities, REST permission callbacks, source adapters, and host policy remain the enforcement points.

## Tool execution core

`WP_Agent_Tool_Execution_Core` mediates calls without owning any concrete tool implementation.
Expand Down
30 changes: 30 additions & 0 deletions src/Runtime/class-wp-agent-execution-principal.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,36 @@ public function to_array(): array {
);
}

/**
* Export the safe principal metadata shape for citations and diagnostics.
*
* This intentionally omits request metadata, token ids, audience claims,
* capability details, owner keys, and binding claims. Those fields may carry
* credentials, opaque session ids, or host-specific authorization material.
* Hosts that need richer audit data should persist it in a private audit log
* rather than attaching it to user-visible citations or tool diagnostics.
*
* @return array<string, mixed>
*/
public function to_safe_metadata(): array {
$owner = $this->conversation_owner();

return array(
'schema_version' => 1,
'effective_agent_id' => $this->effective_agent_id,
'auth_source' => $this->auth_source,
'request_context' => $this->request_context,
'acting_user_id' => $this->acting_user_id,
'workspace_id' => $this->workspace_id,
'client_id' => $this->client_id,
'audience_id' => $this->audience_id,
'owner_type' => is_array( $owner ) ? $owner['type'] : null,
'has_conversation_owner' => is_array( $owner ),
'has_capability_ceiling' => $this->capability_ceiling instanceof \WP_Agent_Capability_Ceiling,
'has_caller_context' => $this->caller_context instanceof \WP_Agent_Caller_Context,
);
}

/**
* Return host-owned cryptographic binding claims for this principal.
*
Expand Down
15 changes: 15 additions & 0 deletions tests/execution-principal-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@
agents_api_smoke_assert_equals( array( 'edit_posts' ), $principal_array['capability_ceiling']['allowed_capabilities'], 'principal exports capability ceiling', $failures, $passes );
agents_api_smoke_assert_equals( array( 'type' => AgentsAPI\AI\WP_Agent_Execution_Principal::OWNER_TYPE_TOKEN, 'key' => '456' ), $principal->conversation_owner(), 'agent token derives token conversation owner', $failures, $passes );

$safe_metadata = $principal->to_safe_metadata();
agents_api_smoke_assert_equals( 1, $safe_metadata['schema_version'], 'safe metadata declares schema version', $failures, $passes );
agents_api_smoke_assert_equals( 'content-helper', $safe_metadata['effective_agent_id'], 'safe metadata includes effective agent id', $failures, $passes );
agents_api_smoke_assert_equals( AgentsAPI\AI\WP_Agent_Execution_Principal::AUTH_SOURCE_AGENT_TOKEN, $safe_metadata['auth_source'], 'safe metadata includes auth source', $failures, $passes );
agents_api_smoke_assert_equals( 'site:42', $safe_metadata['workspace_id'], 'safe metadata includes workspace id', $failures, $passes );
agents_api_smoke_assert_equals( 'kimaki', $safe_metadata['client_id'], 'safe metadata includes client id', $failures, $passes );
agents_api_smoke_assert_equals( AgentsAPI\AI\WP_Agent_Execution_Principal::OWNER_TYPE_TOKEN, $safe_metadata['owner_type'], 'safe metadata includes owner type without owner key', $failures, $passes );
agents_api_smoke_assert_equals( true, $safe_metadata['has_conversation_owner'], 'safe metadata reports conversation owner presence', $failures, $passes );
agents_api_smoke_assert_equals( true, $safe_metadata['has_capability_ceiling'], 'safe metadata reports capability ceiling presence', $failures, $passes );
agents_api_smoke_assert_equals( false, array_key_exists( 'token_id', $safe_metadata ), 'safe metadata omits token id', $failures, $passes );
agents_api_smoke_assert_equals( false, array_key_exists( 'request_metadata', $safe_metadata ), 'safe metadata omits request metadata', $failures, $passes );
agents_api_smoke_assert_equals( false, array_key_exists( 'owner_key', $safe_metadata ), 'safe metadata omits owner key', $failures, $passes );
agents_api_smoke_assert_equals( false, array_key_exists( 'capability_ceiling', $safe_metadata ), 'safe metadata omits capability details', $failures, $passes );
agents_api_smoke_assert_equals( false, array_key_exists( 'binding', $safe_metadata ), 'safe metadata omits binding claims', $failures, $passes );

$from_array = AgentsAPI\AI\WP_Agent_Execution_Principal::from_array(
array(
'acting_user_id' => '7',
Expand Down
Loading