Conversation
…imitive
Adds a third dispatch arm distinct from sync return and TaskHandoff:
``ctx.handoff_to_workflow(fn)`` for adopter-owned external workflows
that complete on their own schedule (human queue review, batch jobs,
Airflow DAGs, ML pipelines, scheduled cron).
Naming choice: ``handoff_to_workflow`` over ``handoff_to_human``.
The primitive isn't human-specific — the external completer could
be a person, a nightly batch job, an ML pipeline, or any
adopter-owned system that calls back via ``registry.complete()``
later. ``workflow`` matches industry-standard vocabulary (Camunda,
Temporal, Step Functions) and pairs symmetrically with the existing
``handoff_to_task`` (framework's task vs. adopter's workflow).
Mental model:
* Sync return — Seller answers immediately.
* ``handoff_to_task(fn)`` — Framework runs ``fn`` in background;
``fn`` returns terminal artifact within seconds-to-minutes.
* ``handoff_to_workflow(fn)`` — ``fn`` runs ONCE to register work
into adopter's external system; framework persists ``submitted``
state and returns wire envelope; adopter's external system later
calls ``registry.complete()`` / ``registry.fail()`` directly when
the work finishes (hours-to-days).
Wire-shape parity: all three project to ``{task_id, status:
'submitted'}`` for the async paths. Buyers can't tell which path
the seller took.
New surfaces:
* ``adcp.decisioning.WorkflowHandoff`` marker class +
``is_workflow_handoff()`` type-identity dispatch helper.
* ``RequestContext.handoff_to_workflow(fn)`` method.
* ``TaskRegistry.discard(task_id)`` Protocol method +
``InMemoryTaskRegistry.discard()`` implementation. Used by the
workflow projection's rollback path — if enqueue fn raises, the
just-allocated task_id is removed so the buyer never sees an
orphan id.
* ``adcp.decisioning.dispatch._project_workflow_handoff()``.
Allocates task_id via ``registry.issue``, calls enqueue fn (sync
via executor + contextvars snapshot, async awaited inline), rolls
back via ``registry.discard`` on ``BaseException``, returns
Submitted envelope. NO background coroutine.
* ``_invoke_platform_method`` checks both markers; routes to the
matching projection.
TaskHandoff docstring updated: drops the ``handoff_to_human`` v4.5.0
forward-promise (now obsolete — ``handoff_to_workflow`` ships in
4.4.0). Points adopters at the new primitive for queued-approval
flows.
Public exports: ``WorkflowHandoff`` added to
``adcp.decisioning.__all__``.
Test coverage in ``tests/test_decisioning_workflow_handoff.py`` (14
new tests):
* Marker shape: type-identity dispatch, subclass rejection.
* Wire-shape parity: Submitted envelope identical to TaskHandoff.
* Sync + async enqueue both supported.
* Rollback: enqueue exception → ``registry.discard()`` → no orphan
task_id reaches the buyer (sync + async paths).
* Registry persists ``submitted`` state correctly.
* External completion via ``registry.complete()`` / ``registry.fail()``.
* End-to-end via ``_invoke_platform_method``.
* No background coroutine spawned (distinct from TaskHandoff).
* Public exports present.
One existing test updated:
``tests/test_decisioning_task_registry.py::test_custom_registry_satisfies_protocol_via_duck_typing``
declares ``discard`` on its stub since the Protocol added it.
2266 tests pass (up from 2252).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third dispatch arm: `ctx.handoff_to_workflow(fn)` for adopter-owned external workflows that complete on their own schedule (human queue review, batch jobs, Airflow DAGs, ML pipelines, scheduled cron).
Naming: `handoff_to_workflow` over `handoff_to_human` — the primitive isn't human-specific; the external completer could be a person, batch job, ML pipeline, or any system that calls back via `registry.complete()`. Matches Camunda / Temporal / Step Functions vocabulary and pairs symmetrically with `handoff_to_task`.
Mental model:
Wire-shape parity: all three async paths project to `{task_id, status: 'submitted'}`. Buyers can't tell which path the seller took.
Rollback: if the enqueue fn raises, the just-allocated task_id is discarded from the registry via the new `TaskRegistry.discard()` method. Buyer never sees a Submitted envelope referencing an orphan id.
TaskHandoff docstring update: drops the `handoff_to_human` v4.5.0 forward-promise (now obsolete since the primitive ships in 4.4.0); points adopters at `handoff_to_workflow` for queued-approval flows.
New surfaces
Test plan
Release plan
10th PR accumulating into the held release-please PR #328 alongside foundation (#316), codemod ergonomics (#329), parity rename (#330), F12 (#331), and the four breadth-sprint specialism batches (#332–#335). Ships in 4.4.0 once salesagent validates.
🤖 Generated with Claude Code