Skip to content

Release v0.10.1 - #37

Merged
lchoquel merged 6 commits into
mainfrom
release/v0.10.1
Sep 22, 2026
Merged

lchoquel merged 6 commits into
mainfrom
release/v0.10.1

Conversation

@lchoquel

@lchoquel lchoquel commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Release v0.10.1

Bumps version from 0.10.0 to 0.10.1. Promotes dev → main: the Python run-results parity wave — working_memory, summarize_usage, the graph and I/O lifts with their docs page, the artifact stack and the bulk storage resolve — plus the stored-source reader and two fixes.

Changelog

Added

  • RunResults.working_memory, the run's whole memory declared on both paths. A completed run now hands back every named stuff it held when it finished — the inputs it was given, the intermediates it produced and the main output — on one field that reads the same whichever path ran, typed as the standard's DictWorkingMemoryAbstract imported from mthds rather than restated: root maps a stuff name onto its concept and content, aliases maps a role such as main_stuff onto one of those names, and every level stays extension-open so a runner's per-stuff extras survive. On the hosted path it is the working_memory.json artifact the platform relays as its own key, no longer an unnamed value riding model_extra; on the blocking path it is lifted off pipe_output.working_memory, which the standard declares required, so it is always set there. The None-versus-absent reading the other optional fields follow applies to it too, and download_artifacts walks the declared field for its working_memory scope. This closes the last run-results gap against @pipelex/sdk. See docs/run-results.md.
  • summarize_usage, one run-level reading of what a run consumed. pipelex_sdk.usage.summarize_usage(results) folds a completed run's tokens_usages / usage_assembly_error pair into a single UsageSummary — a state of records, no_inference or unavailable, the priced total in USD with cost_partial flagging a sum that mixes priced and unrated calls, the two additive token totals, the call count, the relayed assembly error and a by_pipe rollup ordered most expensive first, with the calls the runtime did not attribute grouped under a None pipe code. Every rule docs/run-usage.md states is applied in one place instead of re-derived by each consumer: an unrated call (cost of None) is kept apart from one priced at zero, only input and output are summed because the other categories are subsets, and an empty record list is a run that did no inference — a cost of 0 and zero tokens, never None. It is pure, so it needs no client and does no I/O. A results body that never carried tokens_usages raises the new FieldNotIncludedError rather than turning a key the read did not deliver into a run with no usage to show; a key relayed as null is a value and reads as unavailable. TokensUsageRecord.cost is now validated strict and finite, so a cost relayed as a string, a bool or a NaN fails the results body's parse instead of reaching a total as a number. This is the Python twin of @pipelex/sdk's summarizeUsage, with the same summary shape.
  • RunResults describes a run's graph and its data. Beside graph_spec it already carried, the results object now declares graph_assembly_error — the graph's twin of usage_assembly_error, non-None when the runner's graph assembly failed, which is the only thing that tells a broken graph apart from a run that produced none — and the three I/O artifacts that say what the graph's nodes hold: pipe_io_contracts, input_form and output_form, typed as the standard's PipeIOContracts, InputForm and OutputForm imported from mthds.protocol exactly as the validate report types them, built over the library the run executed against and keyed by namespaced pipe_ref, with pipe_io_artifacts_error beside them as their own "why is there none". Every one reads the same on both paths: the hosted results body relays each as an artifact, and the blocking path lifts each off pipe_output, unwrapping the runner's pipe_io_artifacts envelope onto the three sibling fields. The artifacts are closed shapes, so a member the pinned mthds does not define fails the parse of the whole results body — the same ruling the validate report follows. The hosted body relays neither error key yet, so on that path each is absent from model_fields_set rather than None, and that is the Python reading of the JS undefined. This is the Python half of what @pipelex/sdk shipped in 0.18.0 and 0.20.0.
  • docs/run-results.md — "Reading a run's results", a page walking every field of RunResults: how an absent key differs from a relayed null in Python, the run id as the durable handle, main_stuff with a worked get_run_result example, what graph_spec is and how to keep it, the graph and artifact error twins, the three I/O artifacts and the rule that the contracts and the output form are taken together, the usage pair, pipe_output, and the produced files whose signed public_url must not be stored. Linked from docs/architecture.md, whose parity section now names what the run-results surface still lacks against the JS SDK.
  • method_source_to_contents, the reader for a stored method's bundle source: pipelex_sdk.product_models.method_source_to_contents(method.mthds) turns the polymorphic string get_method hands back — the catalog [{name, content}] array the webapp editor writes, or a bare .mthds bundle as plain text — into the list[str] that run, start and validate take as mthds_contents. It never raises, and an empty list means the method carries no source rather than that reading it failed. It reads the array as the catalog form on key presence and then drops an entry whose content is not a non-blank string, keeping its siblings — the same reading the platform's own resolver and @pipelex/sdk apply to the same stored row, so one stored method reads the same way wherever it is read.
  • The artifact stack — a run's produced files, from its results to bytes on disk. pipelex_sdk.artifacts carries the download twin of prepare_inputs, in four layers a consumer can stop at: collect_artifacts(value) lists the pipelex-storage:// references inside any results value without touching the network, resolve_artifacts(uris) mints a fresh link for each through the platform's bulk route (POST /v1/resolve-storage-url/bulk, chunked at its bound, one verdict per reference with per-reference refusal as a value), fetch_artifact(uri) yields one bounded stream (a timeout, redirects refused, the byte cap enforced mid-stream, no credential of ours forwarded to the object store, the store's headers left neutral), and download_artifacts(run_id | results, dir_path=…) saves a whole run's files under a directory — by run id days after the run, or from a RunResults in hand — and answers a produced verdict naming every reference walked, errors included. Links are always minted fresh and never read off the content's expiring public_url, files are never overwritten, a partial file is unlinked on failure or cancellation, and the working_memory scope brings down the echoed inputs and intermediates too. The shapes, options and defaults live in pipelex_sdk.artifact_models, the typed failures in pipelex_sdk.errors (ArtifactOperationError and its subclasses, plus FieldNotIncludedError), and the whole contract is docs/artifact-download.md. This is the Python twin of what @pipelex/sdk shipped in 0.18.0.
  • resolve_storage_urls_bulk, one request for a whole list of storage references. The client gained the raw bulk wire call under the artifact stack: POST /v1/resolve-storage-url/bulk takes at most the route's bound of references and answers one item per reference in request order, duplicates included, a refused reference carrying its {code, detail} inside a 200. A run producing one file per page no longer costs one round trip per file, and resolve_storage_url stays for the caller with a single link to mint.

Fixed

  • The blocking path stops dropping the executed graph. Against a bare runner, start_and_wait now lifts pipe_output.graph_spec onto RunResults.graph_spec instead of writing None — the runner has always returned the graph there — so the field carries the same document whichever path ran.
  • A stored source nested too deeply to decode now fails as a ValidationError: parse_method_files converts the JSON decoder's RecursionError into the ValueError its contract documents, so MethodData's validator surfaces it as a pydantic.ValidationError like any other malformed response body instead of letting a bare RecursionError escape get_method past a caller's except ValidationError.

Closes L-260922-d9bfb5

🤖 Generated with Claude Code


Summary by cubic

Releases v0.10.1, promoting the run-results parity wave to main. RunResults now declares the run's whole memory, its graph and I/O artifacts, and the summarize_usage fold; the artifact stack downloads produced files through a new bulk storage route; and a stored method's source can be read back into the shape the runner expects.

New Features

  • RunResults.working_memory returns the run's full memory on both paths, and the graph and I/O artifacts (graph_assembly_error, pipe_io_contracts, input_form, output_form) now read the same whichever path ran.
  • summarize_usage folds a run's usage records into one UsageSummary with a priced total and per-pipe rollup; a body that never carried tokens_usages raises FieldNotIncludedError.
  • The artifact stack (collect_artifacts, resolve_artifacts, fetch_artifact, download_artifacts) saves a run's produced files to disk, with fresh links minted through the new bulk resolve_storage_urls_bulk route.
  • method_source_to_contents turns a stored MethodData.mthds — the catalog array or a plain .mthds bundle — into the mthds_contents list that run, start and validate take.
  • New docs/run-results.md and docs/artifact-download.md document the new surfaces.

Bug Fixes

  • The blocking path now lifts graph_spec off pipe_output instead of writing None.
  • A stored source nested too deeply for the JSON decoder now surfaces as a ValidationError instead of a bare RecursionError.

Closes L-260922-d9bfb5.

Written for commit d135454. Summary will update on new commits.

Review in cubic

lchoquel and others added 6 commits September 13, 2026 14:38
…as two shapes (#31)

`MethodData.mthds` has two at-rest shapes and the field does not say
which it holds — the catalog `[{name, content}]` array the webapp editor
writes, or a bare `.mthds` bundle as plain text — so a Python caller
holding one had to guess, and guessing wrong sends the two characters
`[]` to the runner as MTHDS source. `method_source_to_contents` resolves
it to the `list[str]` that `run`, `start` and `validate` take as
`mthds_contents`, bringing the Python SDK to the contract
`@pipelex/sdk`'s `methodSourceToContents` already holds.

It reads the array the way the server reads the same row: the catalog
form is recognized on key presence, and an entry whose `content` is not
a non-blank string is dropped while its siblings are kept — character
for character what the platform's resolver and `@pipelex/sdk` do. A
client-side reader of a server-stored field exists so a caller can do
locally what the platform does with that row, so the two must not
disagree. The decoder is shared with `parse_method_files`, which keeps
reading `MethodData.python` strictly, and now converts the JSON
decoder's `RecursionError` into the `ValueError` the contract documents.

Closes L-260907-28adb9

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds `method_source_to_contents` to resolve the two at-rest shapes of
`MethodData.mthds` — the catalog `[{name, content}]` array or a bare
`.mthds` bundle — into the `list[str]` that `run`, `start`, and
`validate` take as `mthds_contents`, so a caller no longer has to guess
which shape a stored row holds or send `[]` to the runner on a wrong
guess. Also converts the JSON decoder's `RecursionError` into
`ValueError` in `parse_method_files`, so a stored `python` nested too
deeply now fails as a `pydantic.ValidationError` instead of escaping
`get_method` past a caller's `except ValidationError`.

- Treats an array as the catalog form on key presence and drops entries
whose `content` is not a non-blank string, keeping siblings — matching
the platform's own resolver and `@pipelex/sdk` exactly; the earlier
stricter reading was ruled against because it disagreed with the code
that decides whether a stored method runs.
- Shares only the decoder and blankness rules with `parse_method_files`,
never raises, and reads a `None` field as "no source".

Closes L-260907-28adb9.

<sup>Written for commit 9f152bc.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Pipelex/pipelex-sdk-python/pull/31?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…docs page, Python (#33)

`RunResults` now declares `graph_assembly_error`, the three I/O
artifacts (`pipe_io_contracts`, `input_form`, `output_form`, typed by
import from `mthds.protocol` as closed shapes) and
`pipe_io_artifacts_error`, and the blocking path lifts the executed
graph and unwraps the runner's artifact envelope onto the same fields
the hosted path relays, so every field reads the same whichever path
ran. A new `docs/run-results.md` walks each field, with the
`model_fields_set` reading that keeps a key the hosted body did not
carry distinct from one relayed as null.

Closes L-260918-f1a51b
Closes L-260921-c9d9d9
Advances L-260921-dbd611

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_013EHNErfCtpGRo7RJDNgRdC


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
`RunResults` now carries the executed graph and a run's data description
on both the hosted and blocking paths, so every field reads the same
whichever path ran. Closes L-260918-f1a51b and L-260921-c9d9d9.

- Declares `graph_assembly_error`, the three I/O artifacts
(`pipe_io_contracts`, `input_form`, `output_form`, typed from
`mthds.protocol`) and `pipe_io_artifacts_error`; the blocking path lifts
each off `pipe_output`, unwrapping the runner's `pipe_io_artifacts`
envelope onto the three sibling fields.
- Fixes the blocking path dropping the executed graph — `start_and_wait`
now lifts `pipe_output.graph_spec` instead of writing `None`.
- Adds `docs/run-results.md`, covering each field and how a Python
reader distinguishes an absent key from one relayed as `null` via
`model_fields_set`.
- The I/O artifacts are closed shapes: a member the pinned `mthds` does
not define fails the parse of the whole results body with pydantic's
`ValidationError`, and on the blocking path a failed parse discards the
runner's response.

<sup>Written for commit 329f848.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Pipelex/pipelex-sdk-python/pull/33?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…ython twin (#34)

`summarize_usage` folds a completed run's `tokens_usages` /
`usage_assembly_error` pair into one `UsageSummary`, the Python twin of
the JS fold, with the empty-list reading of ruling D6 and a per-pipe
rollup. A body that never carried the key raises
`FieldNotIncludedError`, which Python can tell from a relayed null, and
the record's cost is now validated strict and finite so a coerced or
non-finite value fails the parse instead of reaching a total.

Closes L-260918-f9bc27
Advances L-260921-dbd611

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_013EHNErfCtpGRo7RJDNgRdC

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
`summarize_usage` folds a completed run's `tokens_usages` /
`usage_assembly_error` pair into one run-level `UsageSummary` — state,
priced USD total with a partial-cost flag, input/output token totals,
call count, relayed assembly error, and a per-pipe rollup ordered most
expensive first.

- Pure function: no I/O, no client, input untouched.
- An empty record list reads as a run that did no inference (cost `0`,
zero tokens); `None` stays reserved for unavailable or unrated calls.
- A results body that never carried `tokens_usages` raises the new
`FieldNotIncludedError` instead of reading as unavailable, since
`model_fields_set` tells an absent key from a relayed `null`.
- `TokensUsageRecord.cost` is now validated strict and finite, so a
coerced or non-finite value fails the parse rather than reaching a
total.

<sup>Written for commit 78ba23b.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Pipelex/pipelex-sdk-python/pull/34?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…hon twin (#35)

The Python artifact stack, the twin of the JS one over httpx and
`asyncio`: `collect_artifacts` walks a run's results for storage
references, `resolve_artifacts` signs them through the bulk route in
chunks, `fetch_artifact` streams one within bounds, and
`download_artifacts` saves a scope's files under a byte budget with
typed per-item errors and a verdict. The live e2e leg is written and
gated on credentials this branch did not have, so it skips here.

Closes L-260918-f3342c
Advances L-260921-dbd611

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds the Python artifact stack, the download twin of `prepare_inputs`:
`collect_artifacts` walks a run's results for `pipelex-storage://`
references offline, `resolve_artifacts` mints fresh links via the bulk
route in chunks, `fetch_artifact` streams one reference under a byte
cap, and `download_artifacts` saves a scope's files under a byte budget
with a typed per-item verdict. Links are always minted fresh, no client
credentials reach the object store, files are never overwritten, and
partial files are unlinked on failure or cancellation. It mirrors
`@pipelex/sdk`'s 0.18.0 contract. Closes L-260918-f3342c and advances
L-260921-dbd611.

**Migration**
- Bare `pytest` and `make test` now collect only `tests/unit`; the live
e2e leg runs via `make e2e-test` and skips itself without
`PIPELEX_E2E_BASE_URL` and `PIPELEX_API_KEY`.

<sup>Written for commit aea0099.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Pipelex/pipelex-sdk-python/pull/35?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…, Python twin (#36)

`RunResults` now declares `working_memory`, typed as the standard's
`DictWorkingMemoryAbstract` imported from `mthds`, so a completed run's
whole memory reads the same on both paths: the hosted path relays the
`working_memory.json` artifact as its own key instead of leaving it on
`model_extra`, and the blocking path lifts it off
`pipe_output.working_memory`. The download stack's `working_memory`
scope reads the declared field, and the run-results, architecture and
artifact-download pages record the run-results surface at parity with
`@pipelex/sdk`.

Closes L-260918-7c9a2c
Advances L-260921-dbd611

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Declares `working_memory` on `RunResults`, typed as the standard's
`DictWorkingMemoryAbstract` imported from `mthds`, so a completed run's
whole memory reads the same whichever path ran.

- The hosted path relays the `working_memory.json` artifact as its own
key instead of leaving it on `model_extra`; the blocking path lifts it
off `pipe_output.working_memory`, which the standard declares required.
- `download_artifacts`'s `working_memory` scope now reads the declared
field, and the run-results, architecture, and artifact-download pages
track the surface at parity with `@pipelex/sdk`.

Closes L-260918-7c9a2c.

<sup>Written for commit d1893d3.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/Pipelex/pipelex-sdk-python/pull/36?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@lchoquel
lchoquel merged commit c532ab3 into main Sep 22, 2026
20 checks passed
@lchoquel
lchoquel deleted the release/v0.10.1 branch September 22, 2026 01:13
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant