Skip to content

feat(runtime): extend the run result envelope for durable multi-step runs - #573

Merged
chubes4 merged 2 commits into
mainfrom
feat/571-envelope-multistep
Sep 27, 2026
Merged

chubes4 merged 2 commits into
mainfrom
feat/571-envelope-multistep

Conversation

@chubes4

@chubes4 chubes4 commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #571

Problem

WP_Agent_Run_Result_Envelope couldn't model durable multi-step runs without loss: unrecognised statuses silently coerced to the active running state, there was no field for per-step records, no status detail beyond the canonical enum, and no parent/child run relationships for fan-out.

New fields (additive, empty defaults, appended at the end of the constructor)

Field Type Notes
status_detail string Opaque, consumer-defined raw status/reason, preserved verbatim alongside the canonical status.
steps array<int,array<string,mixed>> Per-step result records, normalized as a list of maps (same drop-non-array-entries rule as logs).
parent_run_id string The id of the run this run was fanned out from, if any.
child_run_refs array<int,array<string,mixed>> Canonical references to child runs, normalized like artifact_refs.

SCHEMA and VERSION are unchanged. from_array()/to_array() round-trip all four new fields; new getters (get_status_detail(), get_steps(), get_parent_run_id(), get_child_run_refs()) added.

normalize_status() behavior change

Input Old result New result
Exact enum value (e.g. succeeded) unchanged unchanged
null / '' running running (preserved — see below)
completed_no_items running completed
failed - timeout running failed
agent_skipped running skipped
cancel_requested running cancelled
any other unrecognised value (e.g. quux) running incomplete

Rule: exact enum match wins. Otherwise a recognisable terminal prefix/suffix (failed… → failed, completed… → completed, cancel… → cancelled, skipped/*_skipped → skipped) maps to that terminal value; anything else maps to incomplete. Unknown input is never coerced to running — that was the bug (a finished run with a domain-specific terminal status looked alive to any observer).

Behavior change, called out per the issue's acceptance criteria: empty/null input is the one case still preserved as running, matching the existing behavior for in-progress producers that build the envelope before a terminal status is known. I checked every current envelope producer in src/ (WP_Agent_Task_Run_Control, WP_Agent_Runtime_Package_Run_Result, WP_Agent_Workflow_Run_Result, WP_Agent_Package_Adoption_Result) and none of them currently pass an unrecognised non-empty status through to the envelope, so this is additive/dormant until a new consumer (e.g. Data Machine's durable multi-step runs) starts using it — no existing producer's observed behavior changes.

from_array() also now sets status_detail to the raw status string whenever the raw value wasn't an exact enum match and no status_detail was explicitly supplied in the input array, so the original domain status is never lost even when the caller didn't think to pass it explicitly.

Workflow projection

WP_Agent_Workflow_Run_Result::to_run_result_envelope() already existed; it now also projects steps into the envelope's new top-level steps field (previously steps only rode inside metadata.steps, which is kept for backward compatibility with any existing reader).

Docs

Grepped docs/ for run-result/v1 — the one reference (docs/channels-workflows-operations.md) only names the schema, it doesn't enumerate fields, so no doc changes were needed.

Tests

Added to tests/run-result-envelope-smoke.php:

  • Round-trip of all four new fields through from_array()/to_array().
  • normalize_status(): unknown → incomplete, completed_no_items → completed, failed - timeout → failed, agent_skipped → skipped, cancel_requested → cancelled, null/'' → running (historical default preserved), every exact enum value unchanged.
  • from_array(): raw status preserved verbatim in status_detail only when not an exact enum match; exact-match statuses don't synthesize a status_detail.
  • Workflow run result projection carries steps into the envelope's canonical steps field.

No existing test assertion changed behavior (there was no existing test asserting the unknown→running coercion).

Results

composer smoke   → OK (all suites, including run-result-envelope-smoke: 73/73, and agents-api-no-product-imports-smoke: 7/7)
composer phpstan → [OK] No errors

AI disclosure

AI assistance: Yes / Tool: OpenCode / Used for: implementation and tests

…runs

Add additive, empty-defaulted fields so consumers with durable
multi-step runs can adopt the canonical envelope without loss:

- status_detail: opaque, consumer-defined raw status/reason preserved
  verbatim alongside the canonical status.
- steps: per-step result records, normalized as a list of maps.
- parent_run_id / child_run_refs: parent/child run relationships for
  fan-out, with child_run_refs normalized like artifact_refs.

normalize_status() no longer coerces unrecognised input to the active
'running' state. Exact enum matches still win; a recognisable terminal
prefix/suffix ('failed…', 'completed…', 'cancel…', 'skipped'/
'*_skipped') maps to that terminal value, and anything else maps to
'incomplete'. Empty/null input still maps to 'running', preserving the
existing behavior in-progress producers rely on when the envelope is
built before a terminal status is known. from_array() preserves the
raw status in status_detail whenever it wasn't an exact enum match and
no status_detail was explicitly supplied.

WP_Agent_Workflow_Run_Result::to_run_result_envelope() now also
projects its steps into the envelope's new top-level steps field (in
addition to the existing metadata.steps projection, kept for backward
compatibility).

SCHEMA and VERSION are unchanged; all new fields round-trip through
from_array()/to_array().

Fixes #571
@chubes4
chubes4 merged commit 610f4a9 into main Sep 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Run result envelope can't represent durable multi-step runs: silent status coercion to running, no status detail, steps, or child runs

1 participant