Skip to content

Pass a nested graph's omitted input defaults at the call site (v0.1.35) - #83

Open
Volv-G wants to merge 1 commit into
masterfrom
piforge/relevance-fanout/native-nested-graph-input-defaul-48c82a2
Open

Volv-G wants to merge 1 commit into
masterfrom
piforge/relevance-fanout/native-nested-graph-input-defaul-48c82a2

Conversation

@Volv-G

@Volv-G Volv-G commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

🤖 AI-assisted: implemented by a PiForge code-writer agent and reviewed by a human before merge.

Problem

Tangle's backend applies an input's default only for a container component and for a run's root graph. It never applies it for a graph component nested inside another graph. So when a parent task calls a nested graph and omits an argument for an input that declares a default, that input is unset inside the child. Any required consumer then fails the whole submit with HTTP 422:

Task has a required input X, but no upstream artifact and no default value

An optional consumer quietly falls back to its own default instead of the graph's. This hit discovery's Daily Pulse vLLM saved pipeline, where Relevance declares verbose with a default and passes it to a fan-out step whose verbose input is required.

Fix

In _process_subpipeline_children, right after _validate_subpipeline_inputs, the compiler now calls _materialize_child_input_defaults(parent_task, child_body). This writes each defaulted child input that the call omits into the parent task's arguments as a constant, using the compiled child body as the source of truth. As a result, the backend never has to apply a nested default.

  • An argument the caller passes always wins, whatever its value.
  • Inputs without a default (required ones, or optional ones with no default) stay unset.
  • Every nesting level is covered, because each artifact compiles its own subpipeline tasks.
  • A child sidecar shared by several calls is unchanged. Each call's own task gets its own arguments.
  • When a task has no arguments key yet, it is inserted right after componentRef, following the canonical task key order.
  • Missing-required and unknown-input errors still run against the arguments the caller actually passed.

This replaces a tangle-deploy compile hook that wrapped the private _validate_artifact.

Scope decision: hydration is unchanged

Graphs nested by other routes are not rewritten: a ref(url=...) to a graph YAML, a published graph component, or a saved pipeline. Hydration's contract is to resolve componentRefs and leave the rest of the document alone, and downstream tests rely on that byte-for-byte. If those routes need the same treatment, the right place is a pure transform at submit time in PipelineRunner.prepare_pipeline_for_run (after hydration, before validation), as a follow-up PR. The README states the limitation.

Tests

tests/test_nested_graph_input_defaults.py holds behaviour-level tests. Each one compiles and hydrates a pipeline, then works out the effective value each leaf container input receives under Tangle's rules (no nested defaults), rather than checking dict shapes. They cover:

  • an omitted input taking the child's default;
  • a passed value winning over the default;
  • an optional input without a default staying unset;
  • a required input without a default still being refused at compile time;
  • a shared child called twice, each call keeping its own arguments;
  • three nesting levels, each applying its own defaults;
  • the Daily Pulse shape, using graph_input(..., default=...).

Full local suite: passed. uv lock changes only the tangle-cli version line.

Version: 0.1.35.

Tangle applies an input's default for a container component and for a
run's root graph, never for a graph nested inside another graph. A
subpipeline(child)(...) call that omitted a defaulted child input left it
unset inside the child, and a required consumer failed the submit with
HTTP 422 ("Task has a required input ..., but no upstream artifact and no
default value").

The compiler now writes each omitted defaulted child input into the
parent task's arguments as a constant, from the compiled child body,
right after subpipeline input validation. An argument the caller passes
always wins, inputs without a default stay unset, and every nesting level
applies its own child's defaults. Hydration is unchanged: graphs nested
via ref(...), published graphs or saved pipelines are not rewritten.
@Volv-G
Volv-G requested a review from Ark-kun as a code owner November 3, 2026 17:32

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