Skip to content

feat(plugin): operation maps and payloads on invocation hooks - #622

Closed
wangyb-A wants to merge 4 commits into
plugin-hook-field-shapefrom
plugin-invocation-info-maps
Closed

feat(plugin): operation maps and payloads on invocation hooks#622
wangyb-A wants to merge 4 commits into
plugin-hook-field-shapefrom
plugin-invocation-info-maps

Conversation

@wangyb-A

Copy link
Copy Markdown
Contributor

Closes #617. Also implements #616.

Stacked on #615 (base is plugin-hook-field-shape) because the 10-19 handler
commit modifies plugin_invocation_info_shape.py, which only exists there.
Please merge #615 first; this should auto-retarget to main.

What

Invocation-level plugin info objects carried no view of the execution's
operation state, and no view of the execution payloads. Python plugins could
not see what the JS SDK exposes on every invocation hook.

#617 (GA scope)

  • InvocationInfo.operations — the checkpointed operation map converted to
    OperationInfo, on invocation-start and invocation-end. Mirrors JS,
    where it sits on InvocationBaseInfo.
  • InvocationStartInfo.updated_operations — the subset named by the durable
    invocation input's UpdatedOperationIds, i.e. operations completed
    externally while the execution was suspended. Start hook only, matching JS's
    InvocationInfo.updatedOperations.

#616 (experimental, out of GA conformance scope)

  • InvocationInfo.execution_input — the deserialized input event, the same
    object the durable handler receives.
  • InvocationEndInfo.execution_result — the serialized result from the
    invocation output; None when the invocation suspended or failed.

All four fields are kw-only with empty/None defaults, so existing plugins,
hook constructors, and positional callers are unaffected.

Notable decisions

  • The end hook re-reads the operation map rather than reusing the
    start-of-invocation snapshot. This matches JS, which overrides
    operations: toOperationInfoMap(...) at every onInvocationEnd site, and it
    is strictly more informative — the suspending invocation reports 1 operation
    at start and 2 at end.
  • Snapshot eagerly, convert lazily. Plugin invocation hook infos missing operations and updated-operations maps #617 suggested lazy construction for
    operation-heavy executions. Fully lazy was wrong: a plugin that stashes the
    info and reads it later would observe a later state than its own hook. So
    the raw map is snapshotted when the hook fires (a cheap dict copy) and only
    the per-operation OperationInfo conversion is deferred and cached. A unit
    test pins this.
  • is_replayed is left False on map entries, matching JS's
    toOperationInfoMap (isReplay: false) — these describe stored state, not
    replay events.
  • The payload fields are marked EXPERIMENTAL: in their docstrings. Reaching
    any of this already requires the plugins= parameter, which emits a
    FutureWarning.
  • The 10-19 handler deliberately does not dump executionInput /
    executionResult: the requirement's canonical schema omits them and asserts
    nothing about them. Its docstring was corrected accordingly.

Testing

  • Live plugin conformance suite, us-west-2: 23/23, exit 0.
    10-19 flipped PASSED with no changes to the requirement. 10-1..10-18 and
    10-20..10-23 unaffected.
  • Unit tests: 1492 -> 1498 passing; plugin_test.py 68 -> 81.
  • hatch fmt --check clean.

Known pre-existing flake (not from this change)

One earlier run of the suite showed 10-22 (PluginOperationChangeShape) red;
an immediate re-run on byte-identical code was 23/23 clean. Evidence it is not
a regression: the only edit on that path is a pure refactor (an inline dict
comprehension replaced by _to_operation_info_map, the same conversion), and
60/60 local in-process repeats of the 10-22 handler satisfy its primary
matcher. Two candidate causes remain (terminal-batch coalescing in the
checkpoint loop, or the runner's ~20s CloudWatch ingestion window); JS's
handler for that requirement is structurally identical, so it is likely
cross-SDK. Tracking separately.

Alex Wang added 4 commits August 10, 2026 22:36
Invocation-level plugin infos carried no view of the execution's
operation state, so Python plugins could not see what the JS SDK
exposes on every invocation hook.

Add to the invocation infos:
- InvocationInfo.operations, the checkpointed operation map converted
  to OperationInfo, on both invocation-start and invocation-end
- InvocationStartInfo.updated_operations, the subset named by the
  invocation input's UpdatedOperationIds, i.e. operations completed
  externally while the execution was suspended

Both are kw-only with empty-map defaults, so existing constructor
calls and plugins are unaffected. The end hook re-reads the map so it
reports end-of-invocation state rather than the start snapshot.

The map is snapshotted when the hook fires but converted to
OperationInfo only on first access, so an operation-heavy execution
does not pay per invocation for a view no plugin reads, while a
plugin that stashes the info still sees the state as of its hook.

Refs #617
The 10-19 handler is a canonical dump of each invocation hook's own
info. Now that the Python infos expose the operation maps, emit the
canonical operationsCount on both hooks and updatedOperationsCount on
invocation-start instead of omitting them.

Refs #617
Port the remaining JS invocation-hook payload surfaces so plugins can
record what an execution was given and what it produced. The Workflow
Insight plugin needs both to emit execution records.

- InvocationInfo.execution_input: the deserialized input event, the
  same object the durable handler receives, on both hooks
- InvocationEndInfo.execution_result: the serialized result from the
  invocation output, None when the invocation suspended or failed

Both are kw-only with None defaults, so this is purely additive.

These are experimental and out of GA conformance scope, so they are
marked EXPERIMENTAL in their docstrings and no conformance
requirement asserts them. Reaching them already requires the
plugins= parameter, which emits a FutureWarning.

Refs #616
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 had a problem deploying to ai-pr-review-runtime August 10, 2026 23:35 — with GitHub Actions Failure
@wangyb-A
wangyb-A had a problem deploying to ai-pr-review-runtime August 10, 2026 23:35 — with GitHub Actions Failure
@wangyb-A
wangyb-A marked this pull request as draft August 10, 2026 23:39
@wangyb-A

Copy link
Copy Markdown
Contributor Author

Superseded by #623, which splits this correctly: #623 is the SDK-only parity fix against main, and the conformance handler changes move to the handlers PR (#615), stacked on #623.

@wangyb-A wangyb-A closed this Aug 10, 2026
@wangyb-A
wangyb-A deleted the plugin-invocation-info-maps branch August 10, 2026 23:46
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.

1 participant