Skip to content

Return plan_result to the client #1349

Closed

Description

@DominicOram

In bluesky I can run the following:

def my_plan():
    yield from ...    
    return 4

RE = RunEngine()
assert RE.plan_result == 4

However, if I run the same plan through BlueAPI there is no way to get the result.

This would really aide in moving small incremental jobs from GDA to Bluesky.

Acceptance Criteria

  • I can get the plan result from the client
  • The format for this in GDA is clean e.g. something like plan_result = run_plan(...)

Activity

  1. tpoliaw commented on Jan 27, 2026

    @tpoliaw
    Contributor

    Looking into how this works in bluesky, it looks like plan_result is available on a returned RunEngineResult rather than on the run engine and only then if the run_engine is configured to return a result.

    RE = RunEngine(call_returns_result=True)
    res = RE(my_plan())
    assert res.plan_result == 4

    We are not currently setting call_returns_result but as far as I can see, we're not using the returned plan UUIDs either so it shouldn't break anything to set it now.

  2. tpoliaw commented on Jan 27, 2026

    @tpoliaw
    Contributor

    Not sure how to handle the case where a plan returns something that isn't JSON serializable. Is returning None an option? An error message (that risks being a confusion if a plan returns a string)? Wrap everything in new wrapper types?

    class TaskResult(BaseModel):
        result: Any
    
    class UnserializableResult(BaseModel):
        result_type: String
    
    class TaskStatus(BlueapiBaseModel): # existing type
        ... # previous fields
        result: TaskResult | UnserializableResult | None = None
  3. tpoliaw commented on Jan 27, 2026

    @tpoliaw
    Contributor

    Will add a proof-of-concept PR that returns results in the task_status of the WorkerEvent that is sent when the plan completes and crashes the server if it's not serializable.

  4. added a commit that references this issue on Jan 27, 2026
  5. linked a pull request that will close this issuefeat: Include plan outcome in WorkerEvents #1357on Jan 27, 2026
  6. tpoliaw commented on Jan 27, 2026

    @tpoliaw
    Contributor

    This may all be changing (soon?) if we move away from using the message bus (see #1326). In that scenario, we probably wouldn't have WorkerEvents and the final event sent over the stream could be the plan result.

  7. tpoliaw commented on Jan 28, 2026

    @tpoliaw
    Contributor

    The problem with having the TaskResult/UnserializableResult types is that we don't know ahead of time that a type isn't serializable. The serialization is done in another library (albeit a DLS one (bluesky-stomp)) with some customisation so we can't realistically try serialising and see what happens.

    We could potentially limit return types to pydantic models + python builtins? Anything that pydantic can create a TypeAdapter from?

  8. DominicOram commented on Jan 28, 2026

    @DominicOram
    ContributorAuthor

    Is returning None an option?

    I think this is fine.

    We could potentially limit return types to pydantic models + python builtins? Anything that pydantic can create a TypeAdapter from?

    Yh, this is fine. I would even be tempted to limit this to just builtins. I really envisaged this "feature" as a bit of a stop gap, to get some basic data out of bluesky. If you're trying to return complex types then you should be using other mechanisms (e.g. the event stream)

  9. added this to the Blueapi 2.0.0 milestone on Jan 28, 2026
  10. removed this from the Blueapi 2.0.0 milestone on Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions