feat(plugin): surface execution input and result - #616
Conversation
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
d1f3d59 to
05c3de7
Compare
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
05c3de7 to
1060dde
Compare
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
This comment has been minimized.
This comment has been minimized.
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
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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
fce6081 to
d92013f
Compare
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
| execution_result: str | None = field( | ||
| default=None, | ||
| kw_only=True, | ||
| metadata={"experimental": True}, | ||
| ) |
There was a problem hiding this comment.
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.
| execution_input: Any = field( | ||
| default=None, | ||
| kw_only=True, | ||
| metadata={"experimental": True}, | ||
| ) |
There was a problem hiding this comment.
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.
Codex AI reviewTwo regressions remain in the new public hook fields; OTel logging and dataclass compatibility need coverage. Reviewed commit |
zhongkechen
left a comment
There was a problem hiding this comment.
Requires a follow-up to resolve the concerning issue.
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
Claude AI reviewSummaryThis is a clean, purely additive API widening. I found no correctness, regression, determinism, or compatibility defects. Verification performed:
Test coverage for the new behavior is strong (forwarding to both hooks, empty payload → Residual test risk (non-blocking): The Reviewed commit |
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
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
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
Summary
Widens the plugin invocation hooks so instrumentation plugins can record execution
input/output:
InvocationInfo.execution_input: Any— the deserialized execution input, populated bydurable_execution()from the invocation's input eventInvocationEndInfo.execution_result: str | None— the serialized result JSON fromDurableExecutionInvocationOutput.result(Noneon failure/suspend), threaded throughfrom_durable_execution_invocation_outputBoth fields are kw-only with
Nonedefaults, 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 emitexecution records carrying
input/output. Porting that plugin to Python (validatedagainst the cross-SDK
insightconformance 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
src/aws_durable_execution_sdk_python/plugin.pyfrom_durable_execution_invocation_outputcarries them;PluginExecutorstart-info plumbingsrc/aws_durable_execution_sdk_python/execution.pyinput_eventinto the invocation-start plugin infotests/plugin_test.pyTesting
hatch run test:all packages/aws-durable-execution-sdk-python/tests/plugin_test.py— 70 passedhatch run test:all packages/aws-durable-execution-sdk-python/tests/execution_test.py— 82 passedhatch fmt --checkclean on all touched files18-requirement
insightconformance suite 18/18 on both record sinks (live, us-west-2)