Skip to content

Add experimental activity sessions and worker session options - #1979

Draft
THardy98 wants to merge 5 commits into
mainfrom
dev/sticky-activities
Draft

THardy98 wants to merge 5 commits into
mainfrom
dev/sticky-activities

Conversation

@THardy98

@THardy98 THardy98 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What was changed

This change introduces a minimal subset of the planned Sessions API as specified in temporalio/api#885 (commit d183f54c642fe320218bc3bf8592b2533540f050).

The intention with the Sessions API is to address worker affinity use cases.

Changes:

The current API supports:

  • worker options to enable a worker to serve sessions:
    Worker(session_config=WorkerSessionConfig(...))

  • worker session options include a "single session" mode that allows it to serve only a single session
    SingleSessionAdmission(stable_identity=...)
    the stable identity lets the session bind to a particular identity (i.e. in the event the worker dies and a new worker with the same stable identity is provided).

  • activity session options that allow an activity to run in the same session as its workflow:
    workflow.activity_session(ActivitySessionOptions(placement=UseWorkflowSession()))
    this co-locates a workflow's workflow and activity tasks on the same worker.
    Note: the activity must run on the same task queue as the workflow (otherwise, it will be routed to its own task queue)

  • Replayer allows Session workflows to replay only on session-enabled workers

Usage:

The API uses a context manager to declare session scope, example usage:

@workflow.run
async def run(self, prompt: str) -> str:
    # The scope covers activities started inside it, including plugin calls.
    with workflow.activity_session(ActivitySessionOptions(placement=UseWorkflowSession())):

        # In session: inside the scope.
        result = await workflow.execute_activity(
            run_tool, prompt, start_to_close_timeout=timedelta(minutes=5),
        )

        # Different task queue: the scope is skipped, normal routing.
        await workflow.execute_activity(
            gpu_infer, result, task_queue="gpu",
            start_to_close_timeout=timedelta(minutes=10),
        )

Note
This PR is dependent on feature branches (PRs):

Why?

An initial Python API for the Sessions API (activity affinity and single-session workers), to serve worker affinity use cases.

@THardy98
THardy98 force-pushed the dev/sticky-activities branch 3 times, most recently from 919da29 to 3f4b21b Compare October 21, 2026 14:02
@THardy98 THardy98 changed the title Expose experimental activity sessions and worker affinity Add experimental activity sessions and worker session options Oct 21, 2026
@THardy98
THardy98 force-pushed the dev/sticky-activities branch 5 times, most recently from 07fd242 to 5b870cc Compare October 22, 2026 01:38
@THardy98
THardy98 force-pushed the dev/sticky-activities branch from 0234cfc to b6d76ef Compare October 28, 2026 20:58
Pin Core to the session stack (temporalio/sdk-rust#1687 at 6e88cb53),
which adopts the session API from temporalio/api#885, regenerate the Python
protos, refresh the bridge lockfile, and import Core's release notes.
`Worker(session_config=WorkerSessionConfig(...))` lets a worker serve
sessions (it also polls its sticky queue for activities).
`SingleSessionAdmission(stable_identity=...)` dedicates the worker to one
workflow for its lifetime; Core then polls only its sticky queues and
excludes it from eager workflow start. Session workers need fixed-size
activity slots: two for paired polling, one for single-session.
Activities started inside
`workflow.activity_session(ActivitySessionOptions(placement=UseWorkflowSession()))`
that target the workflow's task queue run on the workflow's worker; others
use ordinary routing. The scope is a ContextVar read in the caller's task:
it nests, follows tasks created inside it, and doesn't reach signal or
update handlers unless they open their own scope. The resolved options are
on `StartActivityInput.session_options` for interceptors.

Core validates placement (session worker, workflow's task queue), including
during replay, so Python only resolves the scope into the command.
`ActivitySessionOptions`, `ActivitySessionPlacement` and `UseWorkflowSession`
live in temporalio.common.
Session workflows only run, including replay, on workers that serve
sessions; replaying elsewhere would silently drop the affinity their
activities expect, so Core fails the workflow task. The Replayer accepts the
same `session_config` as `Worker` and passes it to Core's replay worker.
Core requires remote activities alongside session options; the replay
worker still runs workflow-only, and `admission` has no effect since it
doesn't poll.
@THardy98
THardy98 force-pushed the dev/sticky-activities branch from b6d76ef to ffb0b6b Compare October 28, 2026 21:08
Cover scope resolution (nesting, explicit and other task queues, tasks
created in a scope, scoped and unscoped signal handlers), Core rejecting a
session activity on a non-session worker, `activity_session` outside a
workflow, and the Core-enforced worker rules: nonzero workflow cache,
non-empty stable identity, and no eager workflow start for single-session
workers. These run against the stock test server.

This branch has not been deployed

No deployments
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