diff --git a/agents-api.php b/agents-api.php index 4eb7914..c2909ef 100644 --- a/agents-api.php +++ b/agents-api.php @@ -166,6 +166,7 @@ require_once AGENTS_API_PATH . 'src/Runtime/interface-wp-agent-run-control-store.php'; require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-option-run-control-store.php'; require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-run-control.php'; +require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-run-result-envelope.php'; require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-run-outcome.php'; require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-runtime-package-run-request.php'; require_once AGENTS_API_PATH . 'src/Runtime/class-wp-agent-runtime-package-run-result.php'; diff --git a/composer.json b/composer.json index d9d5277..753af5c 100644 --- a/composer.json +++ b/composer.json @@ -84,6 +84,7 @@ "php tests/tool-declaration-rejection-smoke.php", "php tests/conversation-loop-interrupt-source-smoke.php", "php tests/run-outcome-status-smoke.php", + "php tests/run-result-envelope-smoke.php", "php tests/iteration-budget-smoke.php", "php tests/conversation-loop-budgets-smoke.php", "php tests/runtime-package-run-contract-smoke.php", diff --git a/src/Packages/class-wp-agent-package-adoption-result.php b/src/Packages/class-wp-agent-package-adoption-result.php index f37b551..5ed4d2d 100644 --- a/src/Packages/class-wp-agent-package-adoption-result.php +++ b/src/Packages/class-wp-agent-package-adoption-result.php @@ -165,6 +165,29 @@ static function ( WP_Agent_Package_Installed_Artifact $artifact ): array { return $data; } + public function to_run_result_envelope(): \AgentsAPI\AI\WP_Agent_Run_Result_Envelope { + return \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::from_array( + array( + 'run_id' => $this->agent_slug, + 'status' => $this->canonical_status(), + 'outputs' => array( + 'agent_slug' => $this->agent_slug, + 'messages' => $this->messages, + 'applied' => $this->applied_artifacts, + 'skipped' => $this->skipped_artifacts, + 'failed' => $this->failed_artifacts, + ), + 'artifact_refs' => $this->artifact_refs(), + 'provenance' => array( 'package_adoption_status' => $this->status ), + 'metadata' => $this->meta, + ) + ); + } + + public function to_canonical_envelope(): \AgentsAPI\AI\WP_Agent_Run_Result_Envelope { + return $this->to_run_result_envelope(); + } + /** * Validates status. * @@ -181,6 +204,27 @@ private function prepare_status( string $status ): string { return $status; } + private function canonical_status(): string { + if ( 'failed' === $this->status ) { + return \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::STATUS_FAILED; + } + if ( in_array( $this->status, array( 'skipped', 'needs-approval' ), true ) ) { + return 'needs-approval' === $this->status ? \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::STATUS_APPROVAL_REQUIRED : \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::STATUS_SKIPPED; + } + + return \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::STATUS_SUCCEEDED; + } + + /** @return array> */ + private function artifact_refs(): array { + $refs = array(); + foreach ( $this->recorded_artifacts as $artifact ) { + $refs[] = $artifact->to_array(); + } + + return \AgentsAPI\AI\WP_Agent_Run_Result_Envelope::normalize_refs( $refs ); + } + /** * Normalizes messages. * diff --git a/src/Runtime/class-wp-agent-run-result-envelope.php b/src/Runtime/class-wp-agent-run-result-envelope.php new file mode 100644 index 0000000..d87a7c4 --- /dev/null +++ b/src/Runtime/class-wp-agent-run-result-envelope.php @@ -0,0 +1,268 @@ + $outputs Consumer-defined outputs. + * @param array> $artifact_refs Canonical artifact references. + * @param array> $evidence_refs Canonical evidence/log references. + * @param array $replay Replay/materialization metadata. + * @param array $provenance Producer/source metadata. + * @param array $timestamps started_at/ended_at/updated_at values. + * @param array $error Stable error envelope. + * @param array $cancellation Cancellation request/result metadata. + * @param array $metadata Host/runtime metadata. + */ + public function __construct( + private string $run_id, + private string $status, + private array $outputs = array(), + private array $artifact_refs = array(), + private array $evidence_refs = array(), + private array $replay = array(), + private array $provenance = array(), + private array $timestamps = array(), + private array $error = array(), + private array $cancellation = array(), + private array $metadata = array() + ) { + $this->status = self::normalize_status( $this->status ); + $this->outputs = self::map_value( $this->outputs ); + $this->artifact_refs = self::normalize_refs( $this->artifact_refs ); + $this->evidence_refs = self::normalize_refs( $this->evidence_refs ); + $this->replay = self::map_value( $this->replay ); + $this->provenance = self::map_value( $this->provenance ); + $this->timestamps = self::timestamps_value( $this->timestamps ); + $this->error = self::map_value( $this->error ); + $this->cancellation = self::map_value( $this->cancellation ); + $this->metadata = self::map_value( $this->metadata ); + } + + /** @return array */ + public static function statuses(): array { + return array( + self::STATUS_PENDING, + self::STATUS_QUEUED, + self::STATUS_RUNNING, + self::STATUS_CANCELLING, + self::STATUS_CANCELLED, + self::STATUS_SUCCEEDED, + self::STATUS_FAILED, + self::STATUS_SKIPPED, + self::STATUS_COMPLETED, + self::STATUS_INCOMPLETE, + self::STATUS_APPROVAL_REQUIRED, + self::STATUS_RUNTIME_TOOL_PENDING, + self::STATUS_BUDGET_EXCEEDED, + self::STATUS_STALLED, + self::STATUS_INTERRUPTED, + ); + } + + /** + * @param array $value Raw envelope. + */ + public static function from_array( array $value ): self { + $timestamps = self::map_value( $value['timestamps'] ?? array() ); + foreach ( array( 'started_at', 'ended_at', 'updated_at' ) as $field ) { + if ( ! isset( $timestamps[ $field ] ) && isset( $value[ $field ] ) ) { + $timestamps[ $field ] = $value[ $field ]; + } + } + + return new self( + self::string_value( $value['run_id'] ?? '' ), + self::normalize_status( $value['status'] ?? null ), + self::map_value( $value['outputs'] ?? ( $value['output'] ?? array() ) ), + self::normalize_refs( $value['artifact_refs'] ?? array() ), + self::normalize_refs( $value['evidence_refs'] ?? array() ), + self::map_value( $value['replay'] ?? array() ), + self::map_value( $value['provenance'] ?? array() ), + $timestamps, + self::map_value( $value['error'] ?? array() ), + self::map_value( $value['cancellation'] ?? array() ), + self::map_value( $value['metadata'] ?? array() ) + ); + } + + public static function normalize_status( mixed $status ): string { + $status = strtolower( trim( self::string_value( $status ) ) ); + return in_array( $status, self::statuses(), true ) ? $status : self::STATUS_RUNNING; + } + + /** + * @param mixed $refs Raw refs. + * @return array> + */ + public static function normalize_refs( mixed $refs ): array { + if ( ! is_array( $refs ) ) { + return array(); + } + + $normalized = array(); + foreach ( $refs as $ref ) { + if ( ! is_array( $ref ) ) { + continue; + } + + $ref = self::map_value( $ref ); + foreach ( array( 'type', 'label', 'id', 'url', 'path', 'mime_type', 'sha256', 'description' ) as $field ) { + if ( isset( $ref[ $field ] ) ) { + $value = trim( self::string_value( $ref[ $field ] ) ); + if ( '' === $value ) { + unset( $ref[ $field ] ); + } else { + $ref[ $field ] = $value; + } + } + } + + if ( array() !== $ref ) { + $normalized[] = $ref; + } + } + + return $normalized; + } + + public function get_run_id(): string { + return $this->run_id; + } + + public function get_status(): string { + return $this->status; + } + + /** @return array */ + public function get_outputs(): array { + return $this->outputs; + } + + /** @return array> */ + public function get_artifact_refs(): array { + return $this->artifact_refs; + } + + /** @return array> */ + public function get_evidence_refs(): array { + return $this->evidence_refs; + } + + /** @return array */ + public function get_replay(): array { + return $this->replay; + } + + /** @return array */ + public function get_provenance(): array { + return $this->provenance; + } + + /** @return array */ + public function get_timestamps(): array { + return $this->timestamps; + } + + /** @return array */ + public function get_error(): array { + return $this->error; + } + + /** @return array */ + public function get_cancellation(): array { + return $this->cancellation; + } + + /** @return array */ + public function get_metadata(): array { + return $this->metadata; + } + + /** @return array */ + public function to_array(): array { + return array( + 'schema' => self::SCHEMA, + 'version' => self::VERSION, + 'run_id' => $this->run_id, + 'status' => $this->status, + 'outputs' => $this->outputs, + 'artifact_refs' => $this->artifact_refs, + 'evidence_refs' => $this->evidence_refs, + 'replay' => $this->replay, + 'provenance' => $this->provenance, + 'timestamps' => $this->timestamps, + 'error' => $this->error, + 'cancellation' => $this->cancellation, + 'metadata' => $this->metadata, + ); + } + + private static function string_value( mixed $value ): string { + return is_scalar( $value ) || $value instanceof \Stringable ? (string) $value : ''; + } + + /** @return array */ + private static function map_value( mixed $value ): array { + if ( ! is_array( $value ) ) { + return array(); + } + + $map = array(); + foreach ( $value as $key => $item ) { + if ( is_string( $key ) ) { + $map[ $key ] = $item; + } + } + + return $map; + } + + /** + * @param array $value Raw timestamps. + * @return array + */ + private static function timestamps_value( array $value ): array { + $timestamps = array(); + foreach ( array( 'started_at', 'ended_at', 'updated_at' ) as $field ) { + if ( isset( $value[ $field ] ) ) { + $timestamp = trim( self::string_value( $value[ $field ] ) ); + if ( '' !== $timestamp ) { + $timestamps[ $field ] = $timestamp; + } + } + } + + return $timestamps; + } +} diff --git a/src/Runtime/class-wp-agent-runtime-package-run-result.php b/src/Runtime/class-wp-agent-runtime-package-run-result.php index c3acd52..eb0b2e1 100644 --- a/src/Runtime/class-wp-agent-runtime-package-run-result.php +++ b/src/Runtime/class-wp-agent-runtime-package-run-result.php @@ -29,6 +29,7 @@ final class WP_Agent_Runtime_Package_Run_Result { * @param array> $evidence_refs Neutral artifact/log refs. * @param array $metadata Host/runtime metadata. * @param array $replay Replay/materialization metadata. + * @param array> $artifact_refs Canonical artifact refs. */ public function __construct( private string $status, @@ -37,7 +38,8 @@ public function __construct( private array $error = array(), private array $evidence_refs = array(), private array $metadata = array(), - private array $replay = array() + private array $replay = array(), + private array $artifact_refs = array() ) {} /** @@ -56,7 +58,8 @@ public static function from_array( array $value ): self { self::array_value( $value['error'] ?? array() ), self::list_of_arrays( $value['evidence_refs'] ?? array() ), self::array_value( $value['metadata'] ?? array() ), - self::array_value( $value['replay'] ?? array() ) + self::array_value( $value['replay'] ?? array() ), + self::list_of_arrays( $value['artifact_refs'] ?? array() ) ); } @@ -95,6 +98,11 @@ public function get_evidence_refs(): array { return $this->evidence_refs; } + /** @return array> */ + public function get_artifact_refs(): array { + return self::list_of_arrays( $this->artifact_refs ); + } + /** @return array */ public function get_metadata(): array { return $this->metadata; @@ -112,12 +120,32 @@ public function to_array(): array { 'run_id' => $this->run_id, 'result' => $this->result, 'error' => $this->error, + 'artifact_refs' => $this->artifact_refs, 'evidence_refs' => $this->evidence_refs, 'metadata' => $this->metadata, 'replay' => $this->replay, ); } + public function to_run_result_envelope(): WP_Agent_Run_Result_Envelope { + return WP_Agent_Run_Result_Envelope::from_array( + array( + 'run_id' => $this->run_id, + 'status' => $this->status, + 'outputs' => $this->result, + 'artifact_refs' => $this->artifact_refs, + 'evidence_refs' => $this->evidence_refs, + 'replay' => $this->replay, + 'error' => $this->error, + 'metadata' => $this->metadata, + ) + ); + } + + public function to_canonical_envelope(): WP_Agent_Run_Result_Envelope { + return $this->to_run_result_envelope(); + } + private static function string_value( mixed $value ): string { return is_scalar( $value ) ? trim( (string) $value ) : ''; } diff --git a/src/Tasks/class-wp-agent-task-run-control.php b/src/Tasks/class-wp-agent-task-run-control.php index 028cd85..4cbb904 100644 --- a/src/Tasks/class-wp-agent-task-run-control.php +++ b/src/Tasks/class-wp-agent-task-run-control.php @@ -7,6 +7,8 @@ namespace AgentsAPI\AI\Tasks; +use AgentsAPI\AI\WP_Agent_Run_Result_Envelope; + defined( 'ABSPATH' ) || exit; /** @@ -92,6 +94,45 @@ public static function normalize_run( array $run ): array { ); } + /** + * Convert a task run/result payload to the canonical run result envelope. + * + * @param array $run Raw or normalized run payload. + */ + public static function to_run_result_envelope( array $run ): WP_Agent_Run_Result_Envelope { + $normalized = self::normalize_run( $run ); + $status = self::envelope_status( self::string_value( $normalized['status'] ) ); + + return WP_Agent_Run_Result_Envelope::from_array( + array( + 'run_id' => self::string_value( $normalized['run_id'] ), + 'status' => $status, + 'outputs' => self::map_value( $normalized['output'] ?? array() ), + 'artifact_refs' => WP_Agent_Run_Result_Envelope::normalize_refs( $normalized['artifact_refs'] ?? array() ), + 'evidence_refs' => WP_Agent_Run_Result_Envelope::normalize_refs( $normalized['evidence_refs'] ?? array() ), + 'provenance' => self::map_value( $normalized['provenance'] ?? array() ), + 'timestamps' => array( + 'started_at' => $normalized['started_at'] ?? '', + 'updated_at' => $normalized['updated_at'] ?? '', + ), + 'error' => self::map_value( $normalized['error'] ?? array() ), + 'cancellation' => self::map_value( $normalized['cancellation'] ?? array() ), + 'metadata' => self::map_value( $normalized['metadata'] ?? array() ) + array( + 'session_id' => $normalized['session_id'], + 'executor_id' => $normalized['executor_id'], + 'execution_metrics' => $normalized['execution_metrics'], + 'diagnostics' => $normalized['diagnostics'], + 'events' => $normalized['events'], + ), + ) + ); + } + + /** @param array $run Raw or normalized run payload. */ + public static function to_canonical_envelope( array $run ): WP_Agent_Run_Result_Envelope { + return self::to_run_result_envelope( $run ); + } + /** * Normalize status values while keeping the public vocabulary bounded. */ @@ -100,6 +141,16 @@ public static function normalize_status( mixed $status ): string { return in_array( $status, self::statuses(), true ) ? $status : self::STATUS_RUNNING; } + private static function envelope_status( string $status ): string { + if ( self::STATUS_QUEUED === $status ) { + return WP_Agent_Run_Result_Envelope::STATUS_QUEUED; + } + if ( self::STATUS_CANCELLING === $status ) { + return WP_Agent_Run_Result_Envelope::STATUS_CANCELLING; + } + return WP_Agent_Run_Result_Envelope::normalize_status( $status ); + } + /** * Start or update an addressable task run in the default store. * diff --git a/src/Workflows/class-wp-agent-workflow-run-result.php b/src/Workflows/class-wp-agent-workflow-run-result.php index b6dc0b5..61910ec 100644 --- a/src/Workflows/class-wp-agent-workflow-run-result.php +++ b/src/Workflows/class-wp-agent-workflow-run-result.php @@ -24,6 +24,8 @@ namespace AgentsAPI\AI\Workflows; +use AgentsAPI\AI\WP_Agent_Run_Result_Envelope; + defined( 'ABSPATH' ) || exit; final class WP_Agent_Workflow_Run_Result { @@ -176,6 +178,29 @@ public function is_failed(): bool { return self::STATUS_FAILED === $this->status; } + public function to_run_result_envelope(): WP_Agent_Run_Result_Envelope { + return WP_Agent_Run_Result_Envelope::from_array( + array( + 'run_id' => $this->run_id, + 'status' => $this->status, + 'outputs' => $this->output, + 'evidence_refs' => $this->evidence_refs, + 'replay' => $this->replay_metadata, + 'provenance' => array( 'workflow_id' => $this->workflow_id ), + 'timestamps' => array( + 'started_at' => $this->started_at, + 'ended_at' => $this->ended_at, + ), + 'error' => $this->error, + 'metadata' => $this->metadata + array( 'steps' => $this->steps, 'inputs' => $this->inputs ), + ) + ); + } + + public function to_canonical_envelope(): WP_Agent_Run_Result_Envelope { + return $this->to_run_result_envelope(); + } + /** * Return a new result with updated fields. Run results are immutable; the * runner builds a fresh instance per state change rather than mutating. diff --git a/tests/run-result-envelope-smoke.php b/tests/run-result-envelope-smoke.php new file mode 100644 index 0000000..1085bdc --- /dev/null +++ b/tests/run-result-envelope-smoke.php @@ -0,0 +1,149 @@ + 'run-123', + 'status' => 'SUCCEEDED', + 'outputs' => array( 'summary' => 'created' ), + 'artifact_refs' => array( + array( + 'type' => ' file ', + 'label' => ' transcript ', + 'url' => ' https://example.com/transcript.json ', + ), + 'ignored', + ), + 'evidence_refs' => array( array( 'type' => 'log', 'label' => 'runner' ) ), + 'replay' => array( 'seed' => 42 ), + 'provenance' => array( 'producer' => 'smoke' ), + 'started_at' => '2026-06-19T00:00:00Z', + 'ended_at' => '2026-06-19T00:00:01Z', + ) +); +agents_api_smoke_assert_equals( WP_Agent_Run_Result_Envelope::STATUS_SUCCEEDED, $envelope->get_status(), 'status lowercases into shared vocabulary', $failures, $passes ); +agents_api_smoke_assert_equals( 'created', $envelope->get_outputs()['summary'] ?? '', 'outputs map is preserved', $failures, $passes ); +agents_api_smoke_assert_equals( 1, count( $envelope->get_artifact_refs() ), 'artifact refs drop non-array items', $failures, $passes ); +agents_api_smoke_assert_equals( 'transcript', $envelope->get_artifact_refs()[0]['label'] ?? '', 'artifact ref string fields trim', $failures, $passes ); +agents_api_smoke_assert_equals( 'runner', $envelope->get_evidence_refs()[0]['label'] ?? '', 'evidence refs normalize with same vocabulary', $failures, $passes ); +agents_api_smoke_assert_equals( '2026-06-19T00:00:00Z', $envelope->get_timestamps()['started_at'] ?? '', 'top-level started_at folds into timestamps', $failures, $passes ); + +echo "\n[2] Runtime package results convert without changing legacy arrays:\n"; +$runtime = WP_Agent_Runtime_Package_Run_Result::from_array( + array( + 'status' => 'succeeded', + 'run_id' => 'runtime-run', + 'result' => array( 'page_id' => 123 ), + 'artifact_refs' => array( array( 'type' => 'package', 'label' => 'bundle' ) ), + 'evidence_refs' => array( array( 'type' => 'trace', 'label' => 'trace' ) ), + 'replay' => array( 'recipe' => 'site' ), + 'metadata' => array( 'runtime' => 'codebox' ), + ) +); +$runtime_array = $runtime->to_array(); +$runtime_envelope = $runtime->to_run_result_envelope(); +agents_api_smoke_assert_equals( 'succeeded', $runtime_array['status'] ?? '', 'runtime legacy status remains present', $failures, $passes ); +agents_api_smoke_assert_equals( 123, $runtime_envelope->get_outputs()['page_id'] ?? 0, 'runtime result maps to canonical outputs', $failures, $passes ); +agents_api_smoke_assert_equals( 'bundle', $runtime_envelope->get_artifact_refs()[0]['label'] ?? '', 'runtime artifact refs map to canonical refs', $failures, $passes ); +agents_api_smoke_assert_equals( 'trace', $runtime_envelope->get_evidence_refs()[0]['label'] ?? '', 'runtime evidence refs map to canonical refs', $failures, $passes ); + +echo "\n[3] Workflow results convert with provenance and replay metadata:\n"; +$workflow = new WP_Agent_Workflow_Run_Result( + 'workflow-run', + 'build-site', + WP_Agent_Workflow_Run_Result::STATUS_FAILED, + array( 'prompt' => 'Build' ), + array( 'partial' => true ), + array( array( 'id' => 'step-1', 'status' => 'failed' ) ), + array( 'code' => 'step_failed', 'message' => 'Step failed' ), + 100, + 110, + array( 'trace_id' => 'trace-1' ), + array( array( 'type' => 'log', 'label' => 'workflow log' ) ), + array( 'workflow_version' => 2 ) +); +$workflow_envelope = $workflow->to_run_result_envelope(); +agents_api_smoke_assert_equals( WP_Agent_Run_Result_Envelope::STATUS_FAILED, $workflow_envelope->get_status(), 'workflow status maps to canonical failed', $failures, $passes ); +agents_api_smoke_assert_equals( 'build-site', $workflow_envelope->get_provenance()['workflow_id'] ?? '', 'workflow id maps to provenance', $failures, $passes ); +agents_api_smoke_assert_equals( 2, $workflow_envelope->get_replay()['workflow_version'] ?? 0, 'workflow replay metadata is preserved', $failures, $passes ); +agents_api_smoke_assert_equals( 'workflow log', $workflow_envelope->get_evidence_refs()[0]['label'] ?? '', 'workflow evidence refs are preserved', $failures, $passes ); + +echo "\n[4] Task run-control arrays convert to the canonical envelope:\n"; +$task_envelope = WP_Agent_Task_Run_Control::to_run_result_envelope( + array( + 'run_id' => 'task-run', + 'session_id' => 'session-1', + 'executor_id' => 'executor-1', + 'status' => 'cancelling', + 'output' => array( 'summary' => 'queued cancel' ), + 'artifact_refs' => array( array( 'type' => 'artifact', 'label' => 'task artifact' ) ), + 'provenance' => array( 'source' => 'task' ), + 'started_at' => '2026-06-19T00:00:00Z', + 'updated_at' => '2026-06-19T00:00:02Z', + ) +); +agents_api_smoke_assert_equals( WP_Agent_Run_Result_Envelope::STATUS_CANCELLING, $task_envelope->get_status(), 'task cancelling status is shared', $failures, $passes ); +agents_api_smoke_assert_equals( 'queued cancel', $task_envelope->get_outputs()['summary'] ?? '', 'task output maps to outputs', $failures, $passes ); +agents_api_smoke_assert_equals( 'executor-1', $task_envelope->get_metadata()['executor_id'] ?? '', 'task executor id maps to metadata', $failures, $passes ); + +echo "\n[5] Package adoption results expose canonical envelope refs:\n"; +$recorded = new WP_Agent_Package_Installed_Artifact( + array( + 'package_slug' => 'demo-package', + 'package_version' => '1.0.0', + 'artifact_type' => 'agents/prompt', + 'artifact_id' => 'demo-prompt', + 'source' => 'agents/demo.md', + 'installed_hash' => 'abc123', + 'current_hash' => 'abc123', + 'installed_at' => '2026-06-19T00:00:00Z', + 'updated_at' => '2026-06-19T00:00:00Z', + ) +); +$package = new WP_Agent_Package_Adoption_Result( + 'applied', + 'demo-agent', + array( 'Applied demo agent.' ), + null, + array( array( 'type' => 'agents/prompt', 'path' => 'agents/demo.md' ) ), + array(), + array(), + array( $recorded ), + array( 'package' => 'demo' ) +); +$package_envelope = $package->to_run_result_envelope(); +agents_api_smoke_assert_equals( WP_Agent_Run_Result_Envelope::STATUS_SUCCEEDED, $package_envelope->get_status(), 'package applied maps to succeeded', $failures, $passes ); +agents_api_smoke_assert_equals( 'demo-agent', $package_envelope->get_outputs()['agent_slug'] ?? '', 'package agent slug maps to outputs', $failures, $passes ); +agents_api_smoke_assert_equals( 'agents/demo.md', $package_envelope->get_artifact_refs()[0]['source'] ?? '', 'package recorded artifacts map to artifact refs', $failures, $passes ); + +agents_api_smoke_finish( 'Agents API run result envelope', $failures, $passes );