Skip to content

Release v0.3.0: resolved main_stuff on both execution modes#6

Merged
lchoquel merged 4 commits into
mainfrom
release/v0.3.0
Jul 5, 2026
Merged

Release v0.3.0: resolved main_stuff on both execution modes#6
lchoquel merged 4 commits into
mainfrom
release/v0.3.0

Conversation

@lchoquel

@lchoquel lchoquel commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

  • One output accessor across both execution modes: result.main_stuff. RunResults.main_stuff is now required and non-null for a completed run (was Any = None). execute() now returns a PipelexExecuteResult — a DictRunResultExecute subtype exposing a resolved .main_stuff accessor, so a blocking result reads its output the same way a durable RunResults does. No main_stuff or pipe_output fallback, no working-memory spelunking, in application code.
  • MissingMainStuffError (new, extends PipelineRequestError, carries run_id): raised when a completed run cannot deliver a main stuff (hosted 200 with null main_stuff, or a blocking response whose main_stuff_name names nothing in the returned working memory). A falsy-but-present main stuff (empty list, 0) is valid output and does not raise.
  • main_stuff_name is declared as a typed field on PipelexExecuteResult (not read out of model_extra) — the Pipelex-branded subtype owns the concept; the neutral mthds wire model correctly leaves it in the extension bag.

This is the SDK half of a coordinated breaking change also landing in pipelex-sdk-js, pipelex-starter-python, and pipelex-starter-js (this repo's downstream starter is pipelex-starter-python release/v0.11.0, currently git-pinned to this branch).

Why

Triggered by verifying the client-repo impact of pipelex v0.37.0 / pipelex-api v0.7.0 (PipeParallel always-combines + the main-stuff invariant). The breaking change itself forces no client changes — investigation in the workspace-root TODOS.md covers all six client repos. This PR is the approved follow-on enhancement: since a completed run is now guaranteed to deliver a main stuff, the SDK resolves it for the caller instead of exposing the locator (main_stuff_name) and making the caller do the lookup.

Test plan

  • make agent-check && make agent-test green
  • New unit tests: blocking-resolves-main_stuff, blocking-raises-when-unlocatable, hosted-null-raises
  • CHANGELOG.md, README.md, docs/architecture.md updated

🤖 Generated with Claude Code

https://claude.ai/code/session_01PibWhJ6mPWdrkMqZATZpkJ

lchoquel and others added 3 commits July 5, 2026 12:31
RunResults.main_stuff is now required and non-null for a completed run. On
the hosted path it is the main_stuff.json S3 artifact; on the blocking path
the SDK resolves working_memory.root[main_stuff_name].content from the execute
response, so both paths deliver the same content shape and consumers read
results.main_stuff directly (no more main_stuff-or-pipe_output fallback / shape
guessing). pipe_output stays as the supplementary full working memory (blocking
path only).

A completed run that cannot deliver a main stuff now raises the new
MissingMainStuffError: a hosted 200 whose main_stuff is null, or a blocking
response whose main_stuff_name names no working-memory root stuff. A falsy but
present main stuff (empty list, 0) is a valid output and does not raise.

Breaking: main_stuff was optional (Any = None). Bump 0.2.0 -> 0.3.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PibWhJ6mPWdrkMqZATZpkJ
… result

execute() now returns a PipelexExecuteResult (a DictRunResultExecute subtype)
that exposes a resolved .main_stuff accessor, so blocking and durable results
read their output the same way (result.main_stuff) with no working-memory
spelunking. The internal blocking->RunResults mapper reads the same accessor,
single-sourcing the resolution. Lives in its own module (execute_result.py) to
avoid an errors<->runs import cycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PibWhJ6mPWdrkMqZATZpkJ
…del_extra dig

PipelexExecuteResult is the Pipelex-branded layer, so main_stuff_name belongs as
a declared field there rather than riding the neutral base's extension bag. Removes
the model_extra lookup + isinstance re-narrowing; the .main_stuff property now reads
self.main_stuff_name directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PibWhJ6mPWdrkMqZATZpkJ
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown

Greptile Summary

This PR unifies completed-run output access around result.main_stuff. The main changes are:

  • execute() now returns PipelexExecuteResult with a resolved main_stuff accessor.
  • RunResults.main_stuff is now required for completed runs.
  • MissingMainStuffError reports completed runs that cannot deliver main output.
  • Tests and docs were updated for the new v0.3.0 result shape.

Confidence Score: 4/5

Mostly safe, with one contained API error-handling issue to fix.

The main output-resolution paths are covered, but a hosted completed result that omits main_stuff raises a raw validation error instead of the new typed error.

pipelex_sdk/client.py

T-Rex T-Rex Logs

What T-Rex did

  • I reproduced the missing hosted main_stuff scenario using the mocked hosted lifecycle endpoints and confirmed that a ValidationError occurs before the typed missing-output check runs.
  • The focused pytest run captured a traceback showing RunResults.model_validate raised Field required for main_stuff, instead of triggering the MissingMainStuffError.
  • Created a focused pytest harness artifact to reproduce the omitted hosted main_stuff response.
  • Added a verbose pytest output artifact that documents the Pydantic ValidationError encountered instead of MissingMainStuffError.
  • Verification via Pytest and Ruff confirms the contract validation checks pass: Pytest shows 17 tests passed with exit code 0, and Ruff reports all checks passed with exit code 0.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
pipelex_sdk/client.py Updates execute and run result paths to expose resolved main_stuff; hosted missing-field handling should preserve MissingMainStuffError.
pipelex_sdk/execute_result.py Adds PipelexExecuteResult to resolve blocking main_stuff from working memory and raise MissingMainStuffError for absent named stuff.
pipelex_sdk/runs.py Makes RunResults.main_stuff required and documents the unified completed-run result shape.
pipelex_sdk/errors.py Adds MissingMainStuffError carrying the run id for completed runs without deliverable main output.
tests/unit/test_client_execute.py Covers blocking execute main_stuff resolution and unlocatable main stuff errors.
tests/unit/test_client_run_fallback.py Covers hosted null main_stuff and blocking fallback resolution; missing hosted main_stuff key remains uncovered.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Caller
participant Client as PipelexAPIClient
participant Hosted as Hosted /v1/runs
participant Runner as Bare /v1/execute

Caller->>Client: start_and_wait(...)
Client->>Client: supports_run_lifecycle()
alt hosted lifecycle
    Client->>Hosted: POST /v1/start
    Hosted-->>Client: pipeline_run_id
    Client->>Hosted: "GET /v1/runs/{id}/results"
    Hosted-->>Client: RunResults(main_stuff)
    Client-->>Caller: result.main_stuff
else bare runner fallback
    Client->>Runner: POST /v1/execute
    Runner-->>Client: pipe_output + main_stuff_name
    Client->>Client: PipelexExecuteResult.main_stuff resolves root[name].content
    Client-->>Caller: RunResults(main_stuff, pipe_output)
end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Caller
participant Client as PipelexAPIClient
participant Hosted as Hosted /v1/runs
participant Runner as Bare /v1/execute

Caller->>Client: start_and_wait(...)
Client->>Client: supports_run_lifecycle()
alt hosted lifecycle
    Client->>Hosted: POST /v1/start
    Hosted-->>Client: pipeline_run_id
    Client->>Hosted: "GET /v1/runs/{id}/results"
    Hosted-->>Client: RunResults(main_stuff)
    Client-->>Caller: result.main_stuff
else bare runner fallback
    Client->>Runner: POST /v1/execute
    Runner-->>Client: pipe_output + main_stuff_name
    Client->>Client: PipelexExecuteResult.main_stuff resolves root[name].content
    Client-->>Caller: RunResults(main_stuff, pipe_output)
end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
pipelex_sdk/client.py:535-536
**Preserve typed error**
`RunResults.model_validate(response.json())` runs before the absence check, so a completed `200` response that omits `main_stuff` raises a raw Pydantic validation error instead of `MissingMainStuffError`. That breaks the new typed-error contract for the same un-deliverable-output condition as `main_stuff: null`; check the decoded payload for a missing or null key before validating into the now-required `RunResults` model.

Reviews (1): Last reviewed commit: "refactor(execute): declare main_stuff_na..." | Re-trigger Greptile

Comment thread pipelex_sdk/client.py Outdated
…n_stuff

`RunResults.model_validate` ran before the main_stuff absence check, so a
completed 200 that omits the key raised a raw Pydantic ValidationError
instead of the typed MissingMainStuffError — while `main_stuff: null` was
correctly caught. Inspect the decoded payload first so both the missing-key
and explicit-null cases surface the same typed error for the same
un-deliverable-output condition; a falsy-but-present main stuff still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTtTK4ViwgKLDbZQRHJfen
@lchoquel lchoquel merged commit 2c2cafc into main Jul 5, 2026
18 of 19 checks passed
@lchoquel lchoquel deleted the release/v0.3.0 branch July 5, 2026 14:29
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