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
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"php tests/canonical-run-lifecycle-smoke.php",
"php tests/channels-smoke.php",
"php tests/chat-run-control-smoke.php",
"php tests/external-chat-run-bridge-fixture-smoke.php",
"php tests/task-execution-smoke.php",
"php tests/frontend-chat-rest-smoke.php",
"php tests/agents-chat-jsonrpc-route-smoke.php",
Expand Down
93 changes: 91 additions & 2 deletions docs/channels-workflows-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ Agents API owns the generic run-control ability contracts and default run-contro
| --- | --- | --- |
| `agents/get-chat-run` | Return status for a known chat run. | `wp_agent_chat_run_status_handler` |
| `agents/cancel-chat-run` | Request best-effort cancellation for a known chat run. | `wp_agent_chat_run_cancel_handler` |
| `agents/list-chat-run-events` | Return lifecycle/event pages for a known chat run. | `wp_agent_chat_run_events_handler` |
| `agents/queue-chat-message` | Accept a next user message while a session has an active run. | `wp_agent_chat_message_queue_handler` |

Clients can expose status, Stop, and Queue controls whenever these canonical abilities are available and the caller has permission for the selected agent. The default handlers preserve safe behavior for synchronous runtimes; runtime-specific handlers are enhancements, not prerequisites.

Run status vocabulary is bounded to `queued`, `running`, `cancelling`, `cancelled`, `completed`, and `failed`. The canonical run payload is:
Run status vocabulary is bounded to `queued`, `running`, `cancelling`, `cancelled`, `completed`, `failed`, `runtime_tool_pending`, `approval_required`, `budget_exceeded`, `stalled`, and `interrupted`. The canonical run payload is:

```php
array(
Expand All @@ -85,10 +86,98 @@ array(
'status' => 'running',
'started_at' => '2026-01-01T00:00:00Z',
'updated_at' => '2026-01-01T00:00:01Z',
'metadata' => array(),
'metadata' => array(
'orchestration' => array(
'provider' => 'external-provider-id',
'run_id' => 'provider-run-id',
'event_cursor' => 'provider-event-cursor',
),
),
)
```

The status, events, and cancel hooks are the generic external durable-run adapter contract. A host can back them with any durable run provider by returning the canonical payloads above; Agents API normalizes status values, validates required identifiers, applies the existing permission callbacks, and keeps provider-specific state inside metadata. The canonical metadata object is `metadata['orchestration']` with `provider` for the adapter/provider id, `run_id` for the durable provider run id, and `event_cursor` for the provider cursor associated with the latest status or event page. These keys are intentionally generic and do not imply a specific runner, queue, or product.

Event handlers return the same run fields plus an `events` list, `cursor`, and `has_more`. Each event uses `id`, `type`, `created_at`, optional `message`, and opaque `metadata`. The returned `cursor` is the client polling cursor for the next `agents/list-chat-run-events` call; adapters can mirror a provider-native cursor in `metadata['orchestration']['event_cursor']` when the provider cursor differs from the public cursor.

### External durable-run bridge

An external orchestration bridge should call its own in-process adapter or service client for its provider-native durable run status, then return the canonical Agents API run-control shape. Agents API does not shell out to an orchestrator and does not require any orchestrator package dependency; the bridge is just a handler behind `wp_agent_chat_run_status_handler` and `wp_agent_chat_run_events_handler`.

Mapping guidance:

| External status field | Agents API field |
| --- | --- |
| `run.state` | `status`, mapped into the bounded Agents API vocabulary: `queued`, `running`, `cancelling`, `cancelled`, `completed`, `failed`, `stalled`, or `interrupted`. |
| `run.id` | `metadata.orchestration.run_id`; keep the client-addressable chat `run_id` unchanged at the top level. |
| `totals` | `metadata.orchestration.totals`. |
| `latest_event_cursor` | `cursor` on event pages and `metadata.orchestration.event_cursor` on status/event payloads. |
| `normalized_events` | `events`, with each item using canonical `id`, `type`, `created_at`, `message`, and `metadata`. |
| `artifact_refs` | `metadata.orchestration.artifact_refs`. |

Example provider-native input shape:

```php
array(
'schema' => 'example-orchestrator/run-status/v1',
'run' => array(
'id' => 'external-run-1',
'state' => 'succeeded',
'started_at' => '2026-06-25T12:00:00Z',
'updated_at' => '2026-06-25T12:03:00Z',
),
'totals' => array(
'events' => 2,
'artifacts' => 1,
'errors' => 0,
),
'latest_event_cursor' => 'provider-cursor-2',
'normalized_events' => array(
array(
'id' => 'provider-event-2',
'cursor' => 'provider-cursor-2',
'type' => 'artifact',
'created_at' => '2026-06-25T12:03:00Z',
'message' => 'Recorded transcript artifact.',
),
),
'artifact_refs' => array(
array(
'id' => 'artifact-transcript',
'type' => 'transcript',
'label' => 'Transcript',
),
),
)
```

Canonical `agents/get-chat-run` output:

```php
array(
'run_id' => 'run-chat-1',
'session_id' => 'session-external-1',
'status' => 'completed',
'started_at' => '2026-06-25T12:00:00Z',
'updated_at' => '2026-06-25T12:03:00Z',
'metadata' => array(
'orchestration' => array(
'provider' => 'example-orchestrator',
'schema' => 'example-orchestrator/run-status/v1',
'run_id' => 'external-run-1',
'state' => 'succeeded',
'event_cursor' => 'provider-cursor-2',
'totals' => array( 'events' => 2, 'artifacts' => 1, 'errors' => 0 ),
'artifact_refs' => array(
array( 'id' => 'artifact-transcript', 'type' => 'transcript', 'label' => 'Transcript' ),
),
),
),
)
```

Canonical `agents/list-chat-run-events` output uses the same run fields, returns provider events mapped into the canonical event shape, sets `cursor` to the latest provider cursor, and repeats that cursor in `metadata.orchestration.event_cursor` when useful. Cancellation stays handler-owned: register `wp_agent_chat_run_cancel_handler` only when the bridge can request cancellation through its durable provider; otherwise Agents API keeps the default local cancellation behavior.

Cancellation is best-effort. A runtime that can abort provider work immediately may do so; a runtime that cannot should mark the run `cancelling` and let its conversation loop stop at the next interrupt check. `WP_Agent_Chat_Run_Control::cancellation_interrupt_message()` builds the message shape expected by `WP_Agent_Conversation_Loop` `interrupt_source` callbacks.

Read abilities return observer-safe run envelopes for non-operators. Stored run state may contain runtime diagnostics, package/workflow selectors, raw refs, provenance, output, or caller metadata for audit/debugging, but `agents/get-chat-run`, `agents/list-chat-run-events`, `agents/get-task-run`, `agents/get-runtime-package-run`, and `agents/list-runtime-package-run-events` redact high-risk keys unless the caller passes the explicit unredacted read gate. Managers keep full access by default; hosts can extend that path with `agents_chat_run_unredacted_read_permission`, `agents_task_unredacted_read_permission`, or `agents_runtime_package_run_unredacted_read_permission` while leaving broad read access observer-safe.
Expand Down
14 changes: 12 additions & 2 deletions src/Channels/register-agents-chat-run-control-abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,10 @@ function agents_chat_run_output_schema(): array {
),
'started_at' => array( 'type' => 'string' ),
'updated_at' => array( 'type' => 'string' ),
'metadata' => array( 'type' => 'object' ),
'metadata' => array(
'type' => 'object',
'description' => 'Opaque run metadata. External durable-run adapters should use orchestration.provider, orchestration.run_id, and orchestration.event_cursor for provider identity, provider run identity, and latest event cursor.',
),
),
);
}
Expand Down Expand Up @@ -486,8 +489,15 @@ function agents_chat_run_events_output_schema(): array {
),
),
),
'cursor' => array( 'type' => 'string' ),
'cursor' => array(
'type' => 'string',
'description' => 'Opaque cursor for the next events page. External durable-run adapters should also mirror the latest durable cursor in metadata.orchestration.event_cursor when useful for status polling.',
),
'has_more' => array( 'type' => 'boolean' ),
'metadata' => array(
'type' => 'object',
'description' => 'Opaque event-page metadata using the same orchestration.provider, orchestration.run_id, and orchestration.event_cursor convention as run status payloads.',
),
),
);
}
Expand Down
35 changes: 31 additions & 4 deletions tests/chat-run-control-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,15 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) {
'status' => 'running',
'started_at' => '2026-01-01T00:00:00Z',
'updated_at' => '2026-01-01T00:00:01Z',
'metadata' => array( 'provider' => 'test', 'token' => 'secret-token' ),
'metadata' => array(
'provider' => 'test',
'orchestration' => array(
'provider' => 'fake-durable-runner',
'run_id' => 'external-run-1',
'event_cursor' => 'external-cursor-1',
),
'token' => 'secret-token',
),
),
10,
2
Expand All @@ -225,6 +233,9 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) {
$status = AgentsAPI\AI\Channels\agents_get_chat_run( array( 'session_id' => 'session-1', 'run_id' => 'run-1' ) );
agents_api_smoke_assert_equals( 'running', $status['status'] ?? null, 'get-run normalizes status payload', $failures, $passes );
agents_api_smoke_assert_equals( 'test', $status['metadata']['provider'] ?? null, 'get-run preserves metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'fake-durable-runner', $status['metadata']['orchestration']['provider'] ?? null, 'get-run preserves external orchestration provider metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'external-run-1', $status['metadata']['orchestration']['run_id'] ?? null, 'get-run preserves external orchestration run id metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'external-cursor-1', $status['metadata']['orchestration']['event_cursor'] ?? null, 'get-run preserves external orchestration event cursor metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'secret-token', $status['metadata']['token'] ?? null, 'manager get-run preserves operator metadata', $failures, $passes );
$GLOBALS['__agents_api_smoke_caps']['manage_options'] = false;
$observer_status = AgentsAPI\AI\Channels\agents_get_chat_run( array( 'session_id' => 'session-1', 'run_id' => 'run-1' ) );
Expand Down Expand Up @@ -283,16 +294,28 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) {
'run_id' => $input['run_id'],
'session_id' => $input['session_id'],
'status' => 'running',
'metadata' => array(
'orchestration' => array(
'provider' => 'fake-durable-runner',
'run_id' => 'external-events-run-1',
'event_cursor' => 'external-event-cursor-1',
),
),
'events' => array(
array(
'id' => 'evt_1',
'type' => 'tool_call',
'message' => 'Calling client/tool...',
'created_at' => '2026-01-01T00:00:00Z',
'metadata' => array(
'turn' => 1,
'tool_name' => 'client/tool',
'tool_call_id' => 'call-1',
'turn' => 1,
'tool_name' => 'client/tool',
'tool_call_id' => 'call-1',
'orchestration' => array(
'provider' => 'fake-durable-runner',
'run_id' => 'external-events-run-1',
'event_cursor' => 'external-event-cursor-1',
),
),
),
),
Expand All @@ -314,6 +337,10 @@ static function ( $handler, array $input ) use ( &$captured_chat_input ) {
agents_api_smoke_assert_equals( 'session-events-1', $event_page['session_id'] ?? null, 'run events handler preserves session id', $failures, $passes );
agents_api_smoke_assert_equals( 'running', $event_page['status'] ?? null, 'run events handler normalizes status', $failures, $passes );
agents_api_smoke_assert_equals( 'evt_1', $event_page['cursor'] ?? null, 'run events handler returns cursor', $failures, $passes );
agents_api_smoke_assert_equals( 'fake-durable-runner', $event_page['metadata']['orchestration']['provider'] ?? null, 'run events handler preserves external orchestration provider metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'external-events-run-1', $event_page['metadata']['orchestration']['run_id'] ?? null, 'run events handler preserves external orchestration run id metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'external-event-cursor-1', $event_page['metadata']['orchestration']['event_cursor'] ?? null, 'run events handler preserves external orchestration event cursor metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'client/tool', $event_page['events'][0]['metadata']['tool_name'] ?? null, 'run events handler returns safe metadata', $failures, $passes );
agents_api_smoke_assert_equals( 'external-event-cursor-1', $event_page['events'][0]['metadata']['orchestration']['event_cursor'] ?? null, 'run events handler returns external event cursor metadata', $failures, $passes );

agents_api_smoke_finish( 'chat run-control', $failures, $passes );
Loading