Skip to content

feat(plugin): surface execution input and result - #616

Merged
wangyb-A merged 6 commits into
mainfrom
feat/plugin-invocation-input-result
Aug 11, 2026
Merged

feat(plugin): surface execution input and result#616
wangyb-A merged 6 commits into
mainfrom
feat/plugin-invocation-input-result

Conversation

@wangyb-A

@wangyb-A wangyb-A commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Widens the plugin invocation hooks so instrumentation plugins can record execution
input/output:

  • InvocationInfo.execution_input: Any — the deserialized execution input, populated by
    durable_execution() from the invocation's input event
  • InvocationEndInfo.execution_result: str | None — the serialized result JSON from
    DurableExecutionInvocationOutput.result (None on failure/suspend), threaded through
    from_durable_execution_invocation_output

Both fields are kw-only with None defaults, so existing plugins, hook constructors,
and positional callers are unaffected — this is a purely additive API widening.

Motivation

The JS SDK already exposes this data to plugins (InvocationInfo.executionInput /
InvocationEndInfo.executionResult), and its Workflow Insight plugin depends on it to emit
execution records carrying input/output. Porting that plugin to Python (validated
against the cross-SDK insight conformance suite,
aws-durable-execution-conformance-tests#73) is blocked without these fields — the plugin
cannot honestly emit data the SDK never hands it. The Java SDK is adding the analogous
plugin accessor in aws-durable-execution-sdk-java#596.

Changes

File Change
src/aws_durable_execution_sdk_python/plugin.py the two kw-only fields + docstrings; from_durable_execution_invocation_output carries them; PluginExecutor start-info plumbing
src/aws_durable_execution_sdk_python/execution.py one line: pass input_event into the invocation-start plugin info
tests/plugin_test.py fixtures extended; new tests: default-None backward compatibility, and the end-info factory carrying input+result

Testing

  • hatch run test:all packages/aws-durable-execution-sdk-python/tests/plugin_test.py — 70 passed
  • hatch run test:all packages/aws-durable-execution-sdk-python/tests/execution_test.py — 82 passed
  • hatch fmt --check clean on all touched files
  • End-to-end: with these fields, the preview Python Workflow Insight plugin passes the
    18-requirement insight conformance suite 18/18 on both record sinks (live, us-west-2)

wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
The handler docstring claimed the Python invocation infos have no
execution-input or execution-result field. They do now; the canonical
dump still omits them because the requirement puts the payload
surfaces out of GA scope and asserts nothing about them.

Refs #616
@wangyb-A
wangyb-A force-pushed the feat/plugin-invocation-input-result branch from d1f3d59 to 05c3de7 Compare August 11, 2026 05:10
@wangyb-A wangyb-A changed the title feat(plugin): surface execution input and result on invocation hooks feat(plugin): surface execution input and result Aug 11, 2026
@wangyb-A wangyb-A closed this Aug 11, 2026
@wangyb-A wangyb-A reopened this Aug 11, 2026
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
The handler docstring claimed the Python invocation infos have no
execution-input or execution-result field. They do now; the canonical
dump still omits them because the requirement puts the payload
surfaces out of GA scope and asserts nothing about them.

Refs #616
@wangyb-A
wangyb-A force-pushed the feat/plugin-invocation-input-result branch from 05c3de7 to 1060dde Compare August 11, 2026 05:26
@wangyb-A
wangyb-A deployed to ai-pr-review August 11, 2026 05:26 — with GitHub Actions Active
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
The handler docstring claimed the Python invocation infos have no
execution-input or execution-result field. They do now; the canonical
dump still omits them because the requirement puts the payload
surfaces out of GA scope and asserts nothing about them.

Refs #616
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 05:33 — with GitHub Actions Inactive
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 05:33 — with GitHub Actions Inactive
@github-actions

This comment has been minimized.

wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses both AI review comments on #616.

Adds two durable_execution()-driven tests asserting the SDK actually
forwards the deserialized input to the invocation hooks, plus the
serialized result on the end hook. Verified as real regression guards:
both fail when execution_input=input_event is removed.

Also corrects the execution_input field comment. It claimed None for
empty input, but execution.py initializes input_event to {} and only
replaces it when a non-empty payload parses, so an empty payload is
surfaced as {}. None occurs only when the field is left unpopulated.

Refs #616
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Convert the execution_input / execution_result field comments to
attribute docstrings prefixed EXPERIMENTAL, matching the wording of the
existing marker on the plugins= parameter in execution.py.

Unlike # comments these render in IDE hover and doc tooling, so plugin
authors see the stability caveat at the point of use. Content is
otherwise unchanged, including the corrected {}-vs-None note. Purely
documentation: the fields keep their None defaults and kw_only=True.

Refs #616
@wangyb-A
wangyb-A deployed to ai-pr-review August 11, 2026 06:20 — with GitHub Actions Active
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 06:30 — with GitHub Actions Inactive
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 06:30 — with GitHub Actions Inactive
@wangyb-A
wangyb-A marked this pull request as ready for review August 11, 2026 06:38
@github-actions

This comment has been minimized.

@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 06:44 — with GitHub Actions Inactive
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 06:44 — with GitHub Actions Inactive
Comment thread packages/aws-durable-execution-sdk-python/tests/execution_test.py
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Alex Wang and others added 6 commits August 11, 2026 18:34
Adds InvocationInfo.execution_input (the deserialized execution input)
and InvocationEndInfo.execution_result (the serialized result JSON) so
instrumentation plugins can record execution I/O. Both are kw-only with
None defaults, so existing plugins and callers are unaffected. Mirrors
the JS SDK's InvocationInfo.executionInput / InvocationEndInfo
.executionResult (the Java SDK is adding the analogous accessor in
aws-durable-execution-sdk-java#596).
Addresses both AI review comments on #616.

Adds two durable_execution()-driven tests asserting the SDK actually
forwards the deserialized input to the invocation hooks, plus the
serialized result on the end hook. Verified as real regression guards:
both fail when execution_input=input_event is removed.

Also corrects the execution_input field comment. It claimed None for
empty input, but execution.py initializes input_event to {} and only
replaces it when a non-empty payload parses, so an empty payload is
surfaced as {}. None occurs only when the field is left unpopulated.

Refs #616
Convert the execution_input / execution_result field comments to
attribute docstrings prefixed EXPERIMENTAL, matching the wording of the
existing marker on the plugins= parameter in execution.py.

Unlike # comments these render in IDE hover and doc tooling, so plugin
authors see the stability caveat at the point of use. Content is
otherwise unchanged, including the corrected {}-vs-None note. Purely
documentation: the fields keep their None defaults and kw_only=True.

Refs #616
Addresses both Codex review comments on #616.

durable_execution() handed the same mutable object to the user handler
and to on_invocation_start, so the aliasing ran both ways: a plugin
mutating info.execution_input changed the handler's event and could
alter execution behaviour, and a handler mutating its event changed
what the frozen start info -- and the end info derived from it --
reported afterwards. Both directions were confirmed reproducible.

PluginExecutor now deep-copies the input for the plugin view. The copy
is eager because the handler starts immediately after the hook, and is
skipped when no plugins are registered so non-plugin executions pay
nothing. On copy failure it falls back to the shared reference rather
than dropping the input.

Adds unit tests for both mutation directions (verified to fail without
the copy) and an e2e suite under tests/e2e/ per AGENTS.md, covering the
input and serialized result through complete invocations, a
suspend/replay pair where only the replay carries a result, and deep
isolation from the handler.

Refs #616
Return None rather than the shared reference when the execution input
cannot be deep-copied, so handler isolation holds even when a snapshot
cannot be created. Applied from an AI review suggestion.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Mark execution_input and execution_result with
metadata={"experimental": True} and a leading EXPERIMENTAL docstring,
matching the pattern #625 established for OperationInfo.result /
OperationInfo.error / InvocationEndInfo.error.

Unlike a docstring alone the metadata is introspectable at runtime, so
the generic test #625 added -- which requires the marker on every
plugin info field named for input, output, result or error -- now
covers these two fields. That test fails on this branch without them.

This matters more since #625 removed the plugins= FutureWarning: the
per-field markers are now the only stability signal on these surfaces.

Refs #616
@wangyb-A
wangyb-A force-pushed the feat/plugin-invocation-input-result branch from fce6081 to d92013f Compare August 11, 2026 18:38
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 18:38 — with GitHub Actions Inactive
@wangyb-A
wangyb-A temporarily deployed to ai-pr-review-runtime August 11, 2026 18:38 — with GitHub Actions Inactive
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
The handler docstring claimed the Python invocation infos have no
execution-input or execution-result field. They do now; the canonical
dump still omits them because the requirement puts the payload
surfaces out of GA scope and asserts nothing about them.

Refs #616
Comment on lines +208 to +212
execution_result: str | None = field(
default=None,
kw_only=True,
metadata={"experimental": True},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex AI review

High: Dataclass fields appear in repr by default. The bundled OTel plugins already log invocation info objects wholesale, and the plugin example does so at INFO, so arbitrary inputs/results, including secrets or large payloads, will now be logged implicitly. Set repr=False on both payload fields and add a regression test ensuring their values are omitted from repr.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks concerning

Comment on lines +179 to +183
execution_input: Any = field(
default=None,
kw_only=True,
metadata={"experimental": True},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex AI review

Medium: This field participates in the generated dataclass equality and hash. Normal dict/list inputs make previously hashable InvocationStartInfo objects raise TypeError when hashed, while comparisons against objects constructed using the prior fields now fail. Preserve additive compatibility by setting compare=False, hash=False on both new payload fields and test those semantics.

@github-actions

Copy link
Copy Markdown
Contributor

Codex AI review

Two regressions remain in the new public hook fields; OTel logging and dataclass compatibility need coverage.

Reviewed commit d92013f69d7b92a984d67ea98683dfd62d7785b1. Workflow run

@zhongkechen zhongkechen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires a follow-up to resolve the concerning issue.

@wangyb-A
wangyb-A merged commit 6967db7 into main Aug 11, 2026
30 of 32 checks passed
@wangyb-A
wangyb-A deleted the feat/plugin-invocation-input-result branch August 11, 2026 19:09
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses both AI review comments on #616.

Adds two durable_execution()-driven tests asserting the SDK actually
forwards the deserialized input to the invocation hooks, plus the
serialized result on the end hook. Verified as real regression guards:
both fail when execution_input=input_event is removed.

Also corrects the execution_input field comment. It claimed None for
empty input, but execution.py initializes input_event to {} and only
replaces it when a non-empty payload parses, so an empty payload is
surfaced as {}. None occurs only when the field is left unpopulated.

Refs #616
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Convert the execution_input / execution_result field comments to
attribute docstrings prefixed EXPERIMENTAL, matching the wording of the
existing marker on the plugins= parameter in execution.py.

Unlike # comments these render in IDE hover and doc tooling, so plugin
authors see the stability caveat at the point of use. Content is
otherwise unchanged, including the corrected {}-vs-None note. Purely
documentation: the fields keep their None defaults and kw_only=True.

Refs #616
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses both Codex review comments on #616.

durable_execution() handed the same mutable object to the user handler
and to on_invocation_start, so the aliasing ran both ways: a plugin
mutating info.execution_input changed the handler's event and could
alter execution behaviour, and a handler mutating its event changed
what the frozen start info -- and the end info derived from it --
reported afterwards. Both directions were confirmed reproducible.

PluginExecutor now deep-copies the input for the plugin view. The copy
is eager because the handler starts immediately after the hook, and is
skipped when no plugins are registered so non-plugin executions pay
nothing. On copy failure it falls back to the shared reference rather
than dropping the input.

Adds unit tests for both mutation directions (verified to fail without
the copy) and an e2e suite under tests/e2e/ per AGENTS.md, covering the
input and serialized result through complete invocations, a
suspend/replay pair where only the replay carries a result, and deep
isolation from the handler.

Refs #616
@github-actions

Copy link
Copy Markdown
Contributor

Claude AI review

Summary

This is a clean, purely additive API widening. InvocationInfo.execution_input and InvocationEndInfo.execution_result are added as kw-only fields with None defaults, durable_execution() passes input_event into the start hook, and from_durable_execution_invocation_output threads both through to the end info.

I found no correctness, regression, determinism, or compatibility defects.

Verification performed:

  • API compatibility: every construction of InvocationInfo/InvocationStartInfo/InvocationEndInfo across the core, otel, testing, examples, and conformance packages uses keyword arguments. The new kw-only defaulted fields leave the positional __init__ signature unchanged (the pre-existing non-kw error field and kw-only status are undisturbed), so no positional caller breaks.
  • execution_result semantics: map correctly on every terminal path — small success → JSON string, large-payload success → "" (checkpointed out-of-band), suspend/PENDING → None, user failure and RETRY → None — confirmed by tracing to_dict/from_dict round-tripping in PluginExecutor.handle_durable_output.
  • Determinism / side-effect safety: the eager copy.deepcopy snapshot is taken synchronously on the handler thread before the user function is submitted, and the handler always receives the original input_event. This correctly prevents a plugin from mutating the input and altering execution behavior, and prevents the handler from retroactively changing the frozen hook view. self._invocation_status is reset in run()'s finally, so no cross-invocation leak.
  • Robustness: on_invocation_end still short-circuits when on_invocation_start was never reached (early non-retryable-fetch returns), so no new crash path.

Test coverage for the new behavior is strong (forwarding to both hooks, empty payload → {}, top-level and nested isolation, suspend-no-result, replay-with-result, and the end-info factory).

Residual test risk (non-blocking): The except Exception fallback in plugin.py _snapshot_execution_input (log + return None) has no covering test. It is effectively unreachable for json.loads output (which is always deep-copyable and acyclic), so this is a defensive branch rather than a real gap; worth a note only because dev-core:cov enforces --cov-fail-under=98. Additionally, the execution_input docstring states durable_execution() "always populates it" — technically the deepcopy-failure fallback can yield None — but this is a documented edge and not a behavioral defect.

Reviewed commit d92013f69d7b92a984d67ea98683dfd62d7785b1. Workflow run

wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses the medium-severity Codex review comment on #616.

The payload fields joined the generated __eq__ and __hash__, so the
widening was not additive. execution_input holds arbitrary deserialized
JSON, and a dict or list value made a previously hashable
InvocationStartInfo raise TypeError on hash(); both fields also made
infos built from the earlier field set compare unequal to infos
carrying a payload. Both effects were reproduced first.

Set compare=False, hash=False on execution_input and execution_result.
Identity fields still drive equality, so payload-only differences now
compare equal -- payloads are incidental data on what is otherwise an
event record.

Adds tests for hashability across dict, list, nested and scalar
payloads, for equality against the prior field set, and for the field
declarations themselves.

Note OperationInfo.result / OperationInfo.error from #625 remain in
compare. They are hashable types so they do not break hash(), but the
equality asymmetry with these fields is worth a maintainer decision.

Refs #616
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses the high-severity Codex review comment on #616.

Dataclass fields land in the generated repr, and instrumentation logs
hook infos wholesale: the bundled OTel plugins at debug level, and the
plugin example at info. Customer input and results -- potentially
secrets, potentially megabytes -- would therefore be written to logs
implicitly, just by adding these fields. Reproduced before fixing.

Set repr=False on execution_input and execution_result. Identity fields
still render, so the repr stays useful, and the values remain readable
through the attributes for plugins that deliberately record them.

Adds a regression test asserting secret-looking values never appear in
either hook info's repr, plus one pinning the field declarations.

Refs #616
wangyb-A pushed a commit that referenced this pull request Aug 11, 2026
Addresses the medium-severity Codex review comment on #616.

The payload fields joined the generated __eq__ and __hash__, so the
widening was not additive. execution_input holds arbitrary deserialized
JSON, and a dict or list value made a previously hashable
InvocationStartInfo raise TypeError on hash(); both fields also made
infos built from the earlier field set compare unequal to infos
carrying a payload. Both effects were reproduced first.

Set compare=False, hash=False on execution_input and execution_result.
Identity fields still drive equality, so payload-only differences now
compare equal -- payloads are incidental data on what is otherwise an
event record.

Adds tests for hashability across dict, list, nested and scalar
payloads, for equality against the prior field set, and for the field
declarations themselves.

Note OperationInfo.result / OperationInfo.error from #625 remain in
compare. They are hashable types so they do not break hash(), but the
equality asymmetry with these fields is worth a maintainer decision.

Refs #616
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.

2 participants