Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Changelog

## [v0.10.1] - 2026-09-22

### 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`.

## [v0.10.0] - 2026-09-13

### Added
Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ make update - Upgrade dependencies via uv
make build - Build the wheels

make test - Run unit tests
make e2e-test - Run the live e2e legs (needs PIPELEX_E2E_BASE_URL + PIPELEX_API_KEY)
make test-with-prints - Run unit tests with prints
make t - Shorthand -> test
make tp - Shorthand -> test-with-prints
Expand Down Expand Up @@ -83,7 +84,7 @@ make li - Shorthand -> lock install
endef
export HELP

.PHONY: all help env env-verbose check-uv check-uv-verbose lock install update build test test-with-prints t tp gha-tests agent-test format lint pyright mypy pylint merge-check-ruff-format merge-check-ruff-lint merge-check-pyright merge-check-mypy merge-check-pylint check-unused-imports fix-unused-imports check-TODOs cleanderived cleanenv cleanall c cc li agent-check
.PHONY: all help env env-verbose check-uv check-uv-verbose lock install update build test e2e-test test-with-prints t tp gha-tests agent-test format lint pyright mypy pylint merge-check-ruff-format merge-check-ruff-lint merge-check-pyright merge-check-mypy merge-check-pylint check-unused-imports fix-unused-imports check-TODOs cleanderived cleanenv cleanall c cc li agent-check

all help:
@echo "$$HELP"
Expand Down Expand Up @@ -193,6 +194,11 @@ test: env
$(VENV_PYTEST) -o log_cli=true -o log_level=WARNING $(if $(filter 1,$(VERBOSE)),-v,$(if $(filter 2,$(VERBOSE)),-vv,$(if $(filter 3,$(VERBOSE)),-vvv,))); \
fi

e2e-test: env
$(call PRINT_TITLE,"Live e2e legs")
@echo "These legs run against a live platform; they skip cleanly when PIPELEX_E2E_BASE_URL and PIPELEX_API_KEY are unset."
$(VENV_PYTEST) tests/e2e -o log_cli=true -o log_level=WARNING -v

test-with-prints: env
$(call PRINT_TITLE,"Unit testing with prints")
@if [ -n "$(TEST)" ]; then \
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ There is no barrel import — package `__init__.py` files stay empty. Import eac

- **Client & construction** — `from pipelex_sdk.client import PipelexAPIClient, DEFAULT_API_BASE_URL, MthdsFile`
- **Run lifecycle types** — `from pipelex_sdk.runs import RunStatus, RunPublic, RunRead, RunResults, RunResultState, WaitForResultOptions, PollInfo`
- **Product wire models** — `from pipelex_sdk.product_models import UserProfile, MethodData, MethodWriteInput, Membership, MembershipsResponse, SubscriptionResponse, PlanView, InvoiceView, OnboardingSubmission, UploadInput, UploadedFile, PipelineRun, ...`
- **Product wire models** — `from pipelex_sdk.product_models import UserProfile, MethodData, MethodWriteInput, Membership, MembershipsResponse, SubscriptionResponse, PlanView, InvoiceView, OnboardingSubmission, UploadInput, UploadedFile, PipelineRun, ...`, with the catalog-source readers beside them: `method_source_to_contents` turns a fetched `MethodData.mthds` into the `mthds_contents` a run or a validate takes, and `MethodFile` / `parse_method_files` / `serialize_method_files` are the codec for a method's custom PipeFunc `python`.
- **Validation verdict types** — `from pipelex_sdk.validation_models import PipelexValidationResult, PipelexValidationReport, PipelexInvalidReport, ValidationErrorItem, SuggestedFix, VALIDATION_VIEW_INPUT_FORM, ...`
- **Codegen tree** — `from pipelex_sdk.codegen_writer import write_codegen_tree, CodegenTreeWriteReport` to write one, `from pipelex_sdk.codegen_check import run_codegen_check, CodegenCheckReport, CodegenDrift, DriftCategory` to verify one, with the format primitives in `pipelex_sdk.codegen_lock` (`CodegenLock`, `parse_lock`, `load_lock`, `validate_artifact_path`, ...) and `pipelex_sdk.codegen_stamp` (`STAMPABLE_SUFFIXES`, `is_stampable_artifact_path`, `compute_content_hash`, `parse_stamped`, ...)
- **Typed errors** — `from pipelex_sdk.errors import ApiResponseError, ApiUnreachableError, PipelineExecuteTimeoutError, PagingNotTerminatingError, RunFailedError, RunTimeoutError, RunLifecycleUnavailableError, RunStillRunningError, CodegenError, CodegenLockError, ...`
Expand All @@ -149,7 +149,7 @@ There is no barrel import — package `__init__.py` files stay empty. Import eac
## Development

```bash
make install # create the venv and install all extras (resolves `mthds` from ../mthds-python)
make install # create the venv and install all extras
make agent-check # fix-imports + format + lint + pyright + mypy
make agent-test # run the test suite quietly (prints only on failure)
make check # full gate: agent-check aggregate + unused-imports + pylint
Expand Down
Loading
Loading