Skip to content

.NET: Python: [Bug]: Declarative workflow IsBlank defaults ignore inputs the caller omitted #9729

Description

Description

In declarative workflows, a PowerFx default for an input the caller did not pass never takes effect. The simple_workflow sample sets

value: =If(IsBlank(inputs.name), "World", inputs.name)

but workflow.run({}) prints Hello, ! instead of Hello, World!.

The cause: when the inputs record has no name field, PowerFx fails to compile inputs.name (and Workflow.Inputs.name) with Name isn't valid. 'name' isn't recognized. DeclarativeWorkflowState.eval() treats that as an undefined variable and returns None for the whole expression, so the IsBlank default is never evaluated. The input only counts as blank when it is present with a blank value:

workflow.run(...) output
{} Hello, !
{"other": 1} Hello, !
{"name": None} Hello, World!
{"name": ""} Hello, World!
{"name": "Alice"} Hello, Alice!

invoke_function_tool/main.py works around this with the comment "both location and unit must be provided". The existing test test_if_with_isblank avoids the case by initializing with {"name": ""}.

Separately, the simple_workflow sample discards the result of its second ("Alice") run, so it prints nothing for that run.

Reproduction

With the .NET runtime available for PowerFx, run python/samples/03-workflows/declarative/simple_workflow/main.py, or:

state = DeclarativeWorkflowState(state_store)
state.initialize({"other": 1})
state.eval('=If(IsBlank(inputs.name), "World", inputs.name)')  # None, expected "World"

Expected behavior

An input the workflow references but the caller omitted behaves as blank, the same as passing it as None, so IsBlank-based defaults apply.

Proposed fix

In DeclarativeWorkflowState.eval(), find inputs.<name> / Workflow.Inputs.<name> references in the formula. Bind any the caller did not supply to blank in the PowerFx symbols for that evaluation only, without writing them to workflow state. Other undefined names keep today's None behavior. I have a PR ready, with a regression test that fails on main.

Package Versions

agent-framework-declarative from main at 1168c00, powerfx with .NET 10.0.12 runtime.

Python Version

3.13

AI Assistance

Claude Code helped find this by running the declarative samples locally, trace it to DeclarativeWorkflowState.eval(), and draft this report. I verified the behavior locally.

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 26, 2026
  2. changed the title [-]Python: [Bug]: Declarative workflow IsBlank defaults ignore inputs the caller omitted[/-] [+].NET: Python: [Bug]: Declarative workflow IsBlank defaults ignore inputs the caller omitted[/+] on Oct 26, 2026
  3. added
    needs-maintainer-triageUsage: [Issues], Target: all issues that the automated triage flow failed to process
    on Oct 26, 2026
  4. eavanvalkenburg commented on Nov 2, 2026

    @eavanvalkenburg
    Member

    Thanks for the reproduction. The team will consider owning omitted-input handling that lets declarative workflows apply useful blank/default expressions. This changes the currently documented legacy undefined-reference fallback, so the exact input-binding and compatibility contract must be agreed first. This is consideration, not an implementation or timing commitment. We will use existing #9730 and the namespace/lexical-scope review as planning input rather than start competing work.

  5. eavanvalkenburg commented on Nov 2, 2026

    @eavanvalkenburg
    Member

    /plan Plan the accepted core ownership of omitted-input blank/default binding, using existing #9730 and the current maintainer scope/alias findings. Reconcile this with eval documented legacy None-on-undefined-reference behavior: decide declared optional inputs versus referenced-but-undeclared inputs, missing/null/empty distinctions and compatibility for unrelated undefined names. Prefer supported engine/schema binding information over maintaining an approximate PowerFx parser. Preserve true global aliases versus With-local bindings/member names, independently resolved inputs and Workflow.Inputs records, quoted identifiers/comments/interpolated code, and no workflow-state mutation. Cover the two existing review regressions through native PowerFx and the public YAML path, plus missing/null/empty/provided values and unchanged namespace behavior. Do not blanket-rebind both aliases from one record. Keep the simple sample second-run output omission separate from the expression-policy change. Scope is the Python declarative wrapper; the .NET runtime dependency alone does not establish a .NET SDK bug. This is not minor-ready: retain both triage labels, stay unassigned and settle semantics before implementation approval.

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

    .NETUsage: [Issues, PRs], Target: .Netneeds-maintainer-triageUsage: [Issues], Target: all issues that the automated triage flow failed to processpythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions