Skip to content

Changelog

[v0.59.0] - 2026-09-16

Added

  • pretty_print_mode in [runtime.log] (Breaking): rich (the default), poor or silent, applied at boot beside log_mode, so a host with no console turns the "Output of pipe" panels off in configuration rather than in code; a silent printer builds no Rich renderable at all. Boot now replaces a PrettyPrinter.mode assigned in code before it, so set the key instead, and teardown restores whatever mode the process held before that boot. pipelex doctor applies the key too.

Changed

  • Remote inputs are checked the way the web checks them (Breaking): an http(s) URL on a Document or Image input is only checked for syntax when the inputs are shaped, and refused with PipelineInputUrlInvalidError when it does not parse. Whether the resource exists is decided by the operator that fetches it, which raises a caller-facing RemoteFileFetchError naming the URL and the answer it got, instead of a raw httpx error. The pre-flight HEAD probe is gone: on the hosted runner a host that stalled it could end a run TIMED_OUT with no error. A local file path must still exist before the run starts.
  • The User-Agent is Pipelex/<version>: the (https://pipelex.com) suffix is gone, because the crawler-style URL in parentheses is what bot walls key on, and sites that stalled the old string serve the new one promptly.

Fixed

  • Poor pretty-print mode: a panel whose title is wider than the terminal no longer hangs the run in an endless wrapping loop, which every "Output of pipe" panel did on an 80-column headless console; an over-wide title is elided instead. Titles print without their Rich markup, a title in square brackets no longer raises, and an output prints as its plain rendering rather than a Rich object's repr.
  • A trace event log the current models refuse is no longer read as an empty one: an event written by a version whose event shape this one no longer accepts now makes the read refuse with a count and a reason, instead of being skipped and the remainder reported as a success; such a run reports neither its graph nor its usage and cost total, each with its assembly error set. A line that is not JSON, left by a crash mid-write, is still skipped with a warning. pipelex graph render gives the same diagnosis for a graph spec saved by an earlier version instead of a raw traceback.

Removed

  • The text and HTML renderings of traced data (Breaking): stuff_text_content and stuff_html_content under [interpreter.pipeline_execution.graph.data_inclusion] are gone, along with a graph IOSpec's data_text and data_html and the unused PrettyPrinter.pretty_html and PrettyRenderable.rendered_pretty_html. Runs no longer pay for rendering every traced input and output on the execution path, and the Mermaid graph page now shows a data node's JSON, with a preview for image and PDF content. A graph spec saved by an earlier version is refused when read back; run the pipeline again to get one in the current shape. Migration: run pipelex migrate — ledger entry pipelex-config@4 deletes both keys from ~/.pipelex/ and from every project .pipelex/ file, keeping one timestamped backup per file.

Security

  • escape_script_tag escapes every <: the filter only matched the literal </script>, so </script > and </script/> closed an embedded JSON block and let the rest of the value parse as live HTML. Every < is now escaped as \u003c, which guards the graph and stuff viewer pages that embed model-generated traced content.

[v0.58.0] - 2026-09-14

Changed

  • License (Breaking): pipelex is now licensed under the Elastic License 2.0 (ELv2) instead of MIT, and the package metadata declares the SPDX expression Elastic-2.0 in place of the MIT license classifier, which makes hatchling 1.27 or later a requirement for building from source; every earlier version, up to and including v0.57.0, stays under MIT. ELv2 is source-available: you may embed pipelex in your own products, including services you offer to others whose features run your methods, and in your internal tools, while its main limitation rules out hosting a service that runs methods for others, whether they send the methods or pick them from a catalog. LICENSE carries the full terms, including its conditions on notices and redistribution, and the License page on docs.pipelex.com explains how Pipelex reads them.

Security

  • Docs site built on mkdocs-material 9.7.7: the docs extra now requires mkdocs-material>=9.7.7, which closes a DOM XSS in the theme's search suggestions (GHSA-xvg9-69gf-fjrf) that the published site shipped. The pull-request documentation check now builds from uv.lock like the deploy does, instead of from its own hand-pinned theme versions.

[v0.57.0] - 2026-09-07

Added

  • A run's results carry the I/O artifacts that describe its data: beside graphspec.json, and gated by the same graphspec_json inclusion flag, a run now writes pipe_io_contracts.json, input_form.json and output_form.json — the validation report's own artifacts under the standard's names, keyed by namespaced pipe_ref for every pipe of the library the run executed against. Until now only /validate produced them, so a consumer of a run's graph either took the no-data floor or paired the graph with a validation of some other bundle text, and a graph viewer shows a data node's value only when it holds the contracts and the output form. They ride on PipeOutput.pipe_io_artifacts beside graph_spec, with a build failure of any kind landing on pipe_io_artifacts_error rather than failing the run or skipping its delivery, and the SPI payload carries them as pipe_io_artifacts_dump. pipelex run, pipelex-agent run and the direct-mode delivery executor all write them, while a run hosted on our Temporal plugin still writes graphspec.json without its siblings. See Execution Graph Tracing.
  • The --version handshake reports three numbers (Breaking): pipelex --version and pipelex-agent --version now print the runtime version, the MTHDS Protocol version and the MTHDS standard version, one <label> <version> line each, instead of the runtime version alone. The three move on independent cadences under the standard's own versioning rule, so none can be inferred from another, and the two MTHDS numbers are read from the mthds package rather than restated here. The first line keeps its historical <program> <version> form, so a consumer that matches a version out of the text is unaffected — but one that treats the whole of stdout as a single version string now holds three lines, so match on the label rather than on the shape of the output. See CLI Reference.

Changed

  • The MTHDS standard version this runtime implements is now 2.0.0 (Breaking): mthds moves to ==0.14.0, whose MTHDS_STANDARD_VERSION was cut from 1.0.0 to 2.0.0 — the standard's first cut under its own versioning rule, accounting for breaking changes to the native set and to the manifest, lock and resolution rules that had already shipped unversioned. Two things follow for users, and no pipelex line caused either: every normalized crate this engine emits is stamped 2.0.0 instead of 1.0.0 (crate fingerprints are unaffected, because mthds_version is excluded from the hashed payload by design), and a METHODS.toml whose mthds_version pinned the old major, such as ^1.0.0, now warns where it did not before, while a plain floor like >=1.0.0 is still satisfied. The protocol version is unchanged at 0.6.0, and the pin stays exact, so a consumer depending on mthds directly must move in step.
  • The Commands section of the shipped agent rules is a compact list: pipelex/kit/agent_rules/commands.md drops its long-form headings and code blocks for one bullet per gate — the target, its alias, when to run it and where the full rationale lives — so the CLAUDE.md that make rules generates from it costs a session a third of what it did. It also now records the one thing a session could not learn from the file itself: that CLAUDE.md and AGENTS.md are generated, that make rules is how they move, and that make check-rules is a CI gate make agent-check does not run.

Fixed

  • The codegen stamp reference no longer understates what moves a fingerprint: Codegen Projections said a stamp moves only when a method's effective type surface changes, which would have a reader mistake an ordinary restamp for drift and expect a prompt edit to leave the generated tree alone. The crate fingerprint is computed over the whole normalized crate — concepts, pipes and domains — so an edited system_prompt or a swapped model restamps every generated file exactly as a renamed concept field does, with the body below the fence byte-identical. content_hash, in the stamp header and in the lock's artifacts[] entries, is what distinguishes that harmless restamp from a real change to the generated code.
  • Compound version constraints written the documented way now parse: parse_constraint stripped no whitespace around the comma that ANDs a compound constraint's clauses, so >=1.0.0, <2.0.0 — the exact spelling of the MTHDS standard's own example — was rejected while the identical >=1.0.0,<2.0.0 parsed. A manifest's mthds_version written that way therefore got no compatibility check at all, since the load path degrades an unparseable constraint to a log line. A malformed compound such as >=1.0.0,,<2.0.0 still fails, because normalizing whitespace must not repair a broken constraint.

[v0.56.0] - 2026-09-03

Added

  • Pipelex Manifold (private beta): pipelex_manifold joins pipelex_gateway in backends.toml as a second Pipelex-operated inference backend, with a matching all_pipelex_manifold routing profile. It ships disabled; since both services share model handles, switching the profile is the whole change. A new provider package supplies the manifold sdk set (manifold_completions, manifold_responses, manifold_img_gen, manifold_extract, manifold_search), the backend declares the service origin with no /v1, and the service token travels on the x-pipelex-api-key header on every protocol. Per-model local overrides work through backends/pipelex_manifold.toml exactly as they do for the gateway.
  • Personal configuration overrides: backends_override.toml and routing_profiles_override.toml sit beside their base files under .pipelex/inference/, are git-ignored, and deep-merge over the base — the global ~/.pipelex/inference/ one first, the project's one last — so a developer switches backends locally without touching a tracked file. Every reader sees the merged document: the boot, pipelex show backends, and the doctor's rows. The .pipelex/.gitignore that pipelex init writes now lists every personal override file. See Personal overrides.
  • Fetch-on-miss for methods: a bundle referencing a method by address (github.com/...->domain.pipe) that misses the installed methods now fetches the package, honoring an @<tag> pin, installs it into ~/.mthds/methods/ with provenance recorded beside the manifest, and loads it. A miss that cannot be bridged raises an error naming the address and the remedy instead of passing silently. Disable it with [interpreter.methods] fetch_on_miss = false or PIPELEX_METHODS_FETCH_ON_MISS. See Include by Address.
  • TypeScript emission gates: make test-ts-gates (alias ttg) provisions a pinned prettier and zod into .ts-toolchain/ and runs the codegen suite with PIPELEX_REQUIRE_TS_GATES=1, under which the two tests that read the emitted TypeScript as TypeScript fail instead of skipping. The same target runs as a required CI job. See TypeScript Emission Gates.
  • Output-form descriptor: the validate report carries an output_form beside pipe_io_contracts and input_form, and the output contract carries a json_schema, closing the asymmetry between a pipe's two halves. build_output_form projects the same concept derivation the inputs use, wraps a plural output as a list node, and publishes the payload's schema (ListContent[...] on the plural arm, with minItems/maxItems on a fixed count). See Pipe IO Contracts.
  • Projection fixture corpus: pipelex-dev generate-projection-corpus writes the shared fixture corpus that pins the TypeScript and Python inputs-template projections byte-for-byte against each other, records every declared divergence from the engine's own renderer, and round-trips every template it pins through the input shaper so a template the runtime would refuse cannot be captured unnoticed (EXPECTED_UNSHAPEABLE declares the known gaps). The corpus covers the open-shaped natives, an outputs bundle, and nested and plural file slots. See Projection Fixture Corpus.
  • bump-kajson skill: a Claude skill and helper scripts that move the kajson pin, re-lock, adapt the engine and write the changelog entry.
  • .worktreeinclude: the gitignored files a fresh worktree of this repo needs (.env, the personal .pipelex/ overrides, .pipelex-dev/test_profiles_override.toml) are declared in a tracked file, read by Claude Code and by the workspace's wt tool.

Changed

  • Inference error domains (Breaking): InferenceErrorCategory gains an error_domain property and the whole CogtError family derives its domain from it, where it declared none. A content-classified failure — a provider policy refusal, a malformed prompt image, a bad prompt parameter, every provider HTTP 400 — now answers HTTP 422 instead of 500 on any surface reading ErrorReport.http_status. ModelChoiceNotFoundError declares input on the class itself. See Error Model.
  • Gateway refusals classified: the Pipelex inference gateway's own refusals no longer fall through to the generic provider-error ladder. Three families carry their own classification, keyed on the gateway's error code rather than the reporting provider, and none is retried: request limits (a body or storage object over its cap, nesting past the depth limit), unresolved references (a storage key or URL the gateway cannot turn into bytes, a host the SSRF guard refuses), and routing refusals (a model the deployment does not serve, an integration switched off). Routing refusals are CONFIGURATION throughout, so they answer 500 rather than 422, and a model the gateway does not serve raises the same availability error a deck miss does. ClassificationResult gains gateway_request_limit, gateway_unresolved_reference and gateway_routing_refusal. See Error Model.
  • Managed gateways by declaration (Breaking): a backend is Pipelex-managed when it declares a model_specs_section, not because it is named pipelex_gateway; more than one can be live, and the published configuration is fetched once and sliced per backend. A managed backend whose ${…} variables do not resolve is disabled with a warning naming it, while bring-your-own-key backends keep failing the boot loudly. Service-terms acceptance is asked for any enabled managed backend, and declining disables all of them. is_gateway_backend is gone, the backend loader takes managed_gateway_configs, and GatewayUnknownModelError names its backend.
  • Inference loaders take a path sequence (Breaking): InferenceBackendLibrary.load, load_active_routing_profile and ModelManager.setup take a sequence whose first path is the base file and whose rest are the overrides in merge order, built by ConfigLoader.backends_file_paths() / routing_profiles_file_paths(); is_pipelex_gateway_enabled takes a config_dir. The routing loader now raises RoutingProfileLibraryNotFoundError and RoutingProfileDisabledBackendError, the classes the boot already caught, and a file that does not parse or a backend that is not a table fails as InferenceBackendLibraryValidationError naming the file.
  • Remote config pin: the default moves to pipelex_remote_config_13.json, the first artifact carrying manifold_model_specs. Its backend_model_specs are identical to version 12's, so the Portkey-cloud path reads what it read before.
  • Dependencies: mthds moves to ==0.13.0, which carries the output-form descriptor, and kajson to ==0.7.1, whose register_classes_dict({}) no longer raises on an empty snapshot.

Removed

  • InferenceBackendLibrary.check_backend_credentials and CredentialsValidationReport: the method had no caller and read the base backends.toml alone, blind to the new override files. The doctor's credentials row does the same job over the merged document.
  • DocumentFormat: the enum and pipelex/tools/misc/document_utils.py are gone; nothing in the engine imported them, and filetype_utils.py already carries the MIME and extension knowledge (Breaking for anyone importing the symbol).

Fixed

  • Class registry isolation: structure classes generated for one library could leak into another load in the same process — a later bundle silently reused an earlier bundle's structured concept of the same name, and two interleaved loads overwrote each other's registration. Every Library opened through LibraryManager.open_library now gets its own ClassRegistry, seeded from the global one, so a load's classes live and die with it. A structure class every load must see has to be registered before any library is opened. See Per-call scoping.
  • Pre-boot registry starvation: a class-registry read before boot instantiated the kajson singleton around an empty registry, and the boot then handed that manager back and discarded its own, so the core models landed where nothing resolved. Any pre-boot manager is now dropped before the boot constructs its own.
  • CLI error reporting: an unknown or ambiguous entry pipe_code and a typo'd --pipe / pipe_ref slice selector now raise EntryPipeNotFoundError / EntryPipeAmbiguousError, which carry the INPUT domain and answer 422 instead of 500; both subclass the classes they replace, so every existing catcher still matches. --help works again on every method subcommand, where the address example's brackets reached rich's markup parser unescaped.
  • TypeScript codegen stability: a dict-valued default, a key or string carrying a double quote, and an exploded choice list containing a comma or a bracket each produced a types.ts that prettier rewrote or that did not parse, so pipelex codegen check reported an untouched file as hand-edited. Defaults render as TypeScript literals, every target emits the quote style its formatter keeps, and every line-break split walks string literals. An always-on invariant asserts no emitted line ends inside an unterminated literal.
  • Zod wire agreement (Breaking for generated TypeScript — regenerate): a non-required field was projected as .optional(), which rejects the explicit null the runtime serializes for an unset field. It is now .nullish(), or .nullable().default(…) where it carries a default, and parsed optional fields type as T | null | undefined. The emitter's line-breaking now models prettier's member-chain rule. See Codegen Projections.
  • Native concept handling: a native.Anything input no longer crashes the pipe-IO contract builder — it publishes a permissive schema and renders {} in a template; a native.JSON input renders {"json_obj": {}} from its pinned structure instead of an empty object the runtime refused; and a native.Dynamic input referenced in a PipeLLM or PipeImgGen prompt no longer fails dry-run validation demanding ImageContent, since a dynamic concept gets no static image or document classification and renders as text. See PipeLLM.
  • Input-form descriptor for class-backed concepts: a concept declaring structure = "SomeClass" collapsed to unknown the moment one field annotation could not be mapped, hiding every document and image position beneath it. Reflection is now field by field: an unmappable annotation is unknown at that field alone, a nested model is walked into, and a RootModel reports its root annotation. See Input-Form Descriptor.
  • Projection corpus divergence gate: the gate that declares every engine/projection difference missed fields the projection stopped rendering, absorbed a regressed file placeholder into an existing class, and could not tell an honoured Concept[N] from a wrong count. All three now refuse, and a pipe declaring no inputs is captured from the projection alone instead of aborting the run.
  • Doctor: pipelex doctor reported a false backend-configuration error for pipelex_gateway on a stock install, because the Backend Files row loaded the library strictly with no gateway config, and that first failure hid real errors in later files. The row now loads leniently, the loader stamps backend_name on every error it raises about one backend, and both rows charge a failure to the backend it declares rather than to any backend whose name appears in the message.
  • Strict disclosure leak: PipelineInputContentError declared its caller-facing flag under the wrong name, so the message a caller needs on a missing url was redacted under STRICT disclosure while the unreadable-local-path message would have leaked. The family is split: PipelineInputUrlMissingError is caller-facing, and PipelineInputContentError stays redacted.
  • HtmlContent JSON rendering: rendered_json emitted html where the standard's pinned concept requires inner_html. The override is deleted and the rendering is the model's own dump.
  • Generated-image storage keys: the key's extension followed the requested image format while mime_type followed the provider's actual answer, so the two could disagree. Both now derive from one resolved mime type.
  • Agent-rules templates: the generate-projection-corpus entry was written into the generated repo-root CLAUDE.md instead of its template, leaving make check-rules red on dev. It now lives in pipelex/kit/agent_rules/commands.md and its Codex twin, and the check runs in CI as lint-agent-rules.

[v0.55.0] - 2026-08-29

Added

  • Run a method by address: every command that resolves a method target (pipelex run method, pipelex validate method, the pipelex build method commands, and their agent-CLI twins) now accepts a method reference <address>[@<tag>]github.com/<owner>/<repo>[/<selector>], optionally pinned at a git tag (a @<name> that names a branch rather than a tag is refused, so only tags pin a revision) — alongside installed names, local paths, and full GitHub URLs. The new pipelex/methods/ package is the single implementation the CLI, the API, and tests all share: the MethodRef grammar parser, fetch-at-tag (wiring the mthds clone_at_version machinery beside clone_default_branch, with the fetched commit SHA always resolved and reported as provenance), package location inside the clone by manifest identity (scan for METHODS.toml, match the full address, case-insensitively; a miss or ambiguity is a loud error listing the packages the clone contains), env-tunable fetched-package ceilings (file count and total bytes, plus a bounded manifest scan — a size cap per METHODS.toml read and a cap on manifests considered per repository), and the reusable structures-refusal check — an alias-aware AST scan for StructuredContent subclasses (bases reached through import ... as aliases and attribute access are caught alongside the literal name) producing the rule-naming error (hosted execution accepts MTHDS concepts and sandboxed PipeFuncs, not in-process Python), which the CLI surfaces as a hosted-parity warning and a hosted runner can apply as a hard refusal. A fetched target's default outputs (run results, graphs, generated files) anchor in the caller's working directory — the fetched clone itself is ephemeral and deleted at process exit — build runner additionally copies the fetched package beside the generated script so the runner stays self-contained, and a method-reference failure reaches the agent CLI as its structured error envelope, like every other agent-CLI error. See Run a Method by Address. (Breaking: bare github.com/... targets no longer resolve as local paths, and packages inside a fetched repository are located by manifest identity rather than directory path — /tree/<branch>/<dir> deep links now resolve through the manifest's address/name.)

Changed

  • Structure-class-not-found diagnostics teach the missing-module case: when a concept's declared structure class does not resolve (ConceptStructureClassNotFoundError, and the corresponding PipeFunc output validation error), the message now states that the class may live in a Python module that was not part of the request, and suggests including the module or expressing the type as MTHDS concepts.

Fixed

  • Blueprint serialization schemas: ConceptBlueprint, ConceptStructureBlueprint, InputSlotBlueprint and ConstructBlueprint publish their real shape again in serialization mode. Each carries a @model_serializer(mode="wrap") annotated -> dict[str, Any], and pydantic turns a serializer's return annotation into the model's serialization JSON Schema in preference to the one it would generate — so all four came back as an opaque {"type": "object", "additionalProperties": true} with no properties, no required and no closed shape. This is the same annotation mthds 0.11.1 dropped for the input-form field descriptors. Nothing here noticed, because validation-mode schemas and model_dump() were both correct throughout; the damage landed in consumers that generate response-model schemas, which FastAPI always does, so a downstream OpenAPI artifact regains its field-level detail on its next pipelex bump. A new sweep test holds every Pipelex model to the rule, and the agent rules now scope "every function return must be typed" so a serializer's return stays unannotated.

[v0.54.0] - 2026-08-28

Added

  • MTHDS standard conformance CI: A new test holds the pinned native-concept set (pinned_blueprints.py) to the standard's own native-concepts.md, read from a sibling mthds checkout — definition for definition, field for field, order included. The mthds-standard-check.yml workflow checks the standard out at its default branch and runs it on every pull request, so the day the standard moves a definition this repo goes red instead of a downstream port.
  • bump-mthds Claude skill: A skill (with its upstream_notes.py helper) that automates moving the mthds dependency: it finds the latest release, re-locks, adapts the engine source to what the release broke, runs the checks and drafts the changelog entry.

Changed

  • mthds is pinned exactly to ==0.11.1 (previously >=0.8.2). Everyone downstream of Pipelex inherits that exact version, so a consumer that also depends on mthds must now move in step with Pipelex rather than resolving its own. (Breaking)
  • Multiplicity [1] is strictly singular: a count of one (e.g. Concept[1]) is universally the single form, not a one-item list. A [1] input takes the value itself and refuses a list, a [1] output produces one object rather than a ListContent of one, a re-rendered ref canonicalizes back to Concept, and a presence marker beside a count of one (Concept[1]?) is accepted on inputs as it already was on outputs. A sequence declaring a plural output whose last step yields one no longer validates, and Concept[0] is rejected at parse time with a validation error instead of surfacing later. See Understanding multiplicity. (Breaking)
  • native.Html.css_class is now optional (str | None), matching MTHDS v0.9.0. An Html value is its markup, and the class is presentation a producer states when it has one — composed HTML and JSON rendering no longer fabricate an empty css_class, and structured output no longer forces a generating LLM to invent it. (Breaking)
  • Input-form descriptors and pipe I/O contracts are typed by the standard now: both artifacts' wire models are imported from mthds.protocol instead of being restated here, and re-exported from pipelex/pipeline/input_form.py and pipelex/pipeline/pipe_io_contracts.py. The wire is unchanged; what moved is which package declares the shape. Importers are affected three ways: a field descriptor is one model per kind (TextField, ObjectField, …) under an InputFormField union discriminated on kind, narrowed with isinstance or match rather than read off a flat model, so FieldKind.is_list and FieldKind.is_object are gone; the date kind's Python attribute is now datetime, the name it always had on the wire, rather than datetime_flag; and the item layer is a distinct InputFormItem union (TextItem, ObjectItem, …) that structurally forbids the name a list item never had, renaming InputFormFieldBase to InputFormItemBase and TextValuedFieldBase to TextValuedItemBase. See Input-form descriptor and Pipe I/O contracts. (Breaking)
  • native.Date and native.Html are object nodes in the input-form descriptor, with fields derived from their pinned definitions, rather than the former date and prose scalars — which reported shapes the slots' own schemas rejected. (Breaking)
  • PresenceMarker is imported from the standard, so the three helpers that are this engine's .mthds suffix grammar rather than wire vocabulary became module-level functions in pipelex/core/pipes/variable_multiplicity.py: presence_from_symbol, presence_symbol and is_force_presence, all keyword-only. is_optional and is_plain stay properties on the enum. (Breaking)
  • Input-form descriptor conformance: a description-only concept's refines now carries only the authored chain and no longer fabricates the native.Text link the spec forbids reconstructing — text-valuedness still reaches the wire as kind: "prose".
  • TypeAdapter agent rule refined (pipelex/kit/agent_rules/python_standards.md): the flat ban becomes a scoped one. Don't design shapes that need a TypeAdapter, but validating into a top-level union or container owned by an external contract — a standard's wire model, a log stream of one-of-many event types — is its sanctioned use, as a module-level singleton.
  • Bumped pyright to 1.1.411. The release narrows hasattr() on an Any or type[Any] operand to type[object] instead of leaving it wide, which turned two duck-typed guards into type errors; both now test what they mean — issubclass(..., BaseModel) before calling model_validate, and typing.get_origin instead of reaching for __origin__.

Fixed

  • Input-form field serialization: mthds 0.11.1 drops the -> dict[str, Any] return annotation from the wrap serializer every field descriptor shares. Pydantic preferred that annotation over the schema it would generate, so TextField.model_json_schema(mode="serialization") and each of its siblings came back as an opaque {"type": "object", "additionalProperties": true} — the mode a server generating response-model schemas asks for, so a FastAPI consumer embedding PipeInputFormDescriptor published a kind-discriminated union whose arms described nothing. The per-kind shapes are back.
  • Sequence output enrichment: a sequence step with nb_output = 1 on an optional boundary no longer drops the optional marker (?) during fix planning.
  • Parallel pipe output validation: a plural branch with nb_output = 1 no longer bypasses type checking, which previously let an incompatible singular result through to crash at run time.
  • Image generation multiplicity: PipeImgGen honors an output_multiplicity=1 run-time override, generating a single image instead of refusing it as unresolved.

[v0.53.0] - 2026-08-25

Added

  • Vacuous-presence lint (input_presence_vacuous): New advisory warning that fires when a required method input (declared without ?) points to a concept with no required fields, since an empty object {} already satisfies it. Scoped strictly to entry pipes (the bundle's declared main_pipe), the message suggests either marking the input optional (?) or adding a required field to the concept. See Understanding optionality and Inline structures.
  • Unified advisory-warnings module (pipelex/pipeline/advisory_warnings.py): A single composition point that assembles all advisory warnings (optionality, vacuous-presence, and intent-hints) in a fixed, deterministic order.
  • Corpus vocabulary exclusion: Added the input_presence_vacuous exclusion to the corpus vocabulary generator (generate_corpus_vocabulary_cmd.py).
  • Documentation: Updated the user guides (Inline structures, Understanding optionality, validate) to cover the new lint, its remedies, and the unified warning channels.

Changed

  • Consistent warnings across all channels: Every whole-bundle validate channel (protocol report, agent CLI, builder operations, and the bare CLI's validate bundle and validate --all) now carries the exact same set of advisory warnings. Previously, channels showed different subsets — intent-hints were missing from the CLI, for instance. To keep output manageable, intent-hint findings are capped per site (five before collapsing into an "...and N more" message) and long authored tokens are elided at sixty characters.
  • Library manager interface (Breaking): LibraryManagerAbstract gained a new abstract method get_accumulated_blueprints(), which the collector reads to find the entry pipes. Any custom library manager injected via Pipelex.setup(library_manager=...) must implement it or fail loudly at construction.

Fixed

  • Bare CLI warning visibility: The bare CLI's validate bundle command now prints its advisory warnings before the strict pending-signature gate exits. Previously, a bundle with an unimplemented placeholder would exit non-zero and swallow the warnings — which is exactly the state an author is in while building a method.
  • Empty structure table parsing: An authored but empty [concept.X.structure] table is now correctly parsed as an object with an empty fields list, instead of falling through a truthiness check and being described as prose refining native.Text.
  • Field-less structure class reflection: A registered Python class declaring no fields is now reflected as an empty object rather than unknown. Genuinely unmappable annotations still correctly report as unknown.
  • Domain splitting in locators: Hierarchical domains such as legal.contracts are now split at the last dot rather than the first when building advisory-warning locators.
  • Release workflow no longer loses the tag when signing fails: The GitHub Release is created, and the dists attached, before the Sigstore step, which is now allowed to fail without failing the job. The tag exists only as a side effect of creating the release, so a refreshed Sigstore trust root previously cost the tag and the release page while PyPI published normally — as happened to v0.52.0. The action is also bumped to a version carrying a current trust root, and release creation is idempotent so the manual retry path works.

[v0.52.0] - 2026-08-24

Highlights

A pipe's inputs now describe themselves well enough to render a form. The validation report carries a per-pipe input_form descriptor built from the loaded pipes and the authored blueprints rather than from the emitted JSON Schema, so a renderer keeps the concept chain, the presence marker, gating, fixed counts, defaults and choices. pipe_io_contracts drops its boolean flags for a three-valued presence and a real multiplicity, and emitted schemas gained the concept ref as title, the authored description as description, and minItems/maxItems on fixed counts. Alongside them a non-normative hints table lets an author state presentation intent without touching semantics, and authoring got louder: an unknown structure-field key, or a required = true paired with a default_value, is now rejected at parse time instead of silently discarded. Breaking: the contract reshape, those two new rejections, and build_validation_report's new required input_form argument.

Added

  • MTHDS intent hints: A non-normative hints table can now be authored on a concept, on a structure field, and on a pipe input slot, with one defined key — intent, over the closed vocabulary prose / label / rating / quantity. Hints travel through the library crate (a concept's effective hints merge along its refinement chain, the nearer declaration winning) and surface on the input-form descriptor, while execution, validation verdicts and pipe contracts never read them. Validation emits advisory warnings (hint_unknown_key, hint_unknown_intent, hint_inapplicable_intent) rather than failing, and unknown-but-well-formed content is preserved. See Intent hints.
  • Input-form descriptors on the validation report (Breaking): PipelexValidationReport.input_form maps every pipe_ref to an ordered list of field descriptors, derived by build_input_form from the loaded pipes and blueprints — never from the emitted JSON Schema — so a UI renderer gets the namespaced concept_ref and its refines chain, the three-valued presence, a stated gating fact, fixed counts, authored defaults beside required, and one-member choices lists, with unknown as the honest escape hatch. Class-backed concepts are reflected through the new reflect_structure_class. Breaking: build_validation_report now requires input_form, so every backend assembles it or fails loudly. See Input-form descriptor.
  • Richer emitted JSON Schemas: A schema now carries the concept ref as its top-level title — replacing the mangled generated-class name — and the concept's authored description as its top-level description, uniformly for generated, class-backed and native concepts. A fixed-count slot (Concept[N]) emits minItems/maxItems on its array wrap while a variable-length one (Concept[]) stays unbounded, and the native content classes (TextContent, ImageContent, DocumentContent, PageContent, NumberContent, HtmlContent, YesNoContent) gained docstrings matching their pinned native descriptions, so provider-side schemas built straight from the classes carry them too.
  • pipelex-dev trace-input-semantics: A capture harness for the chain that turns authored structure syntax into the json_schema on pipe_io_contracts. Given one or more .mthds bundles it dumps per-hop captures — parse blueprint, generated class source, raw pydantic schema, SCHEMA render, wire contract — plus a manifest of each input's wire framing, so a lost or mangled authored fact is localized to exactly one hop. A committed probe bundle exercises every construct the language accepts. See Trace input semantics.

Changed

  • pipe_io_contracts learns presence and real multiplicity (Breaking): An input contract now carries presence (plain / optional / force) — the declared marker verbatim — replacing the two-valued optional, so lint and graph surfaces can see where ! assertions live without a presentation view. Input and output contracts both carry multiplicity (single / variable / fixed), with item_count present exactly when fixed, retiring the old ruling that reported a fixed count as variable; the output contract keeps its two-valued optional, since ! is rejected on outputs. See Pipe I/O contracts and Understanding multiplicity.
  • Unknown keys in a structure-field table are rejected (Breaking): A typo'd or hopeful key on a concept structure field (minimum, unit, a misspelled hint) used to be dropped at parse while the bundle validated green, silently discarding authored intent. ConceptStructureBlueprint now forbids unknown keys, making the field table strict exactly like an input slot table; hint content stays lenient, since an unknown hint key warns and is preserved. The MTHDS JSON Schema regenerates accordingly, structure-field objects carrying additionalProperties: false. See Inline structures.
  • required = true and default_value can no longer be declared together (Breaking): The two are contradictory instructions — a default is applied when the caller omits the field, which makes absence legal — and the generator used to resolve the conflict silently by dropping the required-ness. The pair is now rejected at blueprint validation and at the builder's spec layer, so an authoring agent fails before emitting TOML the parser would reject, with a message naming both remedies. Relatedly, a pydantic default on a hand-written structure class now counts as an authored fact: the input-form descriptor reports such a field as required: false carrying its default, instead of claiming it required and hiding the default.
  • Ruff moves to 0.16.4, matching the version the VS Code extension bundles, so a rule can never fire in the editor and not on the command line — an older binary parses pyproject.toml as Python source and paints phantom syntax diagnostics across it. Two mechanical conversions ride along, both applied by ruff check --fix: selector lists in pyproject.toml name rules rather than code them, and # noqa: CODE suppressions became # ruff: ignore[rule-name] with their explanations preserved. The new formatter also reformats Python code blocks inside Markdown, so the docs picked up trailing-comment and blank-line normalization, and rules newly reported under preview are ignored deliberately rather than in bulk, each with its reason recorded beside it. This is a dev-dependency change: nothing shipped changes.

Fixed

  • Builder-authored defaults no longer evaporate on re-load: The builder's TOML writer emitted the key default where the language reads default_value, so a default authored through the builder — or through the agent-CLI concept command — validated green and then reached nothing when the file was re-loaded. The writer now emits default_value, a write-then-validate round-trip test pins the survival, and the new unknown-key rejection turns any recurrence of this bug class into a hard failure instead of silent loss.
  • pipelex.providers.bedrock no longer imports a type-stub package at runtime: ConverseResponseTypeDef was imported at module level as well as inside the module's if TYPE_CHECKING: block. It resolves only through types-aioboto3, which is a development dependency, so the module-level copy raised ModuleNotFoundError on any install of pipelex[bedrock] without the dev extras. The duplicate is gone and the surviving import is the type-checking one, whose annotation is a local variable annotation and is never evaluated at runtime.
  • A usage example in pipelex.test_extras.shared_pytest_plugins had lost its yield: The example fixture in needs_inference_in_pipelex's docstring showed a teardown call with nothing handing control to the test first, its yield line having drifted into a literal Yield: docstring section on a function that only ever returns a bool. The example is a working fixture again.

[v0.51.0] - 2026-08-21

Added

  • fails_at on every error.* tag in the MTHDS Test Corpus vocabulary. A non-excluded tag now declares the earliest layer of checking that catches its fault: "schema" when a pass over the raw document's shape already rejects a bundle carrying it (a pipe section with no type key, a type outside the closed set of pipe kinds), "runtime" when the document has to be interpreted to notice. The consumer rule is one sentence: a structural sweep expects a diagnostic on an entry exactly when its tag says schema, and silence on every other entry. generate-corpus-vocabulary computes and injects the field from an internally maintained set of schema faults, so it is never hand-written.

The signal lives on the vocabulary tag rather than on each entry.toml because the layer is a property of the error type: per-entry restatement would let two entries covering one fault disagree, with nothing to catch it. Excluded tags carry no value at all — an excluded tag has no entry, so nothing was measured. Previously every consumer had to hardcode which faults it believed were structural, a downstream re-reading of this repo's validation-error registry guaranteed to drift from it; the corpus now ships the answer in the wheel.

  • schema_fault_tags property on CorpusVocabulary. Structural consumers — JSON-Schema sweeps, editor diagnostics, linters — read which faults they are expected to catch instead of maintaining their own list.

  • Gates keeping the signal honest. The exhaustivity gate now requires every error.* tag that is owed an entry to declare a fails_at, so a new validation error type cannot land carrying a fault a structural consumer has no way to sweep for. A new test_mthds_corpus_plxt_exclusions.py gates .pipelex/plxt.toml's corpus exclusions against the vocabulary, so the linter config and the vocabulary cannot drift apart.

Changed

  • The corpus's deliberately invalid entries are linted again — all but the two whose fault the schema is supposed to reject. .pipelex/plxt.toml excluded the whole invalid_* population behind one blanket glob, because some of those entries carry a structural fault and the config had no way to say which. The exclusion now names exactly the schema-fault entries (invalid_missing_pipe_type and invalid_unknown_pipe_type), and every runtime entry is covered by make lint and make format like any ordinary .mthds file.

That makes this repo the first consumer of its own signal, and it is what keeps the signal measured rather than merely asserted: declare a fault runtime when the schema in fact rejects it and its entry stays linted, so make plxt-lint goes red naming it.

  • Documentation. docs/contribute/mthds-test-corpus.md gains a fails_at section covering the consumer rule, why the field sits on the tag, and how the linter config closes the measurement loop. Its invalid-entry authoring warning is corrected to match the narrowed exclusion: only a schema-fault bundle has to be laid out by hand now.

[v0.50.0] - 2026-08-20

Changed

  • Breaking: JobMetadata splits its run-constant half into a new RunMetadata. user_id, pipeline_run_id, storage_scope and request_id are constant for a whole run; pipe_code, pipe_run_id, otel_context and content_generation_job_id change at every step. They sat side by side with nothing saying which was which, so a copy made for a nested pipe had to know, field by field, what to carry through. They are now job_metadata.run_metadata.*, and the copy carries one object.

There is deliberately no read-through accessor on JobMetadata: one spelling for one fact. Every call site reads the nested path.

  • A pipe's result now carries the job it was produced under. PipeOutput.job_metadata and TracingAssembly.run_metadata are set once at the run boundary, so a transport that offloads an oversized result to storage can key it inside the run's own namespace. Both are private attributes behind properties, not model fields: PipeOutput is on the wire — pipelex-api returns it and publishes its schema — so as a field this would have put user_id, request_id and otel_context into a public API's response body and its documented OpenAPI artifact. A transport reads the value by attribute on the live object before serializing, so it never needed to be on the wire at all.

The gap was one-directional and easy to miss: every payload a run sends in carries a job_metadata to resolve a scope through, and nothing it sent back did. Under a hosted orchestrator that meant the two largest payloads an ordinary run produces — the pipe output and the tracing assembly, both around 350 KB — were stored outside every prefix the host's erasure cascade deletes, and survived deletion of the run that produced them. The same bytes were also stored a second time, correctly scoped, inside the delivery argument that carries them.

Set at the run boundary and not at the ~17 PipeOutput(...) construction sites: only the top-level output crosses a transport boundary, and stamping every constructor to serve one of them is the same "remember to add it everywhere" fragility that caused the original bug. Optional throughout — dry runs, signature stubs and tests build outputs with no job in hand — and invisible on the wire, since serialize_completed_output names the fields it returns explicitly.

[v0.49.0] - 2026-08-20

Added

  • The error_type values a validation verdict can carry are now an enumerable, closed registry: a .mthds bundle that fails validation reports each fault under an error_type, and until now there was no way to ask the runtime which faults exist. The values were reachable only by reading raise sites, and the only curated list of them lived outside this repo, in a hand-maintained conformance suite that could silently fall behind. pipelex/validation_error_types.py now holds the vocabulary in full and enumerates it as VALIDATION_ERROR_TYPES, so a consumer building coverage over the language surface — a test corpus, a client mapping faults onto its own UI — reads the registry instead of collecting strings from whichever diagnostics it happened to have seen. It is a union of the enums the runtime already raises, never a second list beside them: the two stage vocabularies plus a new ValidationResidualErrorType naming the one residual channel that had no enum of its own and rode an inline string. A member added to any of them is in the registry the moment it is declared. ValidationErrorItem.error_type is typed against their union, which is what makes the registry closed rather than merely documented — an unregistered string can no longer be constructed or parsed onto an item — and which publishes the vocabulary into the OpenAPI schema served for /validate, where the field was previously an open string. No wire value changed, including the dry-run residual's DryRunError: it keeps the exception-class spelling it has always had, because normalizing it would break every consumer that pins the string and would buy nothing an enumeration does not already give. Membership means a value is reachable on the wire, not that it is worth exercising — the advisory-only warning type and the two unknown_* fallbacks are in the registry and a coverage consumer excludes them on its own side, with a reason, rather than pruning the runtime's truth.
  • Closed registry for validation error types: VALIDATION_ERROR_TYPES, in the new pipelex/validation_error_types.py, is an enumerable, closed registry of every error_type a validation verdict can carry. It is a union of the enums the runtime already raises — PipeValidationErrorType, PipeFactoryErrorType, and a new ValidationResidualErrorType naming the one residual channel that had no enum of its own — so a member added to any of them is in the registry the moment it is declared. A consumer building coverage over the language surface now reads the registry instead of collecting strings from whichever diagnostics it happened to have seen. No wire value changed.
  • The corpus's invalid axis: the MTHDS Test Corpus now carries an error.* tag namespace generated from VALIDATION_ERROR_TYPES, plus a focused invalid entry for every tag in it that a .mthds bundle can actually produce — each authored to trigger exactly one error, and gated on doing so. Five codes are excluded, each with the measurement behind it: circular_dependency_error and unknown_concept are unreachable through bundle validation, optional_force_redundant is advisory-only and rides the warnings array, and the two unknown_* fallbacks name the absence of a diagnosis rather than a language fault.
  • The exhaustivity gate now requires an invalid entry to agree with itself: such an entry states its fault twice — as expected_error, the wire string, and as an error.* tag in covers, the normalized form — and nothing checked that the two matched. The check joins through the vocabulary's own code field rather than re-normalizing, so it carries no second copy of the normalization rule. A valid entry may no longer carry an error.* tag, since its bundle produces no diagnostic for the exhaustivity arm to count.
  • Validation parity test: test_validate_bundle_entry_shape_parity.py validates the same bundle through both entry shapes — in-memory contents and a library directory — and requires the two to produce identical structured diagnostics.

Fixed

  • The same bundle produced different diagnostics depending on how it reached the validator, and the library-directory route — the one the CLI and every local tool take — was the lossy one. Two arms in LibraryManager._load_mthds_files_into_library destroyed the structured data on the way out: a pydantic ValidationError was rewrapped as a bare LibraryError, which routes to the error cascade's structured-forwarding arm rather than its categorizing arm; and a ConceptLibraryError was re-raised message-only, dropping the per-reference items it now carries. In practice unresolved_concept, optional_input_unguarded and optional_output_required were simply unreachable from a file on disk. Both arms now preserve what they were discarding.

Changed

  • ValidationErrorItem.error_type is typed against the closed registry union instead of an open string (breaking). This is what makes the registry closed rather than merely documented — an unregistered string can no longer be constructed or parsed onto an item — and it publishes the exact vocabulary into the OpenAPI schema served for /validate, where the field was previously an open string. As part of this, PipeValidationErrorType and PipeFactoryErrorType moved from pipelex/core/pipes/exceptions.py to pipelex/validation_error_types.py: the vocabulary is a wire contract rather than pipe machinery, and it has to sit below base_exceptions for the wire item to be typed against it at all.
  • Two test fixtures were naming faults that do not exist, and typing error_type against the registry is what surfaced them: one claimed MISSING_CONCEPT, a code no enum has ever defined, and others used member names (INADEQUATE_OUTPUT_CONCEPT) where the wire carries values (inadequate_output_concept). Each was accepted silently while the field was an open string. They now name registry members.
  • The corpus's invalid_* entries are excluded from plxt linting and formatting in .pipelex/plxt.toml: their structural faults are deliberate, and what guards them instead is strictly stronger — the corpus's entry-validation gate runs the real validation engine over every entry in CI, and is red both when an invalid entry fails differently and when it accidentally validates.
  • Documentation: docs/under-the-hood/error-model.md now describes the closed error_type registry, and docs/contribute/mthds-test-corpus.md explains the rules for authoring an invalid corpus entry.

[v0.48.0] - 2026-08-20

Added

  • MTHDS Test Corpus: Introduced a canonical, tagged set of .mthds methods at pipelex/test_extras/mthds_corpus/, replacing scattered per-repo fixtures with a single source of truth that ships in the wheel for synchronized cross-repo consumption. Each entry is a directory holding the method, an optional inputs.json, and an entry.toml declaring what it exercises — covers, tier, granularity, validity, and for a deliberately invalid entry the exact wire error_type it must produce. The cross-repo contract is the workspace spec docs/specs/mthds-test-corpus.md.
  • Corpus Loader API: Added iter_entries() and get_entry() in pipelex.test_extras.mthds_corpus.loader to fetch fixtures programmatically by tag, execution tier, validity, and granularity instead of by file path.
  • Tag Vocabulary Generation: Added the pipelex-dev generate-corpus-vocabulary CLI command and Makefile targets to generate the corpus tag vocabulary (native.*, operator.*, controller.*) directly from runtime registries (NativeConceptCode, PipeType). The two pipe namespaces come from one registry walk, split by PipeType.category, so a pipe kind added later lands in the right namespace with nothing to update. PipeFunc is the corpus's first recorded exclusion: it names a Python function the runtime resolves against its function registry at validation time, so no portable entry can declare one.
  • Feature Tag Axis: Added a hand-maintained feature.* tag namespace for language features spanning multiple blueprint fields or lacking a registry (e.g. feature.multi_file_library, feature.optionals, feature.smart_inputs, feature.structured_output). Each feature tag is declared in the same change that lands its first covering entry, so the vocabulary never advertises coverage the corpus does not have.
  • Strict Corpus CI Gates: Added test suites enforcing corpus integrity: exhaustivity (every registry code has a focused entry), vocabulary drift (committed vocabulary.toml matches registries), manifest validation (strict Pydantic model per entry.toml), entry validation (valid entries compile, invalid entries fail with their declared error_type), and packaging (builds a real wheel to confirm corpus files ship while excluding the generator script).
  • New Corpus Entries: Added inference-tier image payload entries (image_from_planting_brief, image_round_trip_room_photo), a nested structured-output entry (feature_structured_output_delivery_round) for lists of structured objects with optional fields, and focused entries covering batching, conditions, sequences, parallel execution, composition, extraction, and search. No entry names a model: presets are resolved by the validation engine, so an entry pinning one would fail validation on any consumer whose deck does not define it.
  • Documentation: Added a contributor guide for the corpus at docs/contribute/mthds-test-corpus.md.

Changed

  • Centralized Test Fixtures: Migrated end-to-end and integration tests (Date, YesNo, Smart Inputs, Optionals, Multi-file libraries, Image round-trips) to source their .mthds bundles dynamically from the new corpus via get_entry(), removing all local duplicated copies previously scattered across tests/e2e/ and tests/integration/. Nothing in this repo holds a second copy of a language-level .mthds method any more.
  • Makefile Targets: Added generate-corpus-vocabulary-quiet to the cc (check) and up (update) targets to keep the vocabulary current during local development.
  • VS Code Launch Config: Updated the integration test target in .vscode/launch.json from test_image_out_in.mthds to test_image_inputs.mthds to match the refactored test structure.

[v0.47.0] - 2026-08-19

Added

  • codegen.lock now declares its format version, so the format can grow without breaking every reader: the lock is a cross-language interchange format — this CLI writes it, other implementations of the offline check read it — and it validated strictly with no version field at all, so the day it gained any key every consumer pinned to an older reader would have failed CI with an opaque "unknown key" error instead of a drift verdict. Each lock now opens with lock_version = 1; a reader refuses a version it does not know and names which side to upgrade, and it reads the version before the key set, so a future lock is diagnosed by its version rather than by whichever new key it happens to carry first. A lock written before the field existed is version 1 by definition and needs no migration, but every regeneration rewrites the lock once to add the key — that one-line diff is the change consumers will see. The stamp header stays deliberately unversioned, since it already ignores commented fields it does not recognise.
  • Line endings are pinned to LF in the git index and on checkout, on every platform: this repo carries a lot of generated-but-committed text — CLAUDE.md and AGENTS.md, subject_grants.toml, the drift acks, .test_durations, the error-identity snapshot, the generated error pages, the migration goldens, the gateway model references — and each is produced by a writer that passes no explicit newline, so on Windows Python translated every \n to CRLF and the whole set churned in version control with no change of content. No gate would have caught it: they all compare with universal-newline reads, which fold CRLF back to LF before the comparison. A .gitattributes carrying * text=auto eol=lf closes that class for tracked files whichever writer produced them. text=auto leaves git to tell text from binary, so images and other binaries are untouched, and no tracked file held CRLF when this landed — it introduces no renormalisation diff, it pins the invariant. The complementary writer-by-writer fix is the structural half and is tracked separately.
  • Documentation: docs/under-the-hood/codegen-projections.md now covers the strict stamp-header rules, the JSON conformance the options payload must meet, the policy for evolving the lock format under lock_version, and the cross-platform line-ending guarantee.

  • A configuration that will not load now says whether it is wrong or merely old: A validation error is raised against the merged configuration and carries no provenance — it names a key, not which file put it there, and certainly not whether that key was correct last month. When a configuration surface's model refuses, the error now also carries a dry-run scan of that surface over the same directories pipelex migrate walks: a structured migration block naming the remedy, whether that remedy would actually rewrite anything, whether anything there is a person's to resolve rather than the tool's, and the plan per file — the same shape pipelex-agent migrate --dry-run --format json emits, so an agent that parses one has already parsed the other. error_domain deliberately stays config: it is a closed cross-repo enum and the agent-hook specification routes anything else to BLOCK, so a stale configuration reported under a new domain would stop an agent instead of telling it what to run. Consumers branch on the block's presence — absent means the failure is not staleness — and then on would_write, because presence means the migration history has something to say about these files and not that a command repairs them: a block whose only finding is a path no entry explains, or an entry blocked before anything applied, has nothing to run, and naming the remedy there would send a reader to a run that writes nothing and leaves the same error standing. Every surface points that reader at pipelex migrate --dry-run instead, where the diagnosis is. The message gains a paragraph naming the files and the next move. No value read from a user's file appears in the block, which is the third of the three channels that rule covers. A caller validating something that is not a configuration surface — a .mthds bundle, a model deck, a routing profile — names no surface and pays for no walk.

  • Inference backend definitions are a migration surface, and the key the templating change deleted is repaired on every machine that has one: #1104 removed prompting_target from the model-spec blueprint and from every backend file we ship — but pipelex init never overwrites, so the key survives in inference/backends/*.toml on every installation that predates it, where it is fatal twice over: in [defaults] it is copied wholesale into every model of the file, and on a single model it is rejected by name since unknown per-model keys must be header-shaped. Those files were reachable by no surface, no walk and no gate. They are now the inference-backend surface, which required the walk to learn about subdirectories — a file is claimed by the pair (directory, name), one level deep, and only into a directory some configuration family owns — and required the fingerprint to record a document whose root is the open node, since a backend file's root keys are model names rather than a field we could name. Boot tolerance joins at the backend loader too: a stale directory is replayed in memory, boots on the models the current files would produce, and warns once naming every file it carried. Migration: run pipelex migrate after upgrading — ledger entry inference-backend@2 deletes prompting_target from every root table of every backend file, in ~/.pipelex/ and in each project .pipelex/, keeping one timestamped backup per file. Until you run it, each boot repeats the warning; nothing is ever written by a boot. A key you added that we have never heard of is still an error rather than something the entry silently drops, and the served Pipelex Gateway specs are untouched. The one key class the report stays silent about is a per-model request header, legal by shape rather than by name — and only on a model table: [defaults] is copied into every model of a file unsplit, so a header put there breaks all of them, and the report names it like any other key the schema cannot explain.

  • A configuration file the ledger can explain no longer stops the boot: A schema change used to leave every machine in the field with a boot that died on extra="forbid", naming a key the user never chose to have. When a configuration surface fails to validate, its ledger is now replayed over the same files in memory and the result validated again; a boot that succeeds says so in a warning naming the files and, for each of them the command would actually reach, the pipelex migrate remedy. The retry replays whatever the loader merged, so a file loaded through Pipelex.make(config_dir=…) from outside the two directories migrate walks is named instead as the caller's to update where it lives, rather than pointed at a command that would then report nothing to do. Nothing is written either way — a tolerated boot leaves the file and its directory exactly as it found them, which is why the warning keeps coming back until the command is run. Tolerance widens what starts and never what is accepted: the re-validation is what decides, so material an unsafe entry is about still fails the boot because the model still refuses it. Anything that goes wrong inside the retry makes it decline rather than replace the error the user already has — theirs names the key to fix. A healthy configuration pays nothing: it reads no ledger, re-parses no document, and does not even import the migration engine, which is deferred for a second reason too, since the applier lives in the interpreter layer and the configuration loaders sit in the kernel layer that must not load it. This release is where it matters most: the configuration reshape gives pipelex.toml its first ledger entry, so the file every boot reads is the one tolerance carries forward on every machine that upgrades.

  • pipelex migrate and pipelex-agent migrate: The migration engine now has commands, so a configuration that predates the installed pipelex can be repaired instead of re-initialized. Both walk the global ~/.pipelex/ and the project .pipelex/ — those two directories only, one level deep, and into a subdirectory only when a configuration family owns it: inference/backends/ is such a directory, inference/deck/ is not and is never entered. A file is claimed by the pair (directory, name), which is what keeps inference/backends/pipelex_gateway.toml — a pipelex_*.toml match by name — from being rewritten under the main configuration's ledger. Neither command boots: a broken configuration is the reason to reach for them, so they use the ledger, the applier and the filesystem and nothing else, and a test hands them a configuration that cannot load to keep it that way. The human command plans, shows the plan and asks; the machine one writes only with --yes, since it cannot ask, and passing --dry-run and --yes together is refused rather than resolved. Its answer is structured, and needs_attention is the verdict — this run left something a person has to decide, deliberately not did anything get written — with the exit code and the Markdown rendering as presentation. Every file rewritten is backed up beside itself first, and no value read from a user's file appears in either command's output or in the structured plan.
  • A migration now names what it cannot explain about a file: A file newer than the running pipelex — an override migrated on one branch, then an older branch checked out — breaks the boot while a replay finds nothing to do, because every operation's source is absent. That silence beside a dead boot is the failure the migration project exists to remove, so every plan now carries an unexplained[] listing the paths of the migrated document that the current schema does not know and no ledger entry accounts for, each with the two readings the tool cannot tell apart: a typo, or a file written by a newer pipelex. This is the one part of a migration that needs the configuration model — the question goes to the surface's fingerprint, the same projection the coverage gate diffs, so the replay engine stays model-free. The document it reads is the one the run leaves behind rather than the one it found, which is what keeps every key the ledger is about to repair out of the list, and a path a blocked entry is about is left to that entry, which reports it by name with its own guidance. A key the user chose is never rendered: beneath an open mapping a typo is reported at queues.*.retries, naming only the segment the schema does not know.
  • A configuration file may now be refused for declaring a schema version its ledger no longer reaches: min_supported_schema_version had no reader, which left the floor a comment. A migration now reads the reserved [meta] schema_version — exactly as tolerantly as the configuration loader strips it, so a malformed declaration is no declaration — and refuses a file below the floor by name (unsupported_schema_version), writing nothing. Without it, a ledger whose oldest entries had been squashed away would run over a file older than them, change nothing, and report it clean, because the applier skips an operation whose target is absent.

  • An unsafe migration entry now says what it is about, and keeps saying it: unsafe is the ledger's form for a change only a human can make, and the coverage gate's only remedy for a tightened numeric bound — but the engine decided whether to report one by rehearsing its operations, so an entry with none was accepted by the accounting and then guaranteed to reach nobody, and any entry went silent the moment a later safe entry renamed the table its material sat in (reported on the first run, clean on the second, boot still failing). An entry now declares the paths whose value domain narrowed, in declared_narrowed_paths; an entry with no operations must carry one, a safe entry may not, make check-ledger refuses a path the entry's own version does not record, and the coverage gate no longer takes the bare word unsafe as accounting for a narrowing or a lost enumerated spelling — it demands the path be named. The material of an unsafe entry is traced through the ledger before a document is questioned: back through the entry's own operations, since it never applied them, and forward through every later safe entry, so the same file is reported on every run for as long as it carries it. A file that merely sets a narrowed path is reported under a new, weaker value_domain_narrowed reason listing the keys to check by hand — the engine is model-free by design and cannot tell a value the new domain refuses from one it accepts — and those keys are named at the spelling the migrated file carries rather than the one that matched, since the entry is questioned where the replay reaches it and the later safe entries rename the material before the file is written — and that report, alone among blocked entries, does not fail convergence, since a healthy reference document sets those paths like everyone else's file does. Forward tracing is deliberately confined to unsafe entries: a rehearsal may guess, an application may not.

  • A migration operation can now remap every value beneath an open mapping: remap_value accepts key = "*", meaning "each key of the addressed table" — the same "each of these" reading the wildcard already had in a table_path, applied to keys. A mapping from the user's own keys to an enumerated value (dict[str, LogLevel]) keeps its spellings under keys only the document can enumerate, so no fixed key reaches them and this is the only shape in which a member renamed beneath one can be repaired at all. On every other kind a * key is now refused when the operation is parsed: deleting every key of a table is delete_table, and renaming or moving every key onto one fixed name is the many-to-one that already rules a wildcard out of a move_key. Refused rather than tolerated, because an unrefused one is a dead operation that skips forever while looking like an accounting.

  • A schema change that narrows what a value may be is now caught, not waved through as additive: A change can keep every path and every enumerated spelling and still break a user's file — int becomes str, a free string becomes an enum, a bound moves from ge=1 to ge=5. Read as a path diff those look like additions, so the coverage gate used to ask for a golden regeneration, demand no version bump and no entry, and let the next boot reject a valid file with a green gate behind it. It now splits its verdict by direction: a widened type or a relaxed bound still costs only a regeneration, while a narrowing demands a bump and an entry carrying a remap_value for each narrowed path — or marked unsafe, which is the only remedy a tightened numeric bound has, since no structural operation can repair a value. The same rule applies to a stored entry, compared by origin so a narrowing hidden behind a rename is still caught, and to a pre_history entry, so the flag is not a way past it. To see a bound at all the fingerprint now records one: a closed whitelist over gt, ge, lt, le, min_length, max_length and multiple_of, read from annotated_types (now a declared dependency) and from the field metadata pydantic folds a top-level Field(ge=1) into, with anything unrecognized dropped rather than serialized — pattern excluded permanently, because regex containment is not decidable and every pattern edit would read as a tightening. One correction rides along: an enumerated type relaxed into a free string no longer reads as the loss of every spelling it had, and that is read structurally, so list[enum] becoming list[str] is the same benign loosening one container down — while a list[enum] flattened to a bare str, which is no loosening at all, still loses every spelling it had. The contract states in the same change what the gate structurally cannot see — narrowing expressed in a validator, which no projection of the annotations can reach.

  • The telemetry configuration can now be migrated instead of thrown away, and the escape hatch that would have let it ship unverified is closed: telemetry.toml carries the package's first migration entry, telemetry-config@2, which lifts a flat first-generation file — telemetry_mode, host, project_api_key and the rest at the document root — into today's [custom_posthog] section, keeping every value the user chose; four settings the current shape has no home for are dropped, and the entry says which and why. Because that change predates the first fingerprint, no diff describes it, and such an entry is marked pre_history and exempted from the coverage gate's accounting — so the verification that replaces it lands in the same change and neither ships without the other. A pre-history entry must declare the paths it removes (an empty declaration is refused when the ledger is parsed), none of those paths may appear in any fingerprint at or below its own version, the fingerprint pair it sits between must show no removal at all, and every operation must act inside the declaration. It also ships a hand-authored before@N.toml beside the golden chain, which the transform check migrates and holds against the current shape and the current model — the same three claims every other entry answers. One comparator refinement came with it: a created path is now checked against defaults@N or fingerprint@N, because an optional key defaulting to None is a legal destination that no TOML document can carry.
  • Configuration surfaces now have a migration ledger and a gate that forces one to be written: Every configuration surface — pipelex.toml and its tiers, telemetry.toml, pipelex_service.toml, and the inference backend definitions — carries a checked-in ledger at pipelex/migration/ledgers/<surface-id>.toml recording, as data, every shape change it has ever undergone, plus a golden chain (fingerprint@N, defaults@N) that pins what the schema looked like at each version. Each is cut at schema version 1 with no entries; the entries this release then ships on top of that baseline are the bullets below. The new make check-migration-schemas (alias cmig, in make check) recomputes each surface's fingerprint and refuses a change that would break a user's file without recording how to repair it — a removed path or a lost enumerated spelling demands a version bump and an operation accounting for it, a destination the new schema does not know is refused by name, and an entry that removes a path the schema still has is refused as over-deletion. Its counterpart make up-migration-schemas (alias umig, in make up) regenerates the goldens; the regeneration diff is the review artifact. One standing invariant is checked whether or not anything changed: every required path must have a value in its surface's defaults layer, because that is what makes an added key absorbable rather than breaking. The contract the whole thing implements is docs/migration-ledger.md.
  • The migration operation vocabulary: Fix operations became a discriminated union on kind, published as two subsets — every kind for the .mthds fix path, and the structural kinds only for ledgers, so an operation that would write a value into a user's file fails when the ledger is parsed rather than at review time. Two new kinds land with it: move_key, which relocates a key across tables (the whole subtree travels when the key is table-valued, and a missing destination parent is created), and remap_value, which rewrites a value against an explicit old-to-new mapping. A * segment addresses every entry of an open mapping, expanded over the keys the file actually holds. A new CONFLICT outcome separates "this cannot be done without choosing on your behalf" — a rename or move onto an occupied destination, or an ensure_table over a key holding something that is not a table — from the benign SKIPPED it used to hide inside; a conflicting operation writes nothing at all.
  • [meta] schema_version is reserved in configuration files: Every configuration-surface reader now tolerates and strips it. Nothing writes it — the key exists so a later release can stamp a version into a file without that being a breaking change today.
  • make check-ledger (alias cl): The second migration gate, and the first one that joins make agent-check as well as make check. It asks what a ledger entry is allowed to say and whether replaying it is harmless, where check-migration-schemas asks whether a schema change was accounted for. An operation may only act on material some schema version removed — one addressing a path the schema still has would rewrite a perfectly valid file on every run — and it may never name a concrete key beneath an open mapping, whose keys are the user's and unbounded, while the * segment that addresses every entry of one is legal exactly there and nowhere else. A safe value remap whose old spelling is still legal, or whose target is a free string where staleness cannot be proven, is refused because it would rewrite a deliberate choice; a remap to a spelling the new schema rejects is refused because every migrated file would then fail to load with the tool reporting success. A retired path, or a remapped-away spelling, may never come back. Finally, replaying the whole ledger over each of the surface's two reference documents — the complete packaged configuration and the sparse kit starter template, deliberately different shapes — must apply nothing, report nothing, and hand back the very bytes it was given. It reads checked-in files only and fingerprints no live model, which is what makes it safe to run in the loop agents run constantly: every failure names a file the author wrote.
  • The migration engine: A surface's ledger can now be replayed over a real file. The engine takes a document's text and returns the text it should now have, plus a per-file plan naming the entries that applied, the entries it would not apply, and why. Two reporting rules keep a plan from being more optimistic than the file: an entry is reported once — a conflicting operation routes the whole entry into the blocked list, carrying whichever of its operations did land — and an unsafe entry is rehearsed against the document first, so it stays silent on a file that does not need it rather than warning at every boot forever. Writing is transactional per file and always takes a backup: exactly one per file, named <file>.bak.<UTC stamp>, inheriting the source file's mode rather than the umask, written before the replacement and pruned only after it commits. A file that is unparseable, unwritable or changed during the run is reported as blocked while every sibling is migrated normally — unlike the .mthds fix loop, whose files only make sense together and which still commits a round all-or-nothing; both now share one set of transaction primitives. Nothing calls this from the command line yet.
  • The transform goldens: make check-migration-schemas now also performs each migration it is asked to believe in. For every ledger entry it applies the entry's operations to the frozen reference document of the version before it and compares the result with the frozen document of the version after: every path the migration creates must be one the new document has, every path the two documents share must survive, and the last link's output — read the way a user's file is really read, beneath the current defaults layer — must be accepted by the current model. That closes the one defect the other two checks pass in silence: a rename whose destination is misspelled is accounted for by coverage and skipped by convergence, and then migrates every file to a key the schema rejects while the tool reports success. The comparison is deliberately one-directional in each claim, so a bump that also adds keys, edits comments, flips defaults or ships one more entry in a packaged deck stays green.
  • Replay neutrality is now a property rather than an assertion: The guarantee a user's machine depends on — replaying a surface's whole ledger over a file already valid at the current schema changes not one byte — is checked over generated documents, not only over the two reference documents. A reference document carries exactly one value per key, so it cannot see an operation that misbehaves on the other legal spelling of an enumerated field; a sampler that reaches those spellings can. The sampler proposes each mutation from the surface's fingerprint and lets the models decide whether it lands, because schema membership is settled by validators no type projection can see: a legal enum member can still be rejected for what it requires elsewhere in the document, and a mapping typed with arbitrary string keys can still refuse a key outside a fixed set. Idempotence — replaying twice equals replaying once — and prefix coherence — a file caught halfway lands where a replay from zero lands — are checked alongside it, and each generator carries its own vacuity check, since every one of these properties is trivially true over documents nothing acts on. hypothesis joins the development dependencies.
  • pipelex doctor reports pending configuration migrations, and --fix runs them: A configuration the migration history explains now boots with a warning rather than an error — and pipelex-agent silences logging process-wide as its first act, so a machine consumer never learns from a boot that its configuration is stale. Asking was the only channel left and there was nothing to ask. The health report gains a Configuration Migrations row that is pipelex migrate's own dry run: a structured finding (up_to_date, pending, needs_attention, unavailable), the files a migration would rewrite, and separately the files it will not repair on its own — both sets, because a drifted machine usually has some of each and a reader who heard only the first would stop with a broken file still in place. pipelex doctor --fix then offers to run the migration, reaching the command's own write pass rather than a second implementation of it. The row is deliberately not scoped by --global: it answers for a command that walks both configuration directories, and a row naming fewer files than the command touches would be a surprise write. A failure inside the scan is reported as a failure to check rather than as health, and never as an exception — an exception there would replace every row of the report with a single line.

Changed

  • Breaking: where a run's bytes go is now told to the runtime, not derived from who is running (JobMetadata.storage_scope): Storage keys were built from user_id{user_id}/results/{run_id}/ for delivery, {primary_id}/{secondary_id}/… (always {user_id}/{pipeline_run_id}/…) for generated content, a flat top-level normalized/ for inputs pulled off a data: URL. That ties where the bytes live to who uploaded them, which is fine until two people share the work: the second one asks for a file the first one attached and is refused, because the only thing standing between them is a string comparison on the first path segment. A host that serves teams cannot express "these two people may read the same file" in that layout at all.

JobMetadata gains a required storage_scope: one opaque, host-supplied prefix that the runtime composes assets/, results/ and payloads/ onto and never parses. The runtime does not learn what a tenant, an organization or a method is, and that is deliberate — those are host concepts with no meaning in an MIT-licensed runtime, so threading them through the transport was rejected in favour of one string the host fills however its own tenancy works. PipelexPipeRunInput carries it across the boundary alongside a now-required user_id.

It is validated at construction, on the type. The value becomes a storage key prefix, so a .. or a leading slash in it is a traversal out of the tenant's namespace; a JobMetadata that exists is one whose scope is safe, which makes every key derived downstream safe by construction rather than by each call site remembering. This replaces a per-segment sanitizer that ran over user_id and pipeline_run_id separately — collapsing them into one slash-bearing string would otherwise have deleted that control silently.

uri_format loses both {primary_id} and {secondary_id}, replaced by one {storage_scope}. Supported placeholders are now {storage_scope}, {hash} and {extension}, and the shipped default becomes {storage_scope}/{hash}.{extension}. This is not a rename of {primary_id}: keeping that name would have left the configuration saying "primary" with no "secondary" for it to be primary to, and given an operator no way to tell that the placeholder means the whole tenant prefix rather than some id. {hash} is unchanged — the first 16 hex chars of the SHA-256 of the file's own bytes, so identical output keeps one name instead of accumulating duplicates.

A configuration naming either retired placeholder is refused at boot, by name, with the supported set listed. The free-string value domain is not something the migration ledger can prove stale — per docs/migration-ledger.md a remap_value there could only be unsafe, i.e. reported and never applied — so this is a loud failure rather than a repair. Edit uri_format in your .pipelex/pipelex.toml if you customized it.

  • Breaking: "anonymous" no longer appears anywhere on the identity or storage path, and a test keeps it that way: The dry-run paths still passed OTelConstants.DEFAULT_USER_ID as a user_id, which is the last place the telemetry placeholder was still being bound as an identity. They now pass DRY_RUN_USER_ID — a dry run has no caller in the identity sense either, so it says so in its own word rather than borrowing tracing's. The constant survives for telemetry, where "a span with no known caller" is a real concept.

test_storage_scope.py now scans every module under pipelex/ and fails if DEFAULT_USER_ID is ever bound to a user_id or a storage_scope again. The pattern deliberately matches an annotated parameter default (user_id: str = OTelConstants.DEFAULT_USER_ID) as well as a plain assignment — the first version of the guard did not, and passed a real injected regression while looking like it worked.

  • Breaking: identity is required, and "anonymous" goes back to being a tracing label: pipeline_run_setup coalesced a missing user_id to OTelConstants.DEFAULT_USER_ID — the string "anonymous", a tracing placeholder for a span with no known caller. It leaked into the identity path and became a storage key prefix, so every run without an authenticated caller wrote into one shared anonymous/ namespace where each tenant could read the others' outputs. The fallback is what made it silent: a missing identity looked exactly like a present one.

user_id and storage_scope are now required parameters of pipeline_run_setup and prepare_pipe_job, so the same omission is a TypeError at the call site instead of a shared prefix in production. The constant survives for telemetry only.

Two callers pass explicit, greppable values rather than inheriting a default: a dry run — which provably stores nothing — passes DRY_RUN_STORAGE_SCOPE, and PipelexMTHDSProtocol defaults to LOCAL_USER_ID / LOCAL_STORAGE_SCOPE, because a run on somebody's own machine genuinely has one user and no tenancy. A constructor default stating a true fact at the boundary where it is true is a different thing from an or fallback deep in the call stack, and a multi-tenant host cannot reach these by omission — the seam it calls requires both explicitly.

Fixed

  • The codegen stamp header is now read strictly: pipelex codegen check verified a generated file as pristine even when a line had been injected between the stamp's fence markers, because the content hash covers only the body below the fence — so an executable statement could sit inside a header that says DO NOT EDIT and the tree would still report as current. Any line inside the header that does not carry the file's comment prefix now makes the stamp unparseable, which the check already reports as hand-edited. The options value must also be conformant JSON: Python's non-standard NaN / Infinity / -Infinity literals are refused, so a stamp cannot hold something only a Python reader accepts — the header is a cross-language interchange format, and the second implementation of the offline check (in @pipelex/sdk) already rejected them. Commented key: value lines a reader does not recognise are still ignored, so the header can gain fields without breaking older readers. One consequence to know: a file whose header was tampered with is no longer one pipelex codegen types will overwrite — it now refuses a destination it cannot prove it owns rather than clobbering work, so such a file has to be removed before regenerating, which is what the check's advice already tells you to do.
  • pipelex codegen check now reports drift in one order, whichever half of the report it comes from: locked artifacts were listed by their full path compared as a string, while stale orphans came out in the order a directory walk found them — a component-by-component order. For a tree holding a models/ directory beside a models.py, the two halves of a single report therefore sorted paths by different rules, and a second implementation could not mirror both with one comparator. Both halves now use the plain full-string sort, and the ordering is written down as a contract: every locked-artifact drift first, then every orphan, each group by path.
  • Generated files are written with LF line endings on every platform: save_text_to_path let Python translate each newline to os.linesep, so a file emitted on Windows landed as CRLF on disk while its recorded content hash had been taken over the LF text it was built from. The verdict survived, because reads translate back, but the promise that regenerating produces byte-identical output held only within one platform — a team mixing Windows and Linux saw generated trees churn in version control with no change of content. Every artifact this writes (generated code, the codegen lock, JSON outputs, input templates) is now the string verbatim.

  • The scope was validated after the first thing that wrote with it. prepare_pipe_job ran the data-url normalizer — which stores bytes at {storage_scope}/assets/… — and only then constructed the JobMetadata whose validator is supposed to make that key safe. A scope carrying .. therefore escaped the tenant's namespace, and the guard meant to stop it ran afterwards, on damage already done. The defect was ordering, not absence: this file's own "safe by construction" claim did not hold on the one path that mattered. The scope is now validated at the top of the seam, before anything composes a key from it. Pinned by a test that asserts the normalizer was never called, with real inputs — the empty-inputs harness skips the normalizer on its own, so the same test written that way passes with the guard deleted.

  • Generated content had no leaf: it landed loose at the root of the scope. Three things write during a run and each built its key separately — the data-url normalizer under assets/, storage delivery under results/, and generated bytes (an image a pipe produced) directly at {storage_scope}/<hash>.<ext>, because uri_format rendered the whole key and shipped without one. A file under no leaf is invisible to anything listing a run's content by category, cannot be retained or dropped separately from the rest, and shares the root with every leaf added later. Generated bytes now land under generated/.

It is its own leaf, not assets/ or results/. assets/ is what the caller supplied and this is what the run produced — a provenance split an operator can act on ("drop what we generated, keep what the user gave us"). results/ is the delivery envelope: written once at the end, under fixed names, only when storage delivery is configured — whereas generated content is written during the run, content-addressed, and usually intermediate, since a generated image is more often an input to a later step than an output of the pipeline.

Breaking: uri_format renders the FILENAME only, and {storage_scope} is no longer a supported placeholder ({hash} and {extension} remain; a leading / is now refused). The prefix moved into code because a placeholder an operator may omit is an invariant that does not hold — a config dropping it put every run's generated bytes in one namespace. What the format still decides is the filename, including any subdirectory beneath the leaf.

  • Every local run with storage delivery overwrote the previous one. The run id used to be part of the storage key ({user_id}/{key_prefix}{pipeline_run_id}); collapsing the key onto the scope removed it, which is correct for a host — a hosted scope already identifies the run, <org>/<method>/<run> — but left the local default naming a tenant and no run. So every local run wrote local/results/<filename>, the same key each time, each one silently destroying the last. A local run's scope is now its run id, composed in pipeline_run_setup because that is where the run id is minted and the constructor default cannot name a run that does not exist yet. LOCAL_STORAGE_SCOPE is a sentinel meaning "no host supplied a scope" and never reaches a storage key: a tenancy segment on a laptop separates nothing, since there is exactly one tenant, while the run id is the only part that has to be distinct. Keyed on the sentinel exactly, never a prefix test, so a host-supplied scope is never rewritten.

  • A scope ending in a newline passed validation. validate_storage_scope used re.match with a $ anchor, which admits one trailing newline, so "org/mt/run\n" was accepted and the newline travelled into every storage key and log line built from the scope — an unaddressable key and a log-forging primitive. Now fullmatch.

  • The bridge payload required the scope but never validated it. PipelexPipeRunInput.storage_scope was a bare str, so a payload arriving from another process could carry a traversal that was only refused frames later, wherever a key was first composed. It is validated at construction now, making a malformed payload a decoding error that names the field.

[v0.46.4] - 2026-08-18

Changed

  • This repo now dogfoods its own migration .gitignore convention: the project-local .pipelex/ directory picked up the .gitignore that pipelex migrate/pipelex init write to keep timestamped backup copies (*.bak.<timestamp>) out of git status — the same file every consumer project has received automatically since v0.46.2.

Fixed

  • The config-sync gate no longer goes red on what the migrator leaves behind: make check compares the repository's own .pipelex/ against the pipelex/kit/configs/ templates by contents, and it had never been taught about the two things pipelex itself writes into a configuration directory — the .gitignore added in v0.46.2 (which has no kit counterpart by design) and the timestamped *.bak.<stamp> copies a real pipelex migrate leaves beside every file it rewrites. Dogfooding the .gitignore in v0.46.4 broke make check outright, and any developer who ran pipelex migrate in a checkout broke it again — putting the dirtied-repository problem that .gitignore exists to solve straight back, one gate over. Both the check and make up-kit-configs now look past those artifacts, matching backups and .rescue. copies by a glob built from the same infixes and stamp shape the backup namer writes, so a rename there cannot orphan the rule. This also closes a latent hole in make up-kit-configs, which would have mirrored a developer's backup copy into the shipped kit.

[v0.46.3] - 2026-08-18

Fixed

  • The configuration-directory .gitignore now reaches machines with nothing to migrate: v0.46.2 added a .gitignore inside .pipelex/ to keep migration backups out of git status, but pipelex migrate only wrote it on a run that actually had a file to carry forward. A machine already at the current schema was reported clean and returned before the write pass, so it never got the rule — and that is the state every user is in between schema changes, which meant in practice almost nobody received it. Any real (non---dry-run) pipelex migrate now writes it whether or not there is anything to migrate; a run whose migration you decline still writes nothing at all.

[v0.46.2] - 2026-08-18

Fixed

  • Migration backups no longer dirty your repository: pipelex migrate copies every file it rewrites to <file>.bak.<timestamp> beside the original, and inside a project's .pipelex/ those copies showed up as untracked files in git status — a dozen of them after a single run. Pipelex now keeps a .gitignore inside the configuration directory itself, ignoring exactly the timestamped copies it makes; it is written by pipelex init and by any real pipelex migrate, so a machine whose .pipelex/ predates this gets the rule from the run that would otherwise have made the mess. An existing .gitignore there is never modified, and a .rescue. copy is deliberately left visible because it is the one the report asks you to go and collect.

[v0.46.1] - 2026-08-18

Changed

  • Test-duration map refresh is now incremental (developer tooling): make store-test-durations (std) no longer re-runs the whole suite. It collects the tests, measures only the ones missing from .test_durations, and leaves recorded values alone — which takes seconds when little has been added, instead of several minutes. This is driven by what actually unbalances the CI shards: measured against the real eight-way split, a map with stale values but complete coverage costs about 7% of balance, while one with current values and missing entries costs over 50%, because pytest-split imputes an unknown test at the suite mean (~0.25s) when the median test is ~0.002s. Added make store-test-durations-force (stdf) for the occasional full re-measurement. The refresh now also prunes entries whose test file no longer exists, making the test_test_durations_paths gate self-healing, and keeps any recorded value that has not meaningfully moved, which cuts the release diff for that file by about 95% — it had grown large enough that automated PR reviewers declined to read the branch at all. New page: docs/contribute/test-duration-map.md.

Fixed

  • Migrator comment fidelity: Fixed pipelex migrate so structural operations no longer leave comment banners behind or attach them to the wrong sections. Moved keys and tables now carry their introducing comment block with them; deleted keys and tables now drop their introducing comment block; items appended under an existing table now land before that table's trailing banner; and the .mthds fix path now drops comments above stripped native-concept redeclarations. Note: files already migrated by v0.46.0 are not rewritten automatically—tidy the comments by hand or restore the .bak files and re-run pipelex migrate.

[v0.46.0] - 2026-08-17

Added

  • Configuration migration engine & commands: Introduced pipelex migrate and pipelex-agent migrate to bring user configuration files up to the current schema. Migrations rewrite files in-place (preserving user values), create timestamped .bak backups, and run without booting Pipelex, so no credentials or network access are required. Both walk the global ~/.pipelex/ and the project .pipelex/ only, one level deep, and enter a subdirectory only when a configuration family owns it. Configuration validation errors now include a structured migration block so machine consumers can distinguish a stale config from an invalid one.
  • Boot tolerance for stale configs: A config that is out-of-date but explainable by the migration ledger no longer hard-crashes the boot. Pipelex replays the migration ledger in-memory, boots successfully, and warns the user to run pipelex migrate. Nothing is ever written by a boot, so the warning returns until the command is run, and a file the in-memory re-validation still refuses fails the boot as before — tolerance widens what starts, never what is accepted.
  • pipelex doctor migration integration: Added a "Configuration Migrations" row that dry-runs pending migrations; pipelex doctor --fix now offers to apply them. The row reports the files a migration would rewrite and, separately, the files it will not repair on its own, because a drifted machine usually has some of each and a reader who heard only the first would stop with a broken file still in place.
  • Migration ledgers & gates (developer tooling): Every configuration surface — pipelex.toml and its tiers, telemetry.toml, pipelex_service.toml, and the inference backend definitions — now carries a checked-in ledger recording each shape change as data, plus a golden chain pinning what the schema looked like at each version. Added make check-ledger (cl) to verify ledger legality and convergence, make check-migration-schemas (cmig) to ensure all schema changes are accounted for in the ledger, and make up-migration-schemas (umig, with umigf to force past a refusal) to regenerate the goldens. umig deliberately left make up: rewriting a head golden after deleting a field would erase the very removal the coverage gate exists to catch.
  • Migration operation vocabulary: Fix operations became a discriminated union on kind, published as two subsets — every kind for the .mthds fix path, and the structural kinds only for ledgers, so an operation that would write a value into a user's file is refused when the ledger is parsed rather than at review time. move_key relocates a key across tables and remap_value rewrites a value against an explicit old-to-new mapping, with a * segment addressing every entry of an open mapping. A new CONFLICT outcome separates "this cannot be done without choosing on your behalf" from the benign SKIPPED it used to hide inside; a conflicting operation writes nothing at all.
  • A migration names what it cannot explain, and refuses a file it cannot reach: Every plan now carries an unexplained[] listing paths of the migrated document that the current schema does not know and no ledger entry accounts for — a typo, or a file written by a newer Pipelex, two readings the tool cannot tell apart. Separately, a file declaring a [meta] schema_version below its ledger's floor is refused by name (unsupported_schema_version) instead of being run over, left unchanged, and reported clean. No value read from a user's file is ever rendered in either report.
  • [meta] schema_version is reserved in configuration files: Every configuration-surface reader now tolerates and strips it. Nothing writes it — the key exists so a later release can stamp a version into a file without that being a breaking change today.
  • Value domain narrowing detection: The migration engine now detects schema changes that narrow allowed values (e.g., int to str, or tightening a ge=1 bound to ge=5) and forces the author to provide a migration path or an unsafe manual warning. Read as a path diff those look like additions, so they used to cost only a golden regeneration and let the next boot reject a valid file with a green gate behind it. An unsafe entry must now name the narrowed paths in declared_narrowed_paths, and that material is traced through later entries so the same file keeps being reported for as long as it carries it. To see a bound at all the fingerprint records one, over a closed whitelist (gt, ge, lt, le, min_length, max_length, multiple_of); pattern is excluded permanently, because regex containment is not decidable and every pattern edit would read as a tightening.
  • Replay neutrality is checked as a property, not asserted: The guarantee a user's machine depends on — replaying a surface's whole ledger over a file already valid at the current schema changes not one byte — is now checked over generated documents rather than only the two reference documents, which carry one value per key and so cannot reach the other legal spelling of an enumerated field. Idempotence and prefix coherence are checked alongside it, each generator carrying its own vacuity check. The coverage gate also performs every migration it is asked to believe in, applying each entry to the frozen document of the version before it and holding the result against the version after, which catches a rename whose destination is misspelled — accounted for by coverage, skipped by convergence, and otherwise shipped. hypothesis joins the development dependencies.
  • Claude skills: Added the add-migration skill to automate ledger entry creation from coverage gate refusals, and updated the release skill to enforce ledger completeness before cutting a release.

Changed

  • Configuration root reshaped (Breaking): The pipelex.toml root structure has been reorganized to mirror the runtime layers, and the redundant _config suffix has been dropped from keys: [runtime] (process-scoped infrastructure), [inference] (the model-calling seam, formerly [cogt]), [interpreter] (library-scoped method machinery), and [kit] (dev tooling). So [pipelex.log_config.rich_log_config] is now [runtime.log.rich_log], and code reading the config tree moves with the keys: get_config().pipelex.storage_config is get_config().runtime.storage. Class names keep their suffix — the redundancy was in the key, not the type. Migration: run pipelex migrate after upgrading — ledger entry pipelex-config@3 rewrites ~/.pipelex/ and every project .pipelex/ file in place, keeping every value you chose.
  • Templating style is now an authoring decision (Breaking): How a pipe's inputs are tagged into a prompt is declared on the pipe itself (templating_style on PipeLLM) rather than inferred from the model, with one runtime default for everything that declares nothing ([inference.templating].default_templating_style, shipped as xml). The global default is now XML tags (<tag>...</tag>) instead of back-ticks, so every prompt rendered for an OpenAI-family model will change shape. Compose, image-generation, search, and structuring prompts follow this same runtime default, so any template of yours using | tag changes shape whichever model family you run on. Migration: run pipelex migrate — ledger entry pipelex-config@2 carries a tuned [pipelex.prompting_config] forward, the default style travelling to default_templating_style while the per-target prompting_styles map is dropped, since a style is no longer chosen per model; the backup beside the file is where the old map remains readable.
  • Inference backend files are a migration surface (Breaking): prompting_target was removed from every backend file we ship, but pipelex init never overwrites, so the key survives in inference/backends/*.toml on every installation that predates this release — where it is fatal twice over: in [defaults] it is copied wholesale into every model of the file, and on a single model it is rejected by name as an unknown, non-header key. Those files were previously reachable by no surface, no walk and no gate; they now are, and the backend loader replays a stale directory in memory so the boot survives with a warning naming every file it carried. Migration: run pipelex migrate — ledger entry inference-backend@2 deletes prompting_target from every root table of every backend file, in ~/.pipelex/ and each project .pipelex/, keeping one timestamped backup per file.
  • Strict Jinja2 templating filters (Breaking): The tag, format, and with_images filters no longer fall back to a built-in style when the rendering context lacks one; a missing style now raises Jinja2ContextError. Every prompt-rendering entry point now supplies a resolved style, so a missing one is a bug rather than a shape nobody chose. An explicit filter argument (| format("markdown")) is unaffected, and an unknown format name is reported as a template error instead of a bare ValueError.
  • templating_style tables reject unknown keys (Breaking): TemplatingStyle and the rich template table of PipeCompose are now extra="forbid" like every other blueprint. A misspelled optional key — { tag_style = "xml", text_formt = "markdown" }, or templating_stile on a template table — used to be dropped silently, yielding a prompt rendered under a shape the author never asked for; it is a validation error now. The MTHDS JSON Schema gains additionalProperties: false on both definitions.
  • Unknown per-model backend keys must be header-shaped (Breaking): Any unrecognized key in a per-model table inside inference/backends/*.toml is sent to the provider as an HTTP header. To prevent typos from silently becoming headers, these keys must now contain a hyphen (e.g., x-portkey-provider); keys without hyphens fail the boot, in strict and lenient boots alike, naming the key, the model and the file. The key must also be usable on the wire — RFC 7230 token characters only, with a printable single-line string value — and a hyphenated spelling of a real field (max-tokens) is rejected by name. In the remotely served Pipelex Gateway config the same rule prunes such keys instead of failing, as version skew.
  • Telemetry config is migratable: telemetry.toml is now a migration surface. Upgrading from the legacy flat format to the [custom_posthog] format is handled by pipelex migrate, preserving user API keys and exporters instead of forcing a destructive pipelex init telemetry reset. The loader now raises through the same reporting path as every other configuration surface, so the human CLI, the agent envelope and pipelex doctor all say whether the file is old, wrong, or reported on by a migration that would not repair it, rather than each holding a hardcoded remedy. Migration: run pipelex migrate — ledger entry telemetry-config@2 lifts a flat first-generation file into [custom_posthog], keeping every value that has a home in the current shape and saying which settings it drops and why.
  • The migration ledger contract is published, and a config-model change owes it a review: docs/migration-ledger.md joins the documentation navigation beside the other contributor contracts. The config-docs drift contract's review list gains the migration ledgers and the two modules holding the validators that narrow a configuration value domain — a narrowing expressed in a validator is invisible to the fingerprint projection the coverage gate reads, so no derived check can demand the ledger entry it owes, and a review obligation is the only place that can catch it.
  • Keyless dry runs: pipelex run --dry-run no longer requires inference credentials, bringing it to parity with pipelex-agent run --dry-run. It boots keyless with real model specs, every run forced to DRY. Anyone who relied on pipelex run --dry-run as a credentials check should use a live run for that.
  • Reasoning budgets: Thinking budgets are now worker-owned (keyed by family constants like "anthropic" or "gemini") rather than read from the removed prompting_target. Behavior is unchanged; the budget maps keep their anthropic and gemini keys.
  • Remote gateway config: Bumped the remote inference config to pipelex_remote_config_12.json, whose defaults no longer declare the removed field; earlier releases stay pinned to _11, which is frozen. Unknown keys in remote defaults are now pruned rather than rejected, to tolerate version skew — local backend files stay strict.

Fixed

  • pipelex doctor bracket escaping: Fixed a crash where pipelex doctor failed with a MarkupError when a file path or error message contained brackets (e.g., [dev]) that Rich interpreted as formatting tags — worst exactly when the report is what the user came for. Every value the report interpolates is now escaped, including configuration paths, backend names, deck filenames and the text of every exception the --fix pass reports.
  • pipelex doctor --fix no longer offers to overwrite a telemetry config it could migrate: The fix machinery decided what it could repair by searching the row's message for "format has changed", and on a match offered to write a fresh telemetry.toml, discarding every setting in it. It now branches on a structured finding, so an out-of-date file is answered with pipelex migrate and an invalid one is left to a person. The doctor's telemetry check also strips the reserved [meta] table the way boot does, instead of reporting a perfectly bootable file as invalid.
  • Agent CLI double envelopes: Fixed pipelex-agent models and check-model printing two JSON error envelopes on a single stream, resulting in invalid JSON output. agent_error leaves through typer.Exit, which is a RuntimeError and not a SystemExit, so a command boundary re-raising only SystemExit caught the error it had just reported and reported it again.
  • Dotted-key renaming: Fixed a bug in the TOML fix applier where renaming a dotted key (e.g., a.b = 1) accidentally converted it into a block header ([a.b]), swallowing subsequent scalar values into the wrong table. The applier now also refreshes every chunk of a table written in several places, so renaming a in a [a.b][a.c][a.b.d] layout no longer leaves the later chunks rendering under the old name. Both reach every caller of the applier, the .mthds fix loop included.
  • Symlink migration: Migrating a symlinked configuration file now correctly replaces the file at the target of the symlink rather than replacing the symlink itself with a regular file.
  • Backup rotation race conditions: Fixed backup file rotation so a failed migration run deletes its own temporary backup but never deletes a backup created by a concurrent run. A copy kept because a write could not be confirmed is renamed out of the .bak. rotation into .rescue., so a later run cannot prune away the very file the report told the user to go and get. The backup's directory entry is now fsync-ed before the target is replaced, so "back up first, replace second" holds across a power loss and not only a process exit.
  • Boot failure reporting: Fixed a failure to load pipelex.toml producing a bare Python traceback instead of the intended user-friendly, field-level error message. RuntimeBoot and the doctor both caught pydantic.ValidationError around setup_config, and the main configuration raises ConfigValidationError instead, so the arm never fired for the one configuration everything depends on. Both sites now catch either shape a refusal takes and share one helper.
  • Backend errors name the model, the backend and the file: A refusal cut down to max_tokens: Input should be a valid integer sent the reader to grep a directory of files that all have a max_tokens; the loader now says which model, which backend and which file before it quotes the pydantic analysis, and the model deck's message keeps its paths the same way. A backend table whose own fields fail the blueprint (endpoint = 42) is raised as the library's own validation error rather than escaping as a bare pydantic ValidationError that no clause in the boot named. A message carrying a migration block no longer also suggests pipelex init config, which would have reset every value the block had just promised to keep.
  • Gateway enabled truthiness: Fixed enabled = 1 in backends.toml being evaluated differently by the loader and the boot process, which silently dropped the gateway. Both readers now read the value the same way.
  • A blocked configuration file says which state it is in: FileBlockedReason gained unreadable and state_uncertain, so an unreadable file is no longer reported as unwritable and a write whose outcome the transaction could not describe is no longer reported as one that never happened.

Removed

  • prompting_target (Breaking): Removed from LLMSetting, InferenceModelSpec, InferenceModelSpecBlueprint, and all shipped backend TOML files, along with the PromptingTarget enum and the per-target style map. Delete prompting_target from your local .pipelex/inference/backends/*.toml files, or the strict boot loader will reject them — or run pipelex migrate, which does it for you. Check portkey.toml in particular: the key appears in [defaults] and on individual model entries, and a surviving per-model one is rejected by name as an unknown, non-header key.
  • Legacy prompt templates: Removed LLMPromptTemplate, LLMPromptFactoryAbstract, LLMPromptTemplateInputs and LLMPromptTemplateInputsError, which relied on hardcoded XML+Markdown styles predating the new configurable templating system and had no production consumer.
  • Deprecated config keys: Removed [migration.migration_maps], session_id, and plugins.boot_orchestrator from the root configuration. Renames are ledger entries now, so the old-to-new name hints in validation errors go away, .mthds hints included; boot_orchestrator is strictly a boot argument — pass --orchestrator or Pipelex.setup(boot_orchestrator=...) and read it back at run time from the runtime_hub accessor rather than off the config.

[v0.45.0] - 2026-08-14

Added

  • Per-node token usage and cost on the run graph (Breaking): GraphSpec nodes gained a usage object and the graph gained a run-level usage rollup, so a consumer can finally answer "which pipe spent the money". The link already existed in the event stream — every UsageReportEvent names its graph node — and the assembler was dropping it; it now folds those events into per-node totals (own inference calls, tokens by category, cost) plus a subtree_* rollup up the parentage chain, so a controller reports what its whole branch spent. Cost is deliberately three-valued and never overloaded: null means unrated (no rate table — own-GPU models, dry and mock runs, so every dry-run graph is unrated), a rated_inference_calls count marks a mixed rated/unrated node's cost as a lower bound rather than a total, and total_tokens is input_joined + output — never the sum of nb_tokens_by_category, where input_cached is a subset of input. Usage whose node cannot be resolved lands in a typed unattributed bucket instead of being dropped, so the graph's total cannot silently disagree with the cost report's. Every dollar comes from compute_tokens_usage_cost, the same engine the cost table and the API wire records use. Breaking: NodeSpec and GraphSpec are extra="forbid", so an older pipelex rejects the new JSON. See Per-node Usage Attribution.

  • The run graph now records which model actually ran, per node. NodeUsageSpec gained by_model (and subtree_by_model): the inference_model_name, inference_model_id, call count and cost of every model a node actually used, ordered most-used first. Until now a GraphSpec named a model in two places and neither was the outcome — the pipe blueprint holds the authored choice ($writing-factual), and execution_data.resolved_model holds the handle LLMSetting.model carried, which is frequently still an unresolved alias (@default-premium) because aliases resolve later, at inference time. by_model reads from the usage records instead, so it survives alias resolution, deck defaults, and any fallback or retry that landed somewhere other than what was asked for. It is a list rather than a single value because a node routinely uses more than one model: a PipeLLM's text pass and its object-structuring pass resolve separately. Per-model cost follows the same rule as the node's — None iff no call to that model carried a rate table.

Fixed

  • A dry run of a PipeCondition is now reproducible. pipe_dependencies() returns a set[str], and the dry-run arm walks every branch into the same working memory under the same output_name — so the branch iterated last is the one whose stuff the enclosing pipe reports as the condition's output. Iterating the raw set made "last" a function of string-hash order, which varies per process, so the same bundle dry-run twice produced two different graphs: in a sequence routing to two branches that each name their own tail result, the sequence's output name flipped between them run to run. Nothing about a dry run should depend on PYTHONHASHSEED, and anything diffing graph specs across runs — a fixture corpus, a static-vs-dry parity check — saw phantom changes. The loop sorts now, which is what the two other order-sensitive readers of this set (signature_walk, the output_option_N renderer) already did. Live runs were never affected: they take exactly one branch.

  • A {concept, content} envelope carrying an empty list is now a value, not an error: a plural slot is never absent — when nothing is found it is the empty list — and the top-down shaper has always honoured that for a bare [] (D2). The bottom-up factory did not: it infers a list's item type from the first item, so an empty list raised "Cannot create Stuff from empty list in content". The two spellings of one input therefore disagreed — a caller with no pictures could send [] and run, or send {"concept": "native.Image", "content": []} and fail — which made a plural input with nothing in it unrunnable for every caller that wraps its inputs. The envelope names the concept, so there was never anything to infer; it now builds an empty ListContent. Case 1.5 (a bare empty ListContent, no concept anywhere) still raises, and must: nothing there says what the items would have been.

  • Kernel step identity: PipelexKernel.make_step_metadata now accepts pipe_code to name the pipe a step is running, mirroring what the interpreter stamps on its live and dry paths — so log correlation, usage accounting, and per-step labelling see a named step for kernel-driven runs. The key is omitted from the metadata update when not supplied, so a run-level pipe_code is never silently erased; the direct-call façade (llm_text/llm_object) stays deliberately anonymous. PipelexKernel.make also accepts an injectable step_id_source for per-step ids, defaulting to uuid4, so a kernel hosted inside a replay-based executor can supply a replay-safe source.

[v0.44.0] - 2026-08-13

Added

  • Entry-point resolution affordances: Added get_required_entry_pipe, get_optional_entry_pipe, get_required_entry_concept and get_optional_entry_concept to the interpreter_hub and library APIs. These are designed for human-supplied codes (CLI arguments, API payloads) and safely search across domains, unlike strict in-body references. Installed dependency packages are excluded from that search, so adding a dependency cannot make one of your own codes ambiguous.
  • Build-time crate qualification: Introduced a standalone crate_qualification pass that fully qualifies in-body references to their owner domain at library build time, ensuring the normalizer and the live library strictly agree on reference resolution. This supersedes v0.43.0's Cross-domain pipe refs in normalized crates: the two readers still agree, but on owner-domain qualification rather than on a crate-wide search.

Changed

  • Strict in-body pipe reference resolution (Breaking): Bare in-body pipe references (sequence steps, parallel branches, condition outcomes, batch_over targets) now resolve only within their own domain. To reference a pipe in another domain, use the fully qualified form (e.g. sales.generate_tagline). This enforces [exports] visibility rules that were previously bypassable, and lets multiple domains safely reuse the same pipe code without library load failures.
  • Automatic search scope for concepts (Breaking): Replaced the search_domain_codes list parameter across the public API (PipelexMTHDSProtocol, pipeline_run_setup, prepare_pipe_job, InputShaper, WorkingMemoryFactory, etc.) with a single search_scope string derived automatically from the entry pipe. Every entry-lookup refusal — invalid string, miss, ambiguity — is now raised as ConceptLibraryConceptNotFoundError, a class the input-shaping handlers catch.
  • Fully qualified validation error payloads (Breaking): The missing_pipe_code field on unresolved-dependency validation items now returns the fully qualified reference the compiler attempted to resolve (e.g. marketing.render_html) rather than the bare spelling the author wrote.
  • Ambiguity handling for bare code lookups: Bare pipe codes typed by users (via pipelex run, show, which, or API requests) still search across all domains, but when two domains declare the same code the system now raises a clean ambiguity error listing the candidates instead of failing with a traceback.
  • Per-domain scoping in pipelex fix: Rename collisions in the fix loop are now scoped per-domain rather than crate-wide, so two same-named pipes in different domains rename independently without bailing.
  • PipeFunc transports qualified references: PipeFunc now transports the fully qualified pipe_ref to the executor instead of the bare code, preventing resolution failures in strict remote environments.
  • Expanded agent quality gate: make agent-check now includes drift-check and check-hub-layering, so open code↔docs drift contracts fail the local quality gate before reaching CI. The digest reads the git index, so stage changes for the gate to see them.

Fixed

  • Verdict-neutral CLI lookup errors: The build and codegen CLI commands no longer prepend a misleading "not found" verdict to stderr when a bare code is ambiguous; the prefix is now verdict-neutral.
  • Time native conversion in PipeCompose: Time and Time[] native concepts are now correctly converted to datetime.time and list[datetime.time] when copied into native-typed fields via PipeCompose.
  • Batch sub-pipe code derivation: Fixed batch_over synthesized pipes deriving their code from the qualified reference rather than the local code, which caused false-positive "namespace prefix" warnings on every batched run.

Removed

  • ConceptProviderAbstract.get_required_concept_from_concept_ref_or_code: Removed in favor of the new get_required_entry_concept method to standardize entry-point concept resolution.

[v0.43.1] - 2026-08-12

Changed

  • PipeRunParams.batch_max_concurrency is now required (Breaking): The field no longer defaults to None, which previously made an omitted value indistinguishable from an explicitly authored unbounded concurrency (max_concurrency = "unbounded"). Omitting it now raises a ValidationError early—on both direct construction and payload decoding—to prevent accidental unbounded fan-outs.
  • Migration: Construct PipeRunParams via PipeRunParamsFactory.make_run_params(), the single writer for run-scoped invariants (run_mode, pipe_stack_limit, batch_max_concurrency). Integration test fixtures now route through the factory via a new make_mode_guarded_run_params helper.
  • Docs: Added a warning to docs/under-the-hood/pipe-routing-and-execution.md on using the factory method.

Fixed

  • Empty lists in concept envelopes are now parsed as values, not errors: The bottom-up factory previously raised "Cannot create Stuff from empty list in content" when given an empty list inside a concept envelope (e.g., {"concept": "native.Image", "content": []}). Since the envelope names the concept, item-type inference is unnecessary, so it now correctly yields an empty ListContent. This aligns the bottom-up factory with the top-down shaper; callers can pass [] or {"concept": "...", "content": []} to represent an empty plural slot. A bare empty list with no concept still raises an error, as its type cannot be inferred.
  • Docs: Added a note to docs/building-methods/pipes/provide-inputs.md clarifying that an empty list represents an empty plural slot, whereas omitting the key fails with a "missing required inputs" error.

Security

  • uv.lock refreshed onto patched dependency versions: A targeted lock refresh clears the open Dependabot alerts, with no pyproject.toml constraint changes — every declared range already permitted the patched version, so this was stale-lock drift rather than a constraint problem. Bumps aiohttp, cryptography, datamodel-code-generator, pillow, pyasn1, pymdown-extensions and soupsieve. The alerts left open are held by torch, reachable only through the optional docling extra, whose upgrade would drag torchvision, triton and the NVIDIA CUDA stack along with it.

[v0.43.0] - 2026-08-12

Highlights

Every operator's execution semantics is now importable without the interpreter. The new public pipelex.kernel subpackage extracts what a step does — deck resolution, prompt assembly, generation, memory write-back — into plain functions covering every operator, all servable on a kernel-only boot with zero .mthds loaded. The layer below the interpreter takes the kernel's name, and plugins now declare which side they belong to through their entry-point group, so an interpreter-side plugin is never imported into a kernel-only process. Distributed execution sharpens alongside — a run_batch_branch router hook lets a backend isolate each batch branch, and the fan-out bound is frozen onto the run to close a replay hazard. Breaking changes are import-path moves, an error-class unification, a new required build_registrar parameter, a retired plugin entry-point group, and stricter ISO 8601 and failure classification.

Added

  • Pipelex kernel (pipelex.kernel): New public subpackage holding operator-execution semantics as importable functions — deck resolution, prompt assembly, generation, memory write-back — no longer reachable only through a fully booted interpreter with a loaded library. Every operator is covered (LLM and structuring, extraction, image generation, search, template composition, Python function calls), every call is servable on a kernel-only boot with zero .mthds loaded, and a boot-contract test pins it. Two arms stay with the interpreter because their inputs are language or deployment artifacts rather than execution semantics: PipeCompose's construct mode and PipeFunc's pluggable executor. See Pipelex Kernel.
  • PipelexKernel facade with typed result envelopes: A facade class managing per-run state (JobMetadata, CogtRunParams) with ergonomic entry points such as llm_text and llm_object. Every kernel operation returns a strongly-typed envelope (LlmTextResult, LlmObjectResult, ExtractResult, ImgGenResult, SearchResult, ComposeResult, FuncResult) carrying the produced content, the updated memory, and tracer intermediates. pipelex.kernel.memory_ops standardizes the memory boundary with shape_inputs, store_result and typed extraction helpers.
  • run_batch_branch router hook: PipeRouterProtocol gained a second dispatch entry point, used by PipeBatch for each per-item fan-out branch — the only signal a router gets that a dispatch is a batch branch rather than an ordinary step. The default body delegates to run, so in-process execution is unchanged and every existing router keeps working untouched (routers subclass PipeRouterProtocol, so they inherit it); a distributed backend can override it to give each branch its own isolation. Documented on the "Pipe Routing and Execution" page and listed in the Orchestrator SPI.
  • Exceptions-aggregate layering gate: A new AST test bans kernel-layer packages from importing the all-errors aggregate pipelex.exceptions, whose re-exports pull in the interpreter. It matches module-level, function-local and string-named imports alike, closing the blind spot neither check-hub-layering nor the import-closure test can see. The fix for a violation is always to import the error class from the module that defines it.
  • Plugin validation errors: PluginDeclaredInMultipleGroupsError, PluginLayerViolationError and RetiredPluginEntryPointGroupError give loud, actionable startup failures for a misdeclared plugin, each with its own error reference page.

Changed

  • Interpreter operators delegate to the kernel: PipeLLM, PipeStructure, PipeExtract, PipeImgGen, PipeSearch, PipeCompose and PipeFunc now delegate their core execution semantics to the kernel functions — a zero-behavior-change refactor. pipe_operators/llm/helpers.py is gone, its structure-prompt derivation absorbed by the kernel.
  • "Runtime layer" is now "kernel layer" (Breaking): The layer below the method interpreter takes the name of the package that defines it. pipelex.providers.builtins exports KERNEL_BUILTIN_PLUGINS and KERNEL_CORE_UNCONDITIONAL_PLUGIN_NAMES; the hub-layering guard declares KERNEL_LAYER_PACKAGES and answers is_kernel_layer. runtime_bridge keeps the word because it means the orchestration venue, not the layer; runtime_hub and runtime_boot still carry the layer's former name and are renamed when the layer moves into its own package.
  • Plugins declare their layer through their entry-point group (Breaking): The single pipelex.plugins entry-point group is replaced by pipelex.plugins.kernel and pipelex.plugins.interpreter, and PLUGIN_API_VERSION bumps to 4. A kernel-only boot queries the kernel group alone, so an interpreter-side plugin's module is never imported into a process that only runs kernel operations. The declaration is enforced at register time — a kernel-group plugin contributing an orchestrator, a bundle validator, a PipeFunc executor or an interpreter-layer hub slot fails loud, and declaring the same plugin under both groups is a startup error; the restriction is one-directional, so an interpreter-group plugin may still contribute kernel-tier capabilities. pipelex plugins list gains a Group column. This governs which plugins are discovered and what they may register, not what a plugin's module imports.
  • build_registrar gained a required entry_point_groups parameter (Breaking): Every caller must now say which entry-point groups to read; there is no default, and a call at module scope fails at import rather than at first use. This affects any host embedding Pipelex that resolves plugin contributions itself — notably one reading get_http_error_mappers() to wire transport-fault handling. Pass ENTRY_POINT_GROUPS from pipelex.interpreter_plugins.builtins for a full boot, or KERNEL_ENTRY_POINT_GROUPS from pipelex.providers.builtins for a kernel-only one.
  • Prompt reference types moved (Breaking): ImageReference and DocumentReference (with their *Kind enums) moved from pipelex.pipe_operators to pipelex.kernel.prompt_references. They describe how a prompt resolves an image or document out of working memory — execution semantics, not a language artifact.
  • Prompt resolution errors unified (Breaking): LLMPromptBlueprintValueError and ImgGenPromptBlueprintValueError are replaced by pipelex.kernel.exceptions.PromptContentError, moved with the code that raises it. Both blueprints keep their fields, their validation and their make_*_prompt signatures.
  • Image-generation prompts are buildable without the interpreter: pipelex.kernel.img_gen_prompt.assemble_img_gen_prompt is the image counterpart of the kernel's LLM prompt assembly — image-reference resolution, [Image N] placeholder numbering and template rendering. Image generation was the one operator a kernel-only caller could not reach; ImgGenPromptBlueprint now delegates to it, keeping its parse-and-validate role and the max_prompt_images limit.
  • StructuredContentComposer no longer takes pipe_run_params (Breaking): The constructor parameter and the attribute behind it were dead — stored, threaded down to nested composers, and never read — and the docstring's claim that they gated dry-run mode was stale. Dropping them leaves the composer importing nothing from pipelex.pipe_run.
  • Keyless-boot forced-DRY rule has one home: pipelex.runtime_hub.resolve_run_mode_for_boot now owns the coercion a keyless boot (needs_inference=False) applies to a requested run mode, and both run-params factories call it. The pipe tier is unchanged; the kernel tier's PipelexKernel.make previously minted a LIVE CogtRunParams on an offline boot and now gets the same forced DRY and the same warning as a pipe run.
  • Documentation: New Pipelex Kernel page, with Hub layering convention, the plugin pages and the under-the-hood execution pages updated for the kernel rename, the entry-point group split and the batch-branch hook.

Fixed

  • Date and Time could not be produced by a live PipeLLM: A PipeLLM whose output was Date, Date[] or Time failed every real run on date_type / time_type — the structured-output layer validates the model's response in strict mode, where a mode="before" validator forfeits pydantic's strict-JSON acceptance of ISO strings. Both natives now parse ISO 8601 themselves and return a real date / time, so the strict and lax paths agree and the guards (no epoch-seconds, no datetime on a calendar date) still fire in both. Time gained the numeric-string guard Date already had.
  • One ISO 8601 contract for the Date and Time natives (Breaking): pipelex.core.stuffs.iso_temporal now owns what those natives accept — the extended forms only (2026-07-07, 15:40:00+02:00) — and both the content models and StuffContentFactory parse through it, so a model-generated value and an authored one are held to the same contract. It also rejects the end-of-day 24:00 form, which names the next day's midnight: Python 3.14 reads it as this day's midnight where 3.11-3.13 raise, so a value could silently land a full day early on part of the supported interpreter range. Breaking for authored inputs using a non-extended spelling: "15:40:00+0200", "2026-07-07T154000", "24:00:00" and surrounding whitespace are now refused.
  • Cross-domain pipe refs in normalized crates (Breaking): A bare pipe ref calling a pipe declared in another domain was normalized by prefixing the caller's domain, naming a pipe the crate does not hold — nothing raised on the way in, and the crate round-trip broke the method at run time with PipeNotFoundError. Normalization now resolves a bare ref against the whole crate exactly as the live library does, and raises CrateNormalizationError when the ref is ambiguous or matches no pipe.
  • Structureless concepts in the python-structures projection (Breaking): A concept declaring only a description was projected as a StructuredContent subclass while the runtime promotes the same declaration to a refinement of native Text — so the interpreter's text-vs-object dispatch answered differently depending on which class it was handed, and the mismatch raised nothing. The projection now emits TextContent as the base, matching the runtime.
  • Unimportable python-structures module for a concept refining Anything: Anything is the one native with no content class of its own, so a refining concept fell back to the root StructuredContent — emitting the class name without registering its import, and the generated module raised NameError on the consumer's first import. The fallback now goes through the renderer that registers the import.
  • Format-stable import blocks in generated Python: A generated import line whose merged names crossed 88 columns was emitted flat, so the first ruff format in a consumer's tree rewrote it — changing the body bytes and making pipelex codegen check report the file as hand-edited. Import blocks now pre-explode past the threshold like every other emitted construct.
  • Input shaping honors its injected concept provider (Breaking): InputShaper consulted its injected ConceptProviderAbstract for concept resolution and compatibility, then resolved each value's structure class from the process-global class registry anyway — the ambient lookup the provider parameter exists to replace. It now resolves through the provider, so a caller supplying its own provider can shape inputs without a loaded library. Breaking on one failure path: a declared structure class that does not resolve raises ConceptStructureClassNotFoundError instead of a StructureValidationError that pointed the author at their input rather than at the concept.
  • A dry run now identifies the pipe it is running: dry_run_pipe stamps the running pipe onto the JobMetadata it hands down, exactly as live_run_pipe already did — previously log correlation and the per-step labelling a distributed backend derives saw an anonymous step in DRY and a named one in LIVE. Telemetry stays live-only: the dry copy clears otel_context rather than inheriting a live span.
  • Explicit image-generation seed of 0 respected: Building image-generation job params swallowed an explicit seed of 0 through a truthiness check and fell back to the configured default. 0 is a legal seed and now goes through; the "auto" literal still means the backend picks one.
  • PipeBatch fan-out bound is frozen onto the run (Breaking): The [pipelex.pipeline_execution_config] max_concurrency setting is now read once, when the run's parameters are built, and carried as PipeRunParams.batch_max_concurrency instead of being re-read inside every PipeBatch — editing the config mid-run no longer reshapes in-flight batches. This closes a durable-execution hazard: the bound decides where a backend's task boundaries fall between branch dispatches, so a worker redeploy mid-run could make a replay group its dispatches differently from the recorded history. Breaking only for code that mutated max_concurrency mid-run and expected the change to take effect.

Removed

  • Retired the pre-split pipelex.plugins entry-point group (Breaking): A plugin still published under it now fails the boot with a RetiredPluginEntryPointGroupError naming the plugin and the group it should move to, rather than being silently undiscovered. Republish under pipelex.plugins.kernel or pipelex.plugins.interpreter.

[v0.42.0] - 2026-08-01

Added

  • New classified error types: Introduced specific error classes for clearer debugging and handling, each with dedicated documentation pages:
  • OutputStructureSchemaError: raised when an output structure class cannot produce the JSON schema required by a structured leaf.
  • GatewaySearchEmptyResultError & LinkupSearchEmptyResultError: raised when a search returns no object payload to fill the output structure (pointing to the query rather than the model).
  • LinkupError (base class) & LinkupSearchResponseError: raised when the Linkup API answers a structured search with an invalid payload shape.

Changed

  • Strict PipeFunc name collisions (Breaking): Registering two different functions under the same PipeFunc name now raises a FuncRegistryError (naming both origins) instead of silently overwriting the previous registration. Re-registering the exact same function object remains a safe no-op. Docs updated to explain the new flat namespace collision rules.
  • Stricter pipelex validate for structured search (Breaking): pipelex validate now rejects structured-search output classes that Pydantic cannot describe as a JSON Schema (e.g. arbitrary_types_allowed fields), failing early with an OutputStructureSchemaError instead of crashing mid-run inside the worker.
  • Dry-run object mocks built from real classes (Breaking): Dry-run leaf mocks for PipeLLM and PipeSearch structured outputs are now built directly from your output class rather than a schema rebuild. Constraints previously erased are now enforced at mock-build time, failing loudly with a DryRunMockBuildError if Polyfactory cannot satisfy them. Related "Under the Hood" docs for dry-run mock generation updated.
  • Consolidated leaf result conversion: Centralized the logic for converting a leaf result into the caller's class into a single shared helper (pipelex.cogt.content_generation.object_revalidation), ensuring consistent behavior across in-process and distributed-execution boundaries. Related "Under the Hood" docs for distributed content generation updated.

Fixed

  • Preserved class invariants in structured output & web search: In-process structured generation and structured web searches no longer lose your class's invariants (custom @field_validators, json_schema_extra hints, and docstrings). The caller's live class is now passed directly down to the leaf and used as-is, preventing weaker contracts from reaching the provider.
  • Shadowed field names in worker-boundary round trips: Output fields named json, copy, schema, or construct no longer break when crossing a worker boundary. The shared conversion now serializes with by_alias=True, ensuring keys match the published schema and preventing false Field required validation errors. Our distributed-execution plugin picks this fix up with its next release, once it adopts the shared helper.
  • Structured web search envelope unwrapping: Fixed an issue where Linkup and Gateway search backends returned a {data, sources} envelope that no output class could accept, making structured search unusable. The direct backend now requests the payload alone, and both backends recognize and unwrap the envelope if returned.
  • Accurate cost reporting for failed searches: A structured search whose response shape was rejected by the backend previously vanished from the run's cost report because it failed after the provider had answered. Completion and reporting now run in a finally block, ensuring billed usage is always recorded.
  • Malformed Gateway responses properly classified: A Gateway relay answering with non-JSON content previously raised a bare JSONDecodeError; it is now caught and surfaced as a classified GatewaySearchResponseError.

[v0.41.0] - 2026-07-30

Highlights

Importing the Pipelex runtime now loads zero interpreter modules, down from 50 — you can embed the inference engine without loading a line of the MTHDS interpreter. pipelex.hub split into pipelex.runtime_hub and pipelex.interpreter_hub, the boot sequence gained a RuntimeBoot layer seam, and the vendor adapters, the MTHDS parser and the Pipe machinery each moved to the package that owns them. The boundary is enforced in CI (make check-hub-layering) and pinned by a subprocess import-closure test. This is a large breaking cycle: many import paths moved, and PipelexInterpreterErrorMthdsParserError changes a wire-visible error_type. See Hub layering convention.

Added

  • Claude 5 Support: claude-5-sonnet is now served by the Pipelex gateway.
  • Error Identity Snapshotting: Added a generate-error-identity (gei) CLI command and Make target that commits a snapshot of every PipelexError subclass's (error_type, title, type_uri) triple, so renaming an error class — which changes the error_type string consumers branch on — surfaces as a reviewable diff instead of silently breaking them. Documented in Error Model.
  • Hub Layering CI Enforcement: Added make check-hub-layering (chl), now a required CI status check (lint-hub-layering), to enforce the one-way boundary between the runtime and interpreter layers. The rule is transitive — it reports any runtime-layer module that reaches the interpreter hub, naming the shortest import chain and the line of its first hop.
  • Concept Registry Boundary: A golden-set AST test over pipelex/core/concepts/** pins which modules may read the process-global class registry — only the materialization write side may.
  • Vendor-Neutrality Pinning: Added strict tests pinning pipelex.cogt's third-party import roots and its exact cogt → pipelex.providers edges, so a new inference-provider SDK import or a new inversion shows up as a diff. Both packages are runtime-layer, so no other gate can see these edges.
  • Registry Sort Enforcement: The keyword-only guard now enforces the sort order of the subject_grants.toml registry (unsorted-grant violation) to prevent silent breakage during bulk rewrites. See Keyword-only arguments.
  • Test Duration Gating: Added a test ensuring .test_durations contains no dead paths after bulk file moves, keeping CI shard balancing accurate.
  • Registration Surface Docs: New Registration surface page documents every place a new pipe kind has to be registered.

Changed

  • The Hub Split: pipelex.hub has been split into two distinct lifecycles to keep the runtime from loading the method interpreter: pipelex.runtime_hub (process-scoped infrastructure: config, console, secrets, storage, telemetry, model deck, inference workers) and pipelex.interpreter_hub (library-scoped method machinery: library manager, pipe router, pipeline manager). get_pipelex_hub splits into get_runtime_hub / get_interpreter_hub. There is no alias shim — a stale import fails loudly rather than silently resolving to the wrong layer. (Breaking)
  • Composition Root: Pipelex is now the interpreter-layer boot composed on top of the new RuntimeBoot layer seam (pipelex/runtime_boot.py), which stands up the whole runtime while loading zero interpreter modules. Inheritance preserves every attribute address, so no consumer changes; Pipelex.__init__ takes keyword-only config_dir in place of the inert positional config_dir_path, and the dead is_pipelex_service_enabled attribute is gone. (Breaking)
  • Plugin & Provider Separation: Built-in vendor adapters (OpenAI, Anthropic, etc.) moved from pipelex.plugins to pipelex.providers; interpreter-touching built-in plugins (direct, pipe_func) moved to pipelex.interpreter_plugins; pipelex.plugins now strictly houses the plugin mechanism (contracts, registrars). The plugin contract itself is untouched — an out-of-tree plugin still imports pipelex.plugins.contract / registrar and is still discovered through the pipelex.plugins entry-point group. build_registrar now requires builtin_plugins= and core_unconditional_plugin_names= instead of importing them, which is what keeps pipelex.plugins importable without the interpreter. (Breaking)
  • Core Restructuring & MTHDS Parser: The MTHDS parser moved out of core/ into pipelex.mthds_parsing, with PipelexInterpreter renamed to MthdsParser and PipelexInterpreterError renamed to MthdsParserError. The rename is wire-visible: the class name is the error_type clients branch on, and its type_uri moves to errors/mthds-parser-error/ (a docs redirect keeps the retired URI resolving; a consumer switching on the string does not get one). Core's Pipe machinery (abstracts, factories, blueprint, validation, rendering) moved to pipelex.pipe_machinery, and the pipe-kind registration manifest moved to pipelex.pipe_machinery.registry_models, leaving CoreRegistryModels with the value model only. (Breaking)
  • Concept Purity: Concept is now pure data. Class resolution has been removed from the wire model and delegated to ConceptProviderAbstract.get_structure_class(concept=...), and Concept.are_concept_compatible becomes the pure Concept.are_compatible_by_declaration, composed by ConceptLibrary.is_compatible. Three defects close with it: cross-package refines aliases now resolve at five call sites that compared Concept values directly, an unresolvable structure class now raises ConceptStructureClassNotFoundError instead of silently answering False, and native.Anything is answered as a declared property rather than raised. (Breaking)
  • Injected Providers in core/: Core's data model resolves concepts through an injected provider instead of reaching into the hub — StuffFactory, InputShaper, WorkingMemoryFactory, InputStuffSpecsFactory and StuffSpecFactory all take a required concept_provider. The new read-side contract ConceptProviderAbstract owns that surface, which ConceptLibraryAbstract extends, keeping library management in the interpreter layer. (Breaking)
  • Graph Rendering Split: graph_rendering's bundle-driven half is now pipelex.pipeline.bundle_graph_rendering (generate_graph_for_bundle / generate_view_for_bundle); GraphFormat and render_graph_from_spec stay put. The split is along "do I need a loaded method?" — producing a graph spec from a bundle requires dry-running it, rendering an existing spec does not. pipelex.graph, pipelex.tracing, pipelex.observer, pipelex.errors and pipelex.test_extras join the declared runtime layer. (Breaking)
  • Type Relocations: Leaf models moved to the packages that own them, preventing import leaks: SpecialPipelineId / JobMetadata / JobCategory / UnitJobId to pipelex.system.job_metadata, JobMetadataError to pipelex.system.exceptions, PipeRunMode to pipelex.system.pipe_run_mode, PipeRunParamKey to pipelex.system.pipe_run_param_key, PipeRunError to pipelex.core.pipes.exceptions, TraceContext to pipelex.system.trace_context, DataInclusionConfig to pipelex.system.data_inclusion_config, TextFormat / TemplatingStyle / TagStyle to pipelex.tools.templating.*, TemplateCategory to pipelex.tools.jinja2.template_category, and the resolved-field layer to pipelex.core.concepts.resolved_fields. The two pipe renderers regrouped into pipelex.pipe_machinery.rendering. The TOML config shape is unchanged throughout — only the Python homes moved. (Breaking)
  • LLM Prompts: LLMPromptBlueprint no longer has a templating_style field; the style is now passed as a run-scoped parameter to make_llm_prompt. TemplateBlueprint.templating_style stays and now correctly takes precedence over the run-derived style. (Breaking)
  • Image Generation: Geometry mappings moved inward from the vendor adapters to pipelex/cogt/img_gen/GoogleImgGenFactory / OpenAIImgGenFactory are now ImgGenGeminiMapping / ImgGenGptMapping, with wire-literal aliases following the model family rather than the vendor. They were never vendor adapters: they map a cogt-owned taxonomy onto provider wire values, and no family is served by a single adapter. (Breaking)

Fixed

  • Lint-Clean Codegen Artifacts: Generated Python and TypeScript artifacts are now lint-clean by construction, emitting modern typing (builtin generics, X | None), double-quoted Literal members, isort-grouped imports, idiomatic docstrings, pre-wrapped long lines, blank JSDoc lines carrying no trailing space, and a header-only file when there is nothing to emit. This prevents consumer formatters (Ruff/Prettier) from rewriting bytes and invalidating the [hand-edited] codegen stamp. Emitted bytes change for all three targets, so a committed projection reports drift until it is regenerated, and the lint exclusion on generated paths is no longer needed. Documented in Codegen Projections. (Breaking)
  • Prompt Rendering Determinism: Rendering an LLM prompt no longer mutates the library-held pipe object; a pipe's serialized form is now identical before and after runs, and in-process deck or config changes take effect immediately instead of being shadowed by the first run's cached value.
  • Sandbox PipeFunc Structures: A custom PipeFunc returning a non-native concept structure failed in the sandbox with an opaque "Function not found in registry" error — the structure class neither travelled nor was generated, so its import raised ModuleNotFoundError, which the registration path silently swallowed. structures.py is now regenerated from the crate before customer code is registered, and a failed import logs its real cause instead of being swallowed.
  • Boot & Teardown Resilience: A partway-failing teardown no longer permanently wedges the process — the whole un-poisoning set now runs in a finally, not just the singleton de-registration, since anything left behind alongside it turns a loud failure into a silent one. A failed boot no longer leaves the telemetry manager singleton behind or leaks a plugin runtime; plugin teardown is now best-effort per callback so one failing plugin cannot strand another's resources; an injected telemetry manager that raises no longer aborts the release suite; a second make() of an already-booted class is now explicitly refused; and the singleton lookup iterates a copy rather than a live process-global dict that grows during a run. teardown() no longer propagates a plugin callback's exception. (Breaking)
  • Layering Leaks: Built-in vendor adapters were pulling the entire interpreter into the runtime layer by importing MissingDependencyError from the all-errors aggregate pipelex.exceptions; they now import directly from pipelex.system.exceptions. Both gates were blind to it, which is what motivated the guard's new transitive rule.
  • Runtime Structure Generator Typing: The runtime StructureGenerator emitted Optional[X] / List / Dict where the codegen emitters emit modern typing, despite both rendering the same resolved-type tree. Both now agree, with unions folded inside quoted forward references so the annotation stays a single deferred expression.
  • Config Initialization: config_dir passed at boot is no longer silently ignored and now correctly reaches setup_config.
  • Documentation & CLI: Fixed pipelex build --help advertising a non-existent typescript format (it is a downstream use of --format schema); corrected hub.set_observer() documentation, which advertised an inert API, to reflect the Pipelex.make(observers=...) mechanism; rewrote the validation feature page, which promised an inter-step concept check that does not exist and cannot exist given working-memory resolution; and removed a duplicate set_library_manager call in Pipelex.setup.

[v0.40.0] - 2026-07-19

Added

  • Durable runs now persist token usage: the delivery executor writes a tokens_usages.json result artifact ({"tokens_usages": [...], "usage_assembly_error": null}) alongside working_memory.json, the main_stuff.* renders, and the graph outputs. The records use the client-facing TokensUsageRecord wire shape — the same shape the /execute response carries on pipe_output.tokens_usages — so a client polling a durable run's result files reads costs the same way a sync /execute caller does. The artifact is written unconditionally: a run with usage assembly off yields explicit nulls, distinguishable from a pre-artifact run (file absent).
  • TokensUsageRecord — a deliberate client wire shape for token usage (pipelex/reporting/usage_records.py). One flat record per inference call: model_type, inference_model_name, inference_model_id, pipe_code, job_category, unit_job_id, nb_tokens_by_category (raw provider counts), computed USD cost (null when the model has no rate table), and ISO started_at/completed_at. Enum-ish fields are open string sets on the wire, so runtime enum churn is non-breaking for clients. compute_tokens_usage_cost reuses the existing cost engine (model_cost_per_token + the cached/non-cached split), so the wire cost equals the CLI cost table's total for the same call. Documented in TokensUsage Wire Records.

Changed

  • Breaking: tokens_usages records on client surfaces are now TokensUsageRecord wire records, not dumps of the internal usage models. Both the /execute response (pipe_output.tokens_usages, via the new apply_tokens_usage_wire_shape helper API servers apply to the response dump) and the durable tokens_usages.json artifact stop leaking runtime internals: job_metadata (with its user_id, session_id, request_id, pipe_run_id, otel_context, trace_context, content_generation_job_id, pipeline_run_id) and the unit_costs rate table no longer cross the wire — the kept JobMetadata fields (pipe_code, job-kind enums, timing) are flattened onto the record and cost is computed server-side. Null semantics unchanged (null = assembly off, [] = no inference). Internal crossings (runtime-bridge payloads, Temporal transport, the usage-event telemetry stream) keep full-fidelity records, untouched.

Fixed

  • Validation errors stay domain-qualified: every raise site that names a pipe_code now also carries domain_code. The presentation chain (VS Code extension + mthds-ui) identifies pipes by full pipe ref (domain_code.pipe_code); a handful of PipeValidationError raise sites (PipeLLM static input checks, PipeStructure input-mismatch, the pipe sorter's circular-dependency error) omitted domain_code, degrading node decorations and click-to-navigate for those errors in multi-domain bundles. The sorter gains a domain_code parameter threaded from the bundle spec's domain.
  • Hosted PipeFunc dry runs now honor output multiplicity. In sandbox-hosted mode a PipeFunc with a multiplicity output (e.g. Foo[]) mocked a single scalar item because the function annotation is unavailable in-process; downstream pipes expecting a list (e.g. batch_over) then failed the dry-run//validate of a perfectly valid method. The mock now takes its shape from the declared output multiplicity.
  • get_optional_config() honors its non-raising contract before any hub exists. It used to raise RuntimeError: PipelexHub is not initialized when no hub had been created at all, which broke the documented safe pre-boot path — e.g. constructing a PipeFunc in an isolated unit test crashed instead of defaulting to the direct execution mode.
  • PipeFunc transport hardening: the wire contract now rejects a non-finite timeout_seconds (float("inf") used to disable the runaway-code guard entirely), and the transported executor removes its materialized source workdir on every path (success, timeout, failure) instead of leaking a temp directory per invocation, pruning the sys.path entries it inserted for that workdir in the same cleanup so dead entries can't accumulate (or be hijacked by a recreated path) in a reused process.
  • Error reference pages generated for the PipeFunc transport errors (PipeFuncTransportError, PipeFuncExecutionError, DuplicatePipeFuncExecutorError, UnknownPipeFuncExecutionModeError) — their type_uris previously dereferenced to 404s.

[v0.39.2] - 2026-07-17

Added

  • LibraryManagerAbstract.is_crate_loaded(*, library_id, fingerprint) — public query over the per-library crate-fingerprint bookkeeping that already backs load_from_crate's idempotency. A True answer means the library's ClassRegistry holds the crate's dynamic classes, so callers can hydrate within an existing scope instead of opening a fresh one — preserving dynamic-class identity with instances that scope already produced. This is the core half of the fix for the PipeParallel same-concept combine failing over Temporal with expected X, got X pydantic model-type errors.

Fixed

  • PipeCompose construct: whole-stuff copies into native fields now convert in every promised case. { from = "..." } referencing a whole native stuff used to hand the content wrapper (TextContent, ListContent, ...) to the composed field in several cases, failing the dry-run runnable gate on correct methods. Three gaps fixed: optional (non-required) fields never converted (the Optional[X] annotation defeated the type detection), list-of-text targets dumped each item as a {"text": ...} dict instead of extracting the string, and scalar wrappers other than Text were not handled at all. The conversion matrix now covers Text → text, Number → number, YesNo → boolean, and Date → date, for both required and optional target fields, scalar and list-item positions — including nullable list items (list[str | None]), which normalize like their non-nullable counterparts. Unconvertible list items now raise a clear StructuredContentComposerTypeError instead of surfacing as a cryptic pydantic error downstream. One fidelity guard: copying a Date that carries a time of day into a bare date field raises instead of silently dropping the time and its UTC offset (breaking: this case previously truncated silently).

[v0.39.1] - 2026-07-15

Changed

  • Bumped the pipelex-tools-py runtime dependency to >=0.1.3 and the pipelex-tools dev dependency to >=0.7.2, picking up the latest MTHDS schema updates for plxt formatting and linting.

[v0.39.0] - 2026-07-14

Highlights

  • From resolved library to typed clients: pipelex resolve and pipelex codegenpipelex resolve distills a bundle's whole closure into the normalized library crate — a flat, fully-qualified, fingerprinted snapshot — and pipelex codegen types projects it into TypeScript (ts-zod) or Python (python-pydantic, python-structures) artifacts. A trust chain of stamped headers and a codegen.lock makes drift detectable offline: pipelex codegen check verifies artifacts by pure hashing — no engine, no network, no API key.
  • Validation now fixes what it finds — When an error has a deterministic safe fix, every surface says so: a 💡 suggested-fix line in the CLI output, a structured suggested_fix object in agent and API payloads. The new pipelex fix bundle / pipelex-agent fix bundle commands apply those fixes in place through a shared fix loop, with --diff to preview the changes without writing anything.
  • Smart Inputs: the signature decides the shape — Inputs are now interpreted top-down against the pipe's declared input signature instead of bottom-up from their shape alone: a bare string becomes the declared concept (never silently degrading to generic Text), lists shape element-wise, bare URLs/paths resolve for Image/Document inputs, and generated input templates default to the light bare-value form.
  • New native concepts: Date, Time, and YesNo — Built-in concepts for calendar dates (preserving source precision — no fabricated times of day), times of day, and boolean judgments, plus a time structure-field type completing the temporal triple. Every native now materializes from its pinned normative definition, so crate fingerprints byte-agree across implementations.

Added

  • pipelex resolve and the pipelex codegen family: pipelex resolve assembles a bundle closure and emits the normalized library crate — a flat, fully-qualified, natives-expanded, self-contained, fingerprinted snapshot of a resolved library — as JSON or TOML. On top of the crate:
  • pipelex codegen types --target ts-zod|python-pydantic|python-structures projects the crate's concept set into typed artifacts. The ts-zod target emits a pure types.ts (imports only zod, with wire-native snake_case keys so a schema validates a wire payload directly) plus a binder.ts exposing a typed parse/serialize pair per concept; python-pydantic emits self-contained BaseModels; python-structures emits runtime StructuredContent classes.
  • pipelex codegen inputs --pipe <ref> renders a runnable inputs template for a pipe (defaulting to the closure's main_pipe).
  • Generated files are never edited — each carries an AUTOGENERATED header pointing at the sibling extension-file mechanism.

  • Codegen trust chain (stamps · codegen.lock · offline codegen check): Every file pipelex codegen types writes now carries a self-describing stamp header (source crate fingerprint, engine version, projection, options, and a content hash of the body below it), and the artifact set is recorded in a sibling codegen.lock. pipelex codegen check [DIR] verifies generated artifacts are current offline — pure hashing, no engine, network, or API key — reporting drift by category (missing, modified, hand-edited, orphan) with the verdict on the exit code (0 current · 1 drift · 2 no lock). Emission is idempotent (write-if-changed — unchanged output leaves files untouched) and prunes previously-generated files that drop out of the set, so a deleted concept never lingers as a stale generated file. Input templates (codegen inputs) are deliberately not stamped or locked — they are user-editable scaffolds, not tracked generated code.

  • Agent-CLI codegen mirror (pipelex-agent codegen types|check): The codegen family is now available to machine consumers through the agent CLI's two-stream envelopes (--format markdown|json success stream, --error-format error stream). codegen types runs the same engine and write-if-changed emission as the bare CLI; codegen check presents the offline drift verdict structurally — drift is a CodegenDriftError envelope enumerating drifts[] by category (exit 1), a missing or unreadable codegen.lock is a no-verdict error (exit 2). With the pipelex runner, mthds-agent codegen … forwards to these commands.
  • Host-facing resolve/codegen engine cores (what the HTTP routes ride): pipelex.pipeline.resolve_bundle.resolve_crate_from_contents resolves in-memory MTHDS contents into the normalized crate — same engine, same ValidateBundleError verdict vocabulary, and the same loaded-on-success library contract as validate_bundle — so a host (the pipelex-api /v1/resolve + /v1/codegen routes) serves resolution without touching the filesystem. pipelex.codegen.emission.build_stamped_projection is the new pure stamping core (stamped files + codegen.lock content, no disk writes) that write_stamped_projection now rides, so artifacts served over HTTP are byte-identical to locally written ones and pass the offline codegen check verbatim. The shared bundle-loading error cascade translate_to_validate_bundle_error is now public (renamed from its underscore-private form) since it gained a third entry point.
  • Agent bundle fixer: Added pipelex-agent fix bundle, a deterministic in-place fixer over the shared validation fix loop. The command supports --format/--error-format, repeatable --select/--ignore fix-rule filters, --allow-signatures, --max-iterations, and the same bundle-file or directory resolution as pipelex-agent validate bundle. Successful fix results report pending_signatures and is_runnable; by default the command exits non-zero when a fixed bundle remains valid-but-not-runnable.
  • Human bundle fixer: Added pipelex fix bundle, the human counterpart of the agent fixer over the same fix loop, with Rich-rendered output naming every change made (fix descriptions, files written, iteration count), the same --select/--ignore/--allow-signatures/--max-iterations options, and the same bundle-file or directory resolution as pipelex validate bundle. A new --diff flag previews the changes as a unified diff without writing anything — the real fix loop runs against a temp-copy sandbox, and exit codes keep the verdict semantics so --diff answers "would it converge?".
  • Suggested fixes surfaced in pipelex validate: When a validation error has a deterministic safe fix, the human error output now shows a per-error 💡 Suggested fix: line and ends with an actionable footer naming the exact pipelex fix bundle command (echoing the invocation's -L dirs) in place of the generic tip.
  • suggested_fix field in /validate payloads: Each validation error that has a deterministic safe fix now carries an additive structured suggested_fix object — fix_code (the kebab-case rule id), description (the author-facing 💡 text), safety (safe/unsafe), optional source file, and ops (the semantic patch operations) — on every validation_errors[] item, both in the pipelex-agent validate/fix JSON envelopes and in the API /validate response body. This is the machine-actionable counterpart of the 💡 line, letting a software consumer apply the fix directly from the structured payload.
  • Smart Inputs (signature-driven input shaping): Inputs are now interpreted top-down against the pipe's declared input signature rather than bottom-up from their shape alone.
  • Bare values (strings, numbers, booleans, dicts, lists) are automatically shaped into the declared concept (e.g., a bare string becomes a legal.Question, not a generic native.Text).
  • Lists shape element-wise into the declared item concept; single values auto-wrap into lists where required, and empty lists are now legal.
  • Bare URLs/paths for Image or Document inputs are resolved relative to the inputs file's directory.
  • Declared structured lists (e.g., Person[]) accept a direct path to a .csv file by signature, without requiring an envelope.

  • Comprehensive input error handling: A suite of typed errors for input shaping failures (WrongScalarKindError, ListWhereSingularError, MultiplicityCountMismatchError, StructureValidationError, UnknownInputNameError) that provide clear hints and render the expected JSON shape.

  • CLI --explicit flag: Added --explicit to pipelex build inputs and pipelex-agent inputs to generate the legacy {"concept", "content"} envelope templates.
  • Native YesNo concept: Built-in concept (backed by YesNoContent) for boolean judgments. Requires a strict boolean, renders as yes/no in downstream prompts, and can be read via pipe_output.main_stuff_as_yes_no.yes_no.
  • Native Date concept: Built-in concept (backed by DateContent) for calendar dates. Requires a datetime.date and accepts an optional datetime.time. Preserves source precision (never forcing the LLM to fabricate a time of day) and retains UTC offsets verbatim.
  • Native Time concept: Built-in concept (backed by TimeContent) for a time of day, optionally with a UTC offset — never attached to an invented date. A top-level TOML time-of-day literal in an inputs file now maps to Time (Breaking: Time joins the reserved native concept codes, and the former bare-time rejection — InputsTimeOnlyNotSupportedError — is gone).
  • Structure-field type = "time": The concept structure language gains a time field type (a time of day, optionally with a UTC offset), completing the temporal triple beside date and datetime. Generates a datetime.time field; emitted by every codegen target.
  • Pinned normative native definitions: Native concepts now materialize into the normalized crate by lookup into the standard's pinned per-version definitions (pipelex/core/concepts/native/pinned_blueprints.py, mirroring the mthds spec's new native-concepts.md), never by reflection over the runtime content classes — so crate fingerprints over materialized natives byte-agree across implementations. The old reflection is retained solely as a consistency test proving each runtime content class still matches its pinned blueprint.
  • Subject-grants registry — the keyword-only convention hardened: A positional first parameter ("subject") in pipelex/ source is now legal only under an explicit grant recorded in subject_grants.toml at the repo root, carrying the subject's param name and an honest, def-specific review rationale — every grant in the registry has been individually reviewed against the grant rubric. pipelex-dev subject-grant is the sole registry writer; the check-keyword-only AST guard enforces the registry symmetrically (an ungranted subject fails, and so does a stale grant whose def was renamed, moved, demoted, or newly carved out) in make check, CI, and the edit-time hook. Subjects typed bool/int/float are banned outright — no grant can cover them. The registry schema is strict: exactly param + rationale, unknown keys fail the check. See docs/contribute/keyword-only-arguments.md.
  • Drift contracts (pipelex-dev drift plan|check|ack): Deterministic review obligations between code and docs. A root drift.toml declares contracts ("when these trigger files change, these review targets must be re-examined and the examination recorded"); committed ack files under .drift/acks/ record each fulfilled review with a digest, reviewer, and rationale. A contract is fulfilled iff its stored ack digest equals the digest recomputed from the current tree (staged blob OIDs from the git index — no base ref, no timestamps), so trigger edits, matched-file adds/deletes, and contract-definition edits all reopen it.
  • make drift-check gates the make check aggregate and runs as an advisory CI job; make drift-plan prints per-contract Markdown packets with exact per-file changes since the last ack.
  • make drift-ack CONTRACT=… RATIONALE="…" runs the contract's verify commands, then records the review and stages the ack file it writes, so the local gate checks the same index the commit is built from. If staging that ack fails, the ack file is removed rather than left unstaged, so a later check reports a missing ack instead of a false green.
  • For a contract with verify commands, a trigger file left unstaged-modified or untracked is a hard ack error (the verify run would certify content the digest does not cover) — checked both before and after the verify commands, so a trigger the commands themselves format or generate is caught too. For other contracts it stays a warning.
  • Seeded with contracts for the config docs, the CLI docs, and the keyword-only convention spec; workflow documented in docs/contribute/drift-contracts.md.

Changed

  • Keyword-only demotions across the public API (Breaking): The subject-grant review demoted every positional first parameter that failed the grant rubric to keyword-only, and external code calling these surfaces positionally (or subclassing them with positional signatures) breaks. Notable demoted families: the PipeAbstract run family (run_pipe, dry_run_pipe, validate_before_run, … — job_metadata/visited_pipes now keyword-only), StuffContent.rendered_for_prompt/rendered_for_template_async (text_format) and the TextFormatRenderable/ImageRenderable protocols with all their implementations, the LibraryManagerAbstract load family (library_id), ensure_pipelex_booted (config_overrides), pipeline_run_setup (execution_config), ConceptLibrary.is_compatible (tested_concept), GraphTracerProtocol.setup (graph_id), the PromptImageFactory/PromptDocumentFactory content factories (uri), the model-manager/backend-library setup+load (secrets_provider), the reporting protocol's set_event_log/clear_event_log (context_key), and assorted tools helpers (pluralize/count_with_noun, LogLevel.from_int, set_dry_run_forced, …).
  • pipelex build structures is now an alias of codegen types --target python-structures (Breaking): The legacy always-qualified per-file generator is deleted. The alias resolves the target's whole directory closure and emits one stamped structures.py module plus a codegen.lock (bare-when-unique class names, declared imprecision instead of the old silent TextContent guess for structureless concepts). The --force flag is gone (emission is write-if-changed), a .mthds file target now resolves its parent directory's closure, and concepts-only loading of invalid-pipe bundles is no longer supported (the crate requires a valid library). The concepts-only loading API (load_concepts_only, load_concepts_only_from_directory, and the library-manager methods behind them) is removed with it.
  • pipelex build runner emits its structures through the codegen engine (Breaking): The scaffolded structures/ directory now contains the stamped single-module projection (structures.py + codegen.lock) instead of per-concept files, and the generated runner script imports and instantiates the emitted class spellings (from structures.structures import Invoice) — fixing dropped imports for nested custom concepts along the way. A user class backing a structure = "<ClassName>" concept is imported from its real module.
  • Human validation error rendering routed through the shared structured items: pipelex validate's error details now render from the same ValidationErrorItems the agent CLI and API emit, which surfaces information the old renderer silently dropped: pipe-factory errors (e.g. an unknown concept) get their own section, a parse-level failure (e.g. a TOML syntax error) now shows its message instead of no detail at all, and pipe validation errors name their source file. The dry-run message remains visible alongside categorized errors.
  • Validation error messages now speak MTHDS author syntax: Pipe validation errors state the problem and the fix the way an author writes them — a sequence output-multiplicity mismatch now reads "declares its output as 'StoryIdea', but its last step yields 'StoryIdea[]'. Update the sequence's output to 'StoryIdea[]'", and an input-type mismatch reads "input 'dish' is declared as 'Number' but its step needs 'Text'. Update the input to 'Text'" — instead of leaking Python internals (multiplicity=None, Concept(...)/PresenceMarker reprs). The misleading (required: […], provided: …) suffix is suppressed for multiplicity errors (where both sides are identical by definition) and rendered as joined author-syntax refs — never Python list-repr brackets — elsewhere.
  • Agent validate / fix output renders prose, not a JSON dump: pipelex-agent validate bundle and pipelex-agent fix bundle markdown output now renders validation errors as category-grouped prose (a humanized title, identity fields, the message, and a 💡 Suggested fix: line) followed by a fix-aware footer naming the exact pipelex-agent fix bundle command when a safe fix exists — mirroring the human panel. The machine-facing JSON stream (--format json) is unchanged, so software consumers keep branching on the same structured fields.
  • Clean validation summary message on every surface: The top-level error message (agent JSON, the API /validate body) is now a clean, author-facing summary derived from the structured errors, instead of the raw pydantic dump (Validation error(s): … Value errors: '<field>': Value error, …). The API's opt-in invalid-verdict rendered_markdown now carries the same per-error 💡 Suggested fix prose as the CLI. A still-invalid fix that stops with no progress now names the re-proposed fix in author terms rather than dumping an internal fingerprint string.
  • Input templates default to "light" shape (Breaking): pipelex build inputs and pipelex-agent inputs now generate light templates by default, outputting the bare values expected by the signature rather than the verbose {"concept", "content"} envelopes. TOML templates include the declared concept as a # concept: ... comment.
  • Structure-field type = "date" behavior (Breaking): A date field now generates a datetime.date (JSON schema format: date), preventing LLMs from hallucinating times for calendar dates. A new type = "datetime" field handles full timestamps (format: date-time), and default-value validation now rejects a datetime default on a date field.
  • Strict input validation (Breaking): Undeclared input names are now a hard error (previously silently ignored), and top-level null values are rejected — absence must be expressed by omitting the key. Known limitation: this also rejects the extra stuffs carried by a pipelex-agent run --with-memory envelope, so piping one run's full working memory into a downstream pipe that declares only a subset of it now fails with UnknownInputNameError. Until envelope-derived inputs get their own leniency, narrow the envelope to the declared inputs before piping, or pass the inputs explicitly with --inputs.
  • Explicit envelope validation (Breaking): The {"concept", "content"} escape hatch is now compatibility-checked against the pipe's declared signature; incompatible concepts raise a typed error.
  • TOML date/time handling (Breaking): TOML date and datetime literals are natively supported and map directly to the Date concept; a bare time-of-day literal maps to the new Time concept (it never silently becomes a Date — shaping a Time into a Date slot fails the compatibility check).
  • ImageContent flattens its pixel dimensions (Breaking): size: ImageSize | None is replaced by paired optional width / height integer fields (both-or-neither, enforced by a validator) — a named nested shape in the crate is a concept, and the size grouping did not earn that citizenship. ImageSize remains as the internal img-gen parameter type. Image wire payloads change shape accordingly.
  • native.Date declares real structure (Breaking): Materialized Date is no longer structureless — its pinned definition carries a required date field plus an optional time field (the new time field type), so generated clients get real Date types. DateContent's wire form is unchanged.

Fixed

  • Library loaders now bind the target library for the whole load: load_from_blueprints / load_from_crate / load_libraries took an explicit library_id but resolved concepts and the class registry through the ambient current-library ContextVar one hop below — so loading into a library that wasn't current read (and registered generated structure classes into) whatever library the ambient pointer happened to name. Production hosts always set the target current before loading, so this only bit multi-library callers — exactly the isolation the loaders exist to provide. The loaders (and blueprint removal) now wrap their body in scoped_current_library(library_id), restoring the caller's binding on exit, so everything beneath a load resolves against the load's target by construction. A regression test loads into a deliberately non-current library.
  • PipeExtract broke crate normalization (500s on every static-core route): PipeExtractBlueprint.validate_output only accepted the authoring spelling Page[], but normalization qualifies concept refs (Page[]native.Page[]) and then rebuilds the blueprints — so the validator rejected its own normalized output, and any bundle containing a PipeExtract raised a raw pydantic ValidationError out of resolve_crate_from_contents, 500-ing /v1/build/inputs, /v1/build/output, /v1/resolve, and /v1/codegen. The validator now compares the parsed concept ref and multiplicity instead of the raw string, so it holds for both spellings. The output contract is unchanged and still exact — a variable-length list of native Page — since a refined output would be an unverifiable promise: PipeExtract mechanically produces native pages and cannot make them more specific.
  • resolve / codegen rejected bundles that pin a model: The resolve and codegen commands booted without model specs, so library validation could not check pipe model pins (model = "gpt-4o-mini") against the deck and rejected any bundle carrying one. They now boot like validate — offline, but with model specs loaded.
  • Native Image no longer materializes structureless: Every native now materializes from its pinned normative definition, so generated clients for image-producing pipes expose url, public_url, caption, width, height, etc. (The brief interim dict-with-imprecision mapping for the nested size model is superseded by the ImageContent flatten above.)
  • Deterministic input-template placeholders: Mock URL placeholders in generated inputs templates (codegen inputs / build inputs) are now deterministic (https://mock.invalid/<field>), so committed templates no longer churn on every regeneration.
  • codegen inputs is write-if-changed: Like codegen types, an already-current inputs template is left untouched (no mtime churn) and the console reports Unchanged instead of claiming Generated.
  • Opaque generated classes no longer strip content: A structureless or Python-class-backed concept's generated Python class now carries model_config = ConfigDict(extra="allow"), so model_validate keeps the payload verbatim instead of pydantic's default extra="ignore" silently dropping every field. Opaque is pass-through, never lossy — matching the ts-zod z.unknown() behavior.
  • Human CLI ~ expansion: pipelex validate bundle (and the new pipelex fix bundle) now expand ~ in the bundle path and -L/--library-dir values, matching the agent CLI.
  • Validate telemetry label: The CLI_COMMAND telemetry tag for pipelex validate subcommands no longer double-suffixes (previously "validate bundle bundle").
  • Silent mistyping of inputs: Providing a bare string for a refined text concept (e.g., legal.Question) no longer silently degrades the type to a generic native.Text; the declared concept type is now retained.
  • TOML inline table formatting: Light templates now use inline tables for structured values, preventing trailing scalars from being swallowed into [sections].

Documentation

  • Smart Inputs guides: Updated provide-inputs.md, executing-pipelines.md, and native-concepts.md for the Smart Inputs paradigm, leading with the bare-value approach and demoting the envelope format to an escape hatch.

[v0.38.0] - 2026-07-06

Highlights

  • Optionality is a first-class language feature — Concept references in pipe inputs/outputs can be marked ? (optional) or ! (force), so absent values become tracked, first-class data with provenance instead of crashes or silent nulls. Absence flows through a runtime trichotomy — a plain input skips its pipe, a ? input absorbs it and runs, a ! input fails loudly — and a static taint pass proves at author time that every maybe-absent value reaches an explicit sink.
  • Storage and secrets are now plugin seams — Both backends are selected at boot from a config-keyed registry, so third parties can ship pipelex-storage-<backend> / pipelex-secrets-<backend> packages via the pipelex.plugins entry point — the same discovery machinery the inference and orchestrator seams already use.

Added

  • Optionals & presence markers (?, !): Concept references in pipe inputs/outputs can now be marked ? (optional) or ! (force), e.g. clause = "PenaltyClause?". Absent values are first-class data, tracked in working memory via an AbsenceRecord ledger (variable, producing pipe, kind = declared-absent / skipped / not-provided, reason, upstream chain) with provenance captured at the moment absence is produced:
  • A plain input fed an absence causes the pipe to be skipped (implicitly lifted); its own output is recorded absent, chaining back to the origin so the miss short-circuits like Swift's a?.b.c.
  • A ? input absorbs the absence and the pipe runs, handling both arms.
  • A ! input fed an absence fails loudly with a typed OptionalValueAbsentError naming the variable, the consuming pipe, the producing pipe, and the original reason.
  • Callers may omit ?-marked method inputs; the slot starts as a recorded not_provided absence rather than raising a missing-inputs error, and the missing-required-inputs message now names the optional inputs a caller may omit.
  • Post-run reads get a tri-state resolved accessor (WorkingMemory.resolve_stuff / resolve_main_stuff: a value or a recorded absence). A slot with neither a value nor a record is still a hard error — never-produced is a bug, not an absence. Markers round-trip through blueprints, builder specs, IO contracts, and bundle representations; grammar misuse (X[]?, ! on an output, markers on concept definitions) is rejected at parse with optional_marker_invalid.

  • Static optionality validation (the absence-taint pass): Validation proves the absence-safety theorem at author time — a taint pass over each controller's dataflow computes per-slot presence (guaranteed / maybe-absent) and rejects any maybe-absent slot that escapes without an explicit sink, with typed errors (optional_not_handled, optional_output_required, optional_branch_required_field) that each name the absence origin, the propagation path, and the fixes. The validation report also lists every liftable (skippable) pipe (liftable_pipes) — build-time visibility for "may be skipped when X is absent" — and gains a general warnings array for advisory lints that never flip is_valid (first occupant: optional_force_redundant, a ! whose slot is guaranteed present in every flow).

  • Template guard-lint (optional_input_unguarded): Every template reference to a declared-optional (?) input must be guarded — @?var, a {% if var %}…{% endif %} block, or an inline presence conditional — or validation fails with the precise fix. Applies to PipeLLM prompts and system prompts, PipeCompose templates, and PipeCondition expressions. In the same motion, @? finally means what it says: an optional variable is no longer presence-required by the controller miss-gates, so the pipe runs and its guarded templates take the absent arm.
  • Execution graph updates: GraphSpec nodes gain the skipped status with a skip_reason — "why did my workflow produce nothing?" is now answerable from the graph — and data edges fed by a declared-optional (?) output carry optional: true. Both flow through the in-process tracer and the distributed event-replay assembler (new pipe_end_skipped trace event).
  • Storage provider plugin seam: The storage backend (local / in_memory / s3 / gcp) is selected at boot from a StorageProviderRegistry keyed by the open storage_config.method token, populated by the always-on built-in StoragePlugin. Third parties can ship a pipelex-storage-<backend> package that advertises itself under the pipelex.plugins entry-point group — the same discovery/denylist machinery the inference and orchestrator seams use. storage_config.method is now an open str: an unknown method fails loudly at boot with UnknownStorageMethodError (listing the registered methods), not at config parse. See the new docs/under-the-hood/storage-provider-plugins.md.
  • Secrets provider plugin seam: The secrets backend is now a plugin seam selected via the new open secrets_config.method token (default "env") from a SecretsProviderRegistry, populated by the always-on built-in SecretsPlugin. Third parties can ship a pipelex-secrets-<backend> package (Vault, AWS Secrets Manager, …) under the pipelex.plugins entry-point group — the same mechanism the storage seam uses. Out-of-the-box behavior is unchanged: env stays the default. An unknown method fails loudly at boot with UnknownSecretsMethodError. See the new docs/under-the-hood/secrets-provider-plugins.md.
  • TOML pipeline inputs & templates: The --inputs file passed to pipelex run and pipelex-agent run (pipe/bundle/method) can now be TOML in addition to JSON, discriminated by file extension — TOML's multi-line strings make text-heavy inputs much easier to author. run bundle <dir> auto-detects inputs.toml alongside inputs.json when --inputs is omitted, erroring if both exist. Complementarily, pipelex build inputs and pipelex-agent inputs accept --format json|toml (default json) to emit the generated inputs template as TOML. Inline JSON ({…}) and agent-CLI stdin inputs stay JSON-only; a bare TOML datetime/date/time literal is rejected with an explicit "quote it as a string" error (native datetime concept support is a separate track).
  • Gateway models: Added support for nano-banana-2-lite through Pipelex Gateway.

Changed

  • Dropped Python 3.10 support (Breaking): requires-python is now >=3.11,<3.15. The 3.10 compatibility bridges are gone — StrEnum, Self, and Traversable are imported directly from the stdlib at every call site, the pipelex.types re-export module that existed only to paper over 3.10 is deleted, and the backports.strenum conditional dependency (and its type stub) is dropped. CI test/lint matrices, the package-check requires-python floor gate, and the contributor docs now start at 3.11.
  • Plugin API version bumped to 3 (Breaking): PLUGIN_API_VERSION is now 3 (was 2) to add the add_storage_provider and add_secrets_provider registrar menu methods. Plugin discovery version-checks with strict equality, so every external plugin must re-declare targets_api = 3. Our Mistral Workflows plugin registers no storage or secrets provider, so the targets_api bump is its only change. Our Temporal plugin needs the bump and a code migration: it imports the now-removed make_storage_provider_from_config (see Removed) and must switch to resolving the provider via the registry (get_storage_provider_registry().get_required(method=...)).
  • PipeSignature is not a pipe type (Breaking): A signature is now declared by omitting type — a [pipe.x] section with no type and nothing but the contract (description, output, optional inputs, optional signature_for) is a PipeSignature. Writing type = "PipeSignature" explicitly is rejected with a migration error ("PipeSignature is no longer a pipe type — delete the type line"), and a typeless section declaring any non-contract field is a hard error naming the field. The pipelex-agent pipe authoring command mirrors this. MTHDS JSON Schema shape change: the signature arm no longer carries a type property (an explicit tag now fails schema validation), so the downstream schema copies (mthds, vscode-pipelex, mthds-ui) must be re-synced on the next release via the mthds-schema-sync skill.
  • PipeCondition continue now resolves the output as absent (Breaking): The continue special outcome no longer passes the current main stuff through (and no longer errors when there is none). It records a declared-absent AbsenceRecord for the condition's declared output — with the evaluated expression as the reason — and returns success, memory otherwise unchanged. A continue-reachable PipeCondition must therefore declare its output optional (e.g. output = "Constraint?"), or validation rejects it (optional_output_required). Dry-run parity holds. Migration: the previous value stays in working memory under its own name, so downstream pipes consume it explicitly by that name (declared ? when it may be absent); a coalescing operator is the planned ergonomic replacement for pass-through.
  • Controllers combine under absence: PipeParallel no longer crashes when a branch result resolves absent (lifted branch or continue): with a Composite output the absent component is omitted (ledger note kept), and with a structured output a non-required field absorbs the absence as its default (None unless the author declared another), while a required field fed an absent branch raises a typed error naming the branch, the field, and the fixes. PipeBatch compacts: absent branch results are dropped from the aggregated list, so batching a "keep or skip" condition over items yields only the kept results.
  • Absent output delivery is now a first-class success: A run whose declared ? output resolves absent succeeds everywhere. main_stuff_name on PipelexPipeRunOutput and the /execute response names the declared output slot even when it resolved absent — consumers branch on the absence record in the serialized working memory (absences), never on transport, and the ledger round-trips cross-process (hydrate_working_memory reconstructs it). The main_stuff.json/md/html artifact files become an explicit absence document ({"absent": true} plus the provenance chain) on both the typed and raw delivery paths, pipelex run / --save-main-stuff and the agent CLI's run surface it instead of crashing, and OTel/Langfuse capture serializes it. A working memory with neither a value nor a recorded absence still fails delivery loudly.

Fixed

  • Template truth-tests on singular values no longer crash: {% if var %} (and @?var, which expands to it) on a present singular value used to raise TypeError: '…' content does not support len(). — Jinja2's truth test fell through to the artefact's list-only __len__. StuffArtefact now defines __bool__: a present non-list artefact is truthy, a list artefact follows list emptiness. This makes the optionals guard idiom safe on both arms.
  • PipeCompose escaped-sigil literals ($$, @@) no longer double-rewrite: PipeCompose alone stored its template already-preprocessed and then re-ran the sigil rewriter at guard-lint and render time. Because the escape collapse ($$name$name) is not idempotent, the second pass resurrected escaped literals into interpolations — raising a spurious optional_input_unguarded on a $$name that only looks like an optional reference, and silently rendering the value of name instead of the literal $name. PipeCompose now stores authored source and rewrites exactly once, like every other pipe.
  • --inputs ~/… now expands on run pipe / run bundle: a quoted or =-form tilde path (--inputs "~/inputs.json", which the shell leaves unexpanded) is now expanduser()-ed before loading, resolving to the home directory instead of failing on a literal ~ — matching the existing run method behavior, on both the human and agent CLIs.
  • Invalid storage/secrets method errors stay actionable under STRICT: UnknownStorageMethodError / UnknownSecretsMethodError are marked caller-facing, so their "registered methods: … — check storage_config.method" guidance survives STRICT error disclosure instead of being redacted to a generic internal-error message.
  • Non-string JSON pipe type gives an actionable error: a type that is a list/dict/number in a pipelex-agent pipe JSON spec now surfaces as an ArgumentError naming the valid pipe types, instead of a cryptic internal TypeError: unhashable type.
  • GitHub Actions: Removed an invalid environment block from the manual-trigger-tests-check.yml workflow.

Documentation

  • Optionality guide: New "Understanding Optionality" page (docs/building-methods/pipes/understanding-optionality.md) beside the multiplicity guide — presence markers, the runtime trichotomy (skip / run / fail), absence records and provenance, template guards, controllers under absence, and the static safety net. PipeBatch documents compaction under absence, PipeLLM documents the @? optional block sigil, and the run CLI page documents the absence artifact an absent main output produces.

Removed

  • make_storage_provider_from_config (Breaking): The module-level storage-provider factory (pipelex/tools/storage/storage_provider_factory.py) is removed — storage is now resolved through the config-selected StorageProviderRegistry at boot. Downstream code that imported this helper must select through the registry instead.

[v0.37.0] - 2026-07-04

Highlights

  • PipeParallel always combines — and every run has a main output — A parallel now always combines its branch outputs into its declared output concept (with the new native.Composite concept as the ready-made combination vehicle), the combined_output field is deleted from the MTHDS language, and the main-stuff invariant is enforced end to end: every completed pipe run delivers a main_stuff, so downstream surfaces (delivery, graph tracing, telemetry, wire models) can rely on it unconditionally.
  • Portable, statically validated image generation — A new portable size parameter ("1k"/"2k"/"4k" tiers or exact pixel dimensions) carries the same size intent across providers, Gemini models gain image-to-image editing and extreme banner aspect ratios, and Google image models are now validated against declarative geometry rules at blueprint-load time — unsatisfiable requests fail fast instead of at the provider call.

Added

  • Portable image size (size) for PipeImgGen: New size parameter accepting portable tiers ("1k", "2k", "4k") or exact pixel dimensions (e.g. "2048x1152"). A tier means "this pixel class at my chosen aspect_ratio", mapped to each provider's own grid; an exact size is deterministic (declaring aspect_ratio alongside it is a validation error). Unsatisfiable requests fail as hard validation errors at blueprint-load time, never warn-and-ignore. When size is unset, no size intent is sent and the provider default applies. The MTHDS JSON Schema exposes the field, and an optional size default is supported in [cogt.img_gen_config.img_gen_param_defaults].
  • native.Composite concept: New native concept backed by CompositeContent — an untyped, named composition holding sub-contents as top-level fields, serving as the default combination vehicle for parallel branches. Supports the full content surface: smart_dump, kajson/transport round-trip, and markdown/HTML rendering.
  • Gemini image-to-image (img2img) support: The Google img-gen worker now forwards input images to the Gemini API as inline parts, enabling image editing and multi-image composition for the nano-banana models (including the new nano-banana-2-lite). Every img-gen worker now validates input images against the model's declared capability at job start, rejecting img2img on unsupported models with a clean ImgGenParameterError before any provider call.
  • Banner aspect ratios: Support for extreme banner formats (landscape_4_1, landscape_8_1, portrait_1_4, portrait_1_8) from Gemini 3.1 image models. All other image-gen backends reject them with a clean parameter error.
  • Static validation for Google image models: Google image models now use rules blocks (geometry taxonomies like gemini_3_flash) in the backend deck, catching unsupported aspect-ratio and size combinations at blueprint-load time. The Google img-gen factory is keyed by taxonomy instead of hardcoded model names (Breaking: the GoogleImageGenModel name enum is removed — model handles are deck config, not code constants).

Changed

  • PipeParallel always combines (Breaking): A PipeParallel controller now always combines its branch outputs into its declared output concept and stamps it as the main output. The declared output is strictly validated at author time and must be Composite or a structured concept whose fields and types match the branch result names. Combination replaces the removed combined_output field. Migration: pipes declaring both fields just drop the combined_output line; add_each_output-only pipes replace their placeholder output with Composite or a matching structured concept. add_each_output keeps its meaning and now defaults to false.
  • Main-stuff invariant enforced (Breaking): Every pipe run now guarantees a main_stuff; defensive "maybe there is no main stuff" branches are removed, wire models make the field required (main_stuff_name: str on PipelexPipeRunOutput and the /execute response extension), and PipeOutput.optional_main_stuff is replaced by PipeOutput.main_stuff.
  • Orchestrator SPI delivery split (Breaking): OrchestratorProtocol.run is now strictly the blocking arm (returning a completed PipelexPipeRunOutput). A new OrchestratorProtocol.start handles fire-and-forget, returning a PipelexPipeDispatchAck (IDs only). PipelexPipeRunOutput.is_completed is deleted — it is always a completed output now.
  • LLMWorkerInternalAbstract folded (Breaking): Removed and folded into LLMWorkerAbstract, which now owns the whole job lifecycle (capability checks, constraints, telemetry). Subclasses extend LLMWorkerAbstract directly and implement _gen_text/_gen_object, nothing else.
  • Type preservation across transport: Composite components (and their nested lists) now retain strict types across transport boundaries (dump_for_transport / hydration) via private class markers.
  • PipeParallel honors final_stuff_code: A requested final stuff code (e.g. from a PipeBatch) is now correctly stamped on the parallel's combined output.
  • Docs — inference plugins: Rewrote using-inference-plugins.md to demonstrate a genuine pipelex.plugins entry-point package instead of a legacy config workaround.
  • Linting pipeline (repo-local): in the pipelex repo itself, plxt lint now validates .mthds files against the locally generated schema (derived/mthds_schema.json) rather than the released schema bundled with plxt. This is a .pipelex/plxt.toml override for this repo only — the plxt.toml template distributed by pipelex init config is unchanged.
  • Dry-run mocks: Reduced the default dry-run mock list generation from 3 items to 2 to cut dry-run processing time.
  • Bumped mthds dependency from >=0.6.0 to >=0.7.0.

Fixed

  • PipeCondition pass-through failure: A continue outcome with no pre-existing main stuff now fails loudly and actionably at the pipe level instead of crashing downstream surfaces.
  • Stale main stuff in parallels: A pipeline ending in an add_each_output-only PipeParallel no longer silently reports the previous step's output as its main result.
  • add_each_output optional in the builder spec: PipeParallelSpec now defaults add_each_output to false like the blueprint does, so a generated always-combine spec with just branches and output no longer fails validation before to_blueprint().
  • Google img-gen size handling: The native Google worker and gateway path now send the requested image_size to the Gemini API instead of hardcoding "1K", making 2K/4K generation reachable.
  • Img2img support checks: The capability check now keys off the model's declared inputs rather than requiring an input_images rule, which had falsely reported Gemini models as unsupported.
  • Routing profile optional routes: optional_routes declared in routing_profiles.toml are no longer silently dropped by the factory.
  • Unknown boot orchestrator fails loud: Requesting a boot orchestrator no installed plugin provides — via --orchestrator <name> or Pipelex.make(boot_orchestrator=...) — now raises UnknownBootOrchestratorError at boot instead of silently falling back to in-process execution.
  • Failed boot no longer leaks process-global state: A Pipelex.make that raises during setup now releases the process-global singletons a partial boot acquired (config, logging, the kajson class registry, template registries), so a failed boot no longer poisons a subsequent boot in the same process.
  • HuggingFace streaming errors: The provider error-body reader now safely tolerates unread httpx streaming responses without crashing with httpx.ResponseNotRead.

Removed

  • combined_output field (Breaking): Removed from the MTHDS language for PipeParallel; pipes now declare their combination target directly in the output field.
  • Legacy external plugin setter (Breaking): Removed set_llm_worker_from_external_plugin from the inference manager, fully replaced by the pipelex.plugins entry point.

Security

  • Transformers vulnerability: Bumped transformers past CVE-2026-4372 (GHSA-29pf-2h5f-8g72) to resolve a high-severity RCE. To unblock this, the huggingface extra now requires huggingface_hub>=1.5.0,<2.0.0 (Breaking).

[v0.36.0] - 2026-06-30

Highlights

  • Orchestration plugin SPI — Pipelex opens its execution path to pluggable orchestrators: the orchestrator that runs a job is now chosen per call by an open orchestration_mode token, with the core shipping an in-process direct orchestrator and the contract ready for plugins to supply distributed backends.
  • Orchestration plugin contract (orchestration_mode + DeliveryMode): The contract is built on two independent axes — an open orchestration_mode token (which orchestrator runs the job — direct in core, other modes supplied by plugins) and a closed DeliveryMode enum (the wait-semantics axis — BLOCKING / FIRE_AND_FORGET). An orchestrator plugin implements OrchestratorProtocol.run with a required delivery: DeliveryMode keyword and declares a supports_fire_and_forget: bool capability; the orchestrator registries are keyed by the orchestration_mode token. PipelexPipeRunInput carries orchestration_mode + delivery.
  • Orchestrator-dispatched /validate: A new per-call bundle-validator seam (BundleValidatorProtocol / BundleValidatorRegistry) makes /validate dispatch by orchestration_mode the way /start runs a pipe — direct validates in-process, distributed modes dispatch the job to a worker — with a byte-identical verdict across backends. The core direct plugin registers an in-process DirectBundleValidator.
  • Honest fire-and-forget delivery: A fire-and-forget /start request is honored only when the resolved orchestrator can do genuine async; an orchestrator that cannot (e.g. the in-process direct mode) returns a 4xx rather than running the job to completion and falsely acking.

Added

  • Explicit graph targets for validation: Graph generation during validation and dry runs now accepts an explicit pipe_code target, so developers can generate graphs for a specific pipe in a bundle even when no main_pipe is declared. This is wired through the CLI (validate bundle --pipe <target> --graph/--view), dry_run_pipeline, generate_graph_for_bundle, generate_view_for_bundle, and PipelexMTHDSProtocol.validate(extra={"graph_pipe_code": "..."}).
  • GraphSpec source path enrichment: GraphSpec artifacts now include the declaration source file path for pipes and concepts in pipe_registry and concept_registry, powered by LibraryCrate.source_map.
  • CI/environment heartbeats: Added WAIT_WITH_HEARTBEAT and RUN_WITH_HEARTBEAT Makefile macros that emit a periodic heartbeat (default every 20s) to prevent CI runners and sandboxes from timing out during long tasks; applied to the agent-test, pyright, mypy, and pylint targets.
  • Developer guidelines: Expanded python_standards.md with rules on filesystem paths (pathlib.Path over str/os.path), data-holder shapes (NamedTuple vs. pydantic.dataclasses vs. BaseModel), keyword-only arguments (enforced via make cko/make fko), and custom exception placement (exceptions.py or <topic>_exceptions.py).

Changed

  • dry_run_pipeline return shape: dry_run_pipeline now returns the fully resolved, domain-qualified pipe reference alongside the GraphSpec.
  • Graph viewer assets bumped: The CDN-pinned @pipelex/mthds-ui graph viewer used by the generated ReactFlow HTML is upgraded from 0.6.4 to 0.11.0, with refreshed Subresource Integrity hashes. 0.11.0 adds PipeStructure to the viewer's recognized pipe types, so graphs containing a PipeStructure node (e.g. from the preliminary_text structuring path) render instead of showing the GraphSpec validation error screen.

Fixed

  • Graph generation on bundles without a main_pipe: Requesting a graph or view via the CLI no longer fails with a missing main_pipe error when a valid pipe is targeted with --pipe.
  • Developer guidelines typo: Fixed a minor spacing typo in pytest_standards.md.

[v0.35.1] - 2026-06-22

Changed

  • PipeImgGen documentation: Rewrote the docs to clarify how inputs are consumed. PipeImgGen has no dedicated "prompt concept"; it uses a prompt string template into which declared inputs are injected at runtime — Text variables are interpolated directly, while Image variables (single or lists) are injected as reference images to enable image-to-image and editing workflows. Added examples demonstrating this vision pattern.

Fixed

  • Blueprint-stage validation error categorization: PipeValidationErrors raised during blueprint parsing (e.g. PipeBatch or SubPipe item name collisions) previously lost their error_type because Pydantic wrapped them in a generic value_error, degrading them to uncategorized residual errors. The categorizer now unwraps them, preserving their structured error_type, pipe_code, domain_code, and source locators.

Removed

  • Native concept ImgGenPrompt (Breaking): Removed the built-in native.ImgGenPrompt concept. It was structurally identical to Text (mapped to TextContent) and added no unique semantics; PipeImgGen never depended on it. Migration: replace ImgGenPrompt (or refines = "ImgGenPrompt") with Text in your .mthds files. The internal ImgGenPrompt runtime model, the TemplateCategory.IMG_GEN_PROMPT category, and ImgGenPromptError are unchanged.
  • Dead validation error type (Breaking): Removed the PipeValidationErrorType.img_gen_input_not_text_compatible enum value. It had no raise sites and contradicted the current design, where PipeImgGen accepts image inputs as a first-class feature.

[v0.35.0] - 2026-06-18

Added

  • Structured validation errors: Bundle validation failures now emit categorized validation_errors[] items with identity locators, so machine consumers can read error_type and locators instead of parsing text. Categories: unresolved concept reference (pipe_validation/unresolved_concept with pipe_code, concept_code, field_name; or blueprint_validation/unresolved_concept with owning concept_code), undefined pipe dependency (pipe_validation/unresolved_pipe_dependency with pipe_code and the new missing_pipe_code locator), and unknown pipe type (blueprint_validation/unknown_pipe_type with pipe_code).
  • New error locators and types: Added the missing_pipe_code locator field and the PipeValidationErrorType values unresolved_concept, unresolved_pipe_dependency, and unknown_pipe_type.
  • String utilities: Added pluralize and count_with_noun to pipelex.tools.misc.string_utils for correct CLI output grammar (e.g. "1 pipe" instead of "1 pipe(s)").
  • Documentation: Documented the 0/1/2 exit-code policy in docs/under-the-hood/error-model.md and added wip/structured-validation-errors-deferred-findings.md tracking deferred follow-ups.

Changed

  • Validate exit-code policy (Breaking): Both pipelex validate and pipelex-agent validate now use a three-tier exit code mirroring the hosted /validate API: 0 = valid (including valid-but-not-runnable with --allow-signatures), 1 = negative verdict (invalid, or valid-but-not-runnable without --allow-signatures), 2 = no verdict (bad arguments, unresolvable target, or setup/internal errors). Consumers testing zero vs. non-zero are unaffected; machine consumers should rely on the structured is_valid JSON field.
  • Relocated markdown renderer: Moved format_validate_markdown from CLI internals to the public pipelex.pipeline.validation_render module so other surfaces (e.g. the pipelex-api /validate route) can use it without Typer/CLI dependencies, and added render_invalid_validation_markdown for invalid verdicts.
  • Error class hierarchy: ConceptLibraryError now extends LibraryLoadingError (instead of LibraryError) so it can carry per-reference structured items through the existing error cascade.
  • Strict protocol arguments: PipelexMTHDSProtocol now rejects extra extension arguments, raising PipelineRequestError, since the local runtime defines no extension arguments.
  • Bumped mthds dependency from >=0.4.1 to >=0.5.0.

Fixed

  • Dropped pipe_code on blueprint errors: The blueprint validation-error categorizer matched the wrong Pydantic loc key (pipes instead of pipe), causing categorized items (e.g. missing_input_variable) to silently lose their pipe_code locator; it now populates correctly.
  • CLI output grammar: Validation CLI output now uses the new string utilities, fixing awkward pluralizations (e.g. "Validated 1 pipe(s)" is now "Validated 1 pipe").

[v0.34.0] - 2026-06-17

Highlights

  • Structured validation errors — validation failures now return a typed, per-error validation_errors[] on the error wire instead of a bare detail string, with the same shape across the HTTP API and the agent CLI.
  • Structured validation_errors on the error wire: ErrorReport gains a typed validation_errors: list[ValidationErrorItem] | None field. ValidateBundleError.to_error_report() flattens its per-error data onto it via a single shared builder (pipelex/pipeline/validation_errors.py), so the structured error report an HTTP API surfaces carries machine-mappable per-error diagnostics — category, message, identity fields, and a source (declaring file) for cross-file mapping — instead of only a detail string. The closed ValidationErrorCategory set is blueprint_validation / pipe_factory / pipe_validation / dry_run; a residual dry-run failure (DryRunError / PipeRunError, no structured locator) is projected as one dry_run item only when no categorized error has data, so an invalid verdict always carries a non-empty validation_errors[] (the structured-info invariant) rather than a bare message. The same builder feeds the agent CLI's validation_errors JSON array, so the CLI and API structured shapes cannot drift. The list is surfaced under STRICT disclosure (_STRICT_KEPT_FIELDS) because it describes the caller's own submitted bundle, not server internals.
  • Changed — agent CLI validation_errors shape: the validate commands' error envelope now omits null-valued keys per entry (exclude_none) and gains the previously-dropped source, field_name, and concept_code fields. Entries are guaranteed to carry category and message; all other keys are present only when populated, so consumers must treat them as optional rather than assume a fixed key set.
  • Per-content source on the in-memory validate path: PipelexInterpreter.make_pipelex_bundle_blueprint(mthds_source=...) and validate_bundle(mthds_sources=...) let a caller attach a logical source to each in-memory bundle string, threaded into blueprint.source. A sourceless mthds_contents submission previously produced source=None, breaking cross-file diagnostics; a host (e.g. an HTTP API) can now pass per-item sources so the structured validation_errors carry a real owning file. The on-disk CLI path is unchanged (it already records real file paths).
  • dry_run validation category: ValidationErrorCategory gains a dry_run value. A dry-run residual failure that previously produced a bare-message ValidateBundleError with an empty validation_errors[] now surfaces one structured dry_run item carrying the message (graph-level, so typically no source) — closing the structured-info gap that drove consumers to fabricate a category.

  • Keyword-only argument enforcement — a mechanical guard enforces the keyword-only argument convention across pipelex/, end to end — local autofix through the CI gate.

  • Keyword-only AST guard: A custom AST-based linter (pipelex-dev check-keyword-only / make cko) that mechanically enforces the keyword-only argument convention across pipelex/, wired into make check and make agent-check.
  • Keyword-only auto-fix: A non-gating --fix mode (pipelex-dev check-keyword-only --fix / make fix-keyword-only / make fko) that rewrites every mechanically-fixable violation by inserting a bare * as far left as possible (after self/cls), re-parsing each rewrite before writing and reporting the shapes it can't fix mechanically. It runs early in make agent-check; the read-only check still runs last and owns the pass/fail gate.
  • Claude Code hook: A PostToolUse bash hook (.claude/hooks/check-keyword-only.sh) that runs the guard on edited files for immediate blocking feedback to AI agents.
  • CI integration: A dedicated lint-keyword-only job in the GitHub Actions linting workflow to block non-compliant signatures from merging.
  • Convention documentation: New docs/contribute/keyword-only-arguments.md, plus updates to CLAUDE.md and AGENTS.md establishing the keyword-only convention as a standing rule.

Added

  • is_valid on the canonical validation report: PipelexValidationReport gains is_valid: Literal[True] = True, the always-true discriminant of the valid arm of the hosted /validate response union (mirrored by pipelex-api's InvalidReport's Literal[False]). It sits beside is_runnable: a sound bundle may still be not-yet-runnable.
  • Offline CLI unit tests: Coverage for the pipelex-internal logic behind the CLI commands — doctor diagnostics, run execution and its sync wrapper, the build codegen cores, the readiness gate, show/which, and the gateway/telemetry/signature error handlers (the spec'd CLI interface stays owned by our cross-repo spec suite).
  • Offline inference & runtime unit tests: Coverage for layers that can break without a provider — structured-output↔instructor mode mapping, model-deck reference checks, the image-gen argument and worker-routing factories, gateway request-shaping and extract parsing, the Mistral factory, the local observer sink, the output renderer, builder spec validation / to_blueprint(), the pipeline runner's error paths, the TOML config-sync engine, and the storage config validators.

Changed

  • [BREAKING] Keyword-only public API: Top-level public surfaces now require keyword arguments after the first parameter. A caller passing a second-or-later argument positionally must switch to keyword form — affected methods include Pipelex.make(integration_mode, *, ...), Pipelex.setup(integration_mode, *, ...), and PipelexHub.setup_config(config_cls, *, ...). Downstream consumers to check: pipelex-api, n8n-nodes-pipelex, cookbook, starter, and our hosted services.
  • Codebase-wide keyword-only refactor: The entire pipelex/ tree (leaf tools, domain core, inference layer, execution path, framework-sensitive packages) now places a bare * after the subject parameter, requiring all later arguments to be named at the call site (e.g. copy_file(src, target_path=dst, overwrite=False)).
  • Signature subject corrections: Reordered parameters so the true semantic subject is the first positional argument, e.g. parse_pipe_spec(spec_data, *, pipe_type), hydrate_content(raw_content, *, concept), and write_manifest(manifest, *, deck_dir). parse_pipe_spec is consumed by the hosted runner API (pipelex-api), updated in lockstep.
  • [BREAKING] Canonical protocol validation report: PipelexValidationReport is reworked into the canonical typed shape shared by every backend and moved to pipelex/pipeline/validation_report.py — a typed bundle_blueprint (replacing the untyped single-or-list blueprint dump), pipe_io_contracts keyed by namespaced pipe_ref (new pipelex/pipeline/pipe_io_contracts.py), validated_pipes, and the pending_signatures / is_runnable verdict. It is assembled in exactly one place (build_validation_report) with a single shared primary-blueprint rule (select_primary_blueprint).
  • [BREAKING] validated_pipes key pipe_codepipe_ref: the entry has always carried the namespaced pipe_ref (domain.code) under a key named for the wrong identity; the key now says what the value is.
  • Protocol validate now produces a best-effort graph_spec: when the batch declares a main_pipe, the local runtime dry-runs it in-process and ships the resulting GraphSpec via one shared implementation (best_effort_graph_spec); a graph-arm domain failure degrades to graph_spec=None with validation still successful. (dry_run_pipe_in_process moved to pipelex/pipe_run/dry_run_in_process.py to break an import cycle.)
  • [BREAKING] MTHDS_PROTOCOL_VERSION deleted: the hardcoded duplicate is gone from runner.py; the SDK's PROTOCOL_VERSION (mthds.protocol.protocol) is the single source of truth, imported directly by consumers including the hosted API.
  • Additive multi-file library construction: Same-domain .mthds libraries can now be built as separate, additive files (forward-declared PipeSignature headers plus concrete definitions in sibling files), enabling parallel top-down construction. A PipeSignature and a same-code concrete pipe reconcile (contracts compared by normalized concept identity) instead of colliding; concept and qualified pipe references resolve against the merged library across sibling and separately-loaded files; domain description / system_prompt merge order-independently; and a successful validate reports library-wide pending_signatures plus an is_runnable verdict in both JSON and markdown.
  • [BREAKING] PipeSignature evicted from the executable pipe taxonomy: a signature is a contract, not a way of running, so PipeType.PIPE_SIGNATURE and PipeCategory.PIPE_SIGNATURE are removed (any code matching on them must drop that arm). It stays a PipeAbstract subclass with pipe_category = None, and is_signature is now a class fact rather than an enum read.
  • [BREAKING] Signatures are never a validation error (signatures-as-data): the validator no longer raises when a pipe reaches an unimplemented PipeSignature. Strict and lenient modes return the same report body — the assembled library's outstanding signatures via pending_signatures and is_runnable = not pending_signatures. allow_signatures narrows to a sweep-mechanics flag (whether signature pipes are mock-run and listed in validated_pipes); it no longer changes the verdict. The "is this a failure?" decision moves to the consumer: the whole-bundle / whole-library surfaces (pipelex validate, pipelex validate --all, pipelex-agent validate bundle / validate method / validate pipe --all) derive the exit code from the library-wide runnability verdict — strict by default, exiting non-zero on not is_runnable unless --allow-signatures, while still emitting the success envelope (carrying pending_signatures + is_runnable: false). Single-pipe surfaces make no library-wide runnability claim and never gate: bare validate pipe <code>, and validate bundle / validate method invoked with --pipe (the slice can be fully implemented even when unrelated placeholders remain elsewhere). The execute/run path is unchanged — running a stub still raises PipeSignatureNotExecutableError.
  • [BREAKING] Host-wiring guards reclassified to PipelexUnexpectedError: validate_bundle / load_concepts_only's "provide exactly one of mthds_contents / mthds_file_path" guards (and the existing mthds_sources-length-mismatch guard) now raise PipelexUnexpectedError (→ 500), not ValidateBundleError — a caller wiring bug is a programmer error, not a content verdict to be reported as an invalid bundle. The empty-mthds_contents guard stays caller-facing.

Fixed

  • Protocol model deck no longer loses aliases to cross-category collisions: PipelexModelDeck.aliases / waterfalls are now keyed by model category ({category: {alias: model}}) instead of being flattened with update(), which silently kept only the last category's entry for an alias name shared across categories. Breaking for the deck's extension shape.
  • JSON-Schema rendering failures on the validate surfaces are now structured errors: a pydantic schema-generation failure while building pipe_io_contracts now raises the new PipeIOContractError (naming the offending pipe/input/concept) instead of a raw exception.
  • Protocol validate teardown can no longer mask the real error: a raising library teardown is suppressed while a body error is propagating, and the validation library id is captured once so the graph arm and the teardown always target the same library.
  • Empty mthds_contents is rejected with a structured error: validate_bundle and select_primary_blueprint now raise ValidateBundleError instead of crashing primary-blueprint selection with a raw IndexError; the hand-rolled first-declaring-main_pipe loops in execution_seams, dry_run_pipeline, and inputs_ops are folded into the single select_primary_blueprint.
  • Structured-info invariant is now total — parse-level failures carry a structured item: a malformed .mthds file (a TOML-syntax error, an empty blueprint, or a bundle-elaborator failure) is raised with only a message and no categorized data, so an invalid verdict previously rode an empty validation_errors[] for the single most common failure mode. The shared builder (build_validation_error_items) gains a last-resort fallback_message residual: when no categorized error and no dry_run residual produced an item, it emits one blueprint_validation item carrying the message (no source, no error_type — the bundle could not become a blueprint at all). Both surfaces — ValidateBundleError.to_error_report() and the agent CLI's extract_validation_errors() — pass it, so every invalid verdict now carries a non-empty validation_errors[], never a bare message.
  • Qualified same-domain pipe references resolve across files: a controller referencing a sibling-file pipe by qualified name (research.find_key_findings) is now deferred to the merged library like bare references; a reference no file declares is still rejected at load.
  • Concepts-only loading validates concept references: the pipelex structures / load_concepts_only path now runs the cross-file concept-reference check (batching sibling files into one pass) instead of silently accepting a structure field pointing at an undeclared concept.
  • Multi-file dependency packages reconcile signatures with their definitions: a dependency package split into a PipeSignature header plus its concrete sibling now goes through the same additive merge instead of colliding on the duplicate code and dropping one declaration by load order.
  • OpenAI image moderation mapping was inverted: is_moderated=true sent the less restrictive moderation="low" and false sent "auto" — the mapping now matches the flag (enabled → "auto", disabled → "low"). ImgGenSetting.is_moderated also defaults to None, so workers omit the parameter and the provider's own default applies (which also stops force-disabling FAL's enable_safety_checker).
  • Storage config validation now covers every provider and requires a real {hash} slot: the uri_format check is enforced uniformly across local / in-memory / S3 / GCP — every placeholder must be a plain supported {name}, a {hash} slot is required (GCP previously accepted the bare substring hash, silently overwriting every stored object), and buckets must set a positive signed_urls_lifespan_seconds. A misconfigured format now fails fast at Pipelex boot instead of at the first content store.
  • Mistral chat requests now send the system message before the user message: make_simple_messages previously appended it after the user message, contradicting its own docstring and the OpenAI-typed sibling.
  • String concept values in bundle specs are constructible again: a bare string in the ConceptSpec | str union now validates (no longer crashing the mode="before" validator) and passes through to the blueprint as the concept's description instead of into structure, where the loader rejected it.
  • LocalObserver JSONL records always carry the true lifecycle event name: a payload's own event_type key can no longer overwrite the event name in the written record, which had broken event-type filtering for JSONL consumers.
  • Tests under tests/**/build/are no longer silently skipped: pytest's defaultnorecursedirsincludesbuild, so the whole tests/unit/pipelex/cli/commands/build/tree was never collected; the config now overridesnorecursedirsand setstestpaths = ["tests"].
  • Anything-output option numbering is now deterministic: the output renderer sorts a PipeCondition's possible outputs by pipe code instead of iterating a set, so output_option_N / schema_option_N numbering is stable across runs.
  • Documentation examples: Updated code snippets in docs/under-the-hood/ to match the new keyword-only signatures.
  • Test mock assertions: Migrated mock assertions from .call_args.args to .call_args.kwargs to reflect keyword-only calls.

Removed

  • [BREAKING] SignaturesNotAllowedError deleted: with signatures no longer a validation error, the strict-mode signature pre-pass (BundleValidator._signature_pre_pass), the signature_check_error field on ValidateBundleError, the handle_signatures_not_allowed_error CLI renderer, and the SignaturesNotAllowedError exception class itself are removed. Code that caught SignaturesNotAllowedError on the validate path should read the report's pending_signatures / is_runnable instead; the execute path's PipeSignatureNotExecutableError is unaffected.
  • Keyword-only migration scaffolding: With the whole pipelex/ tree converted, the temporary baseline file and the pipelex-dev check-keyword-only --regen-baseline flag are removed; the guard now hard-blocks on any new violation rather than burning one down.

[v0.33.0] - 2026-06-11

Breaking Changes

  • PipelexRunnerPipelexMTHDSProtocol — the runner class now implements the MTHDS Protocol (mthds.protocol.protocol.MTHDSProtocol, mthds 0.4.1). Method renames: execute_pipelineexecute, start_pipelinestart (still NotImplementedError locally). execute/start carry the protocol's basic args plus a generic extra passthrough — server-specific args (a client-supplied run id, callbacks, a stored method_id) ride extra, never named params. Response classes: PipelexPipelineExecuteResponsePipelexRunResultExecute (subclasses RunResultExecutepipeline_run_id + pipe_output), PipelexPipelineStartResponsePipelexRunResultStart (subclasses RunResultStartpipeline_run_id only). state / created_at / finished_at / main_stuff_name / workflow_id are pipelex extension fields on the protocol's base responses; the run identifier keeps the name pipeline_run_id everywhere.
  • mthds pin → 0.4.1 — the restructured SDK (mthds.protocol + mthds.runners.api). The agent CLI's API run path uses MthdsAPIClient (mthds.runners.api.client) directly; domain shapes import from mthds.protocol.*, Dict wire models from mthds.runners.api.models.

Changed

  • --dry-run now mocks at the cogt leaf instead of swapping out the operators. run_mode rides a new CogtRunParams carrier (derived from PipeRunParams.run_mode, stamped on every cogt assignment), so a DRY run mocks inside the inference leaf at zero AI cost, with no API keys and no storage IO (the img/extract DRY branches sit above the store step). ContentGeneratorDry is deleted: operators no longer swap generators (the base operator's dry path simply reuses the live path), and the in-process validation scopes pass an inline ContentGenerator. Object mocks are now schema-built; classes with exotic format constraints should declare examples/mock_format (a re-validation failure surfaces as a typed DryRunObjectFidelityError, and a deterministic mock-build failure as the new DryRunMockBuildError).
  • Unified dry run — --mock-inference removed (breaking). There is now exactly one non-live run mode: --dry-run. The mock-inference mode is retired; its capability — non-zero synthetic usage so the cost report renders — survives as an internal is_mock_usage sub-flag of DRY on CogtRunParams (replacing is_mock_inference; setting it on a LIVE run is a validation error). It is exposed on the Python surface (PipelexMTHDSProtocol / execute / prepare_pipe_job via is_mock_usage=...) and as a hidden test-only CLI trigger, deliberately undocumented. Dry-run leaf coverage was already uniform across every operator, so MockInferenceUnsupportedError and its img-gen/extract/search guards are deleted; MockInferenceObjectFidelityError is renamed DryRunObjectFidelityError (mock-built objects are DRY-only now); the mock_inference usage sentinel is renamed mock_usage.
  • Keyless boot (needs_inference=False) now forces every run to DRY instead of installing a mock generator. Generator selection is purely backend-keyed; the forced-DRY flag is consumed at PipeRunParamsFactory.make_run_params — the single writer of run_mode — so every execution entry point is covered, including the runtime bridge.

Removed

  • dry_run_config.apply_to_jinja2_rendering config key: it was dead — PipeCompose renders templates directly and the jinja2 parse check survives in the templating leaf's DRY branch. Remove it from your .pipelex/pipelex.toml override if present (config is strict and will reject the unknown key).

Fixed

  • Resubmitting a pipeline_run_id after its run finished no longer fails permanently. PipelineManager was the one per-run registry with no per-run removal: every run permanently stranded its key, so a second submission of the same pipeline_run_id against a long-lived server process raised PipelineManagerAlreadyExistsError — surfacing as an unrecoverable 500 from the hosted runner API until process restart. The registry entry is now freed on every exit path: execute's finally removes it after each run (success or failure), and pipeline_run_setup removes its own registration when setup fails after registering (the caller never learns the id on that path, so setup must self-clean). Serial resubmission of a completed/failed run id now succeeds; only genuinely concurrent same-id runs still collide — deliberately, because the collision raise fires before open_tracer and shields the live direct-mode tracer (keyed by the caller-suppliable pipeline_run_id) from open_tracer's stale-key pop-and-replace healing. The hosted API maps the remaining concurrent-duplicate case to 409 Conflict (pipelex-api change).

  • dry_run_pipeline now owns its graph transport — graph generation no longer depends on the host's tracing_config. The function requests a graph explicitly (generate_graph=True) but relied on the host's configured tracing backend as the emit/assemble channel, so a host with tracing_config.is_enabled = false (e.g. pipelex-api's /validate in direct mode, where the web-app's Dry Run button lands) always got "Pipeline execution did not produce a graph spec" and a response without graph_spec. The run now traces through a scoped InMemoryEventLog — the graph comes back under any tracing config, and validation dry-runs stop writing NDJSON/DynamoDB trace events as a side effect when tracing IS enabled. The no-graph contract violation is now a typed DryRunGraphNotProducedError (was a bare PipelexError, ambiguous in host logs that record only the exception type) — raised by both dry_run_pipeline and dry_run_pipe_in_process.

  • tz-aware datetime payloads no longer fail to decode on hosts without a timezone database — kajson bumped to 0.7.0. kajson ≤ 0.6.0 decoded timezone-aware datetimes via ZoneInfo(...), which needs the IANA tz database: system tz files or the tzdata package. On hosts with neither (slim containers, uv-managed standalone Pythons — including the CI test runner, where tzdata only arrived transitively via pandas on Python < 3.11), decoding any aware-datetime payload raised ZoneInfoNotFoundError. Fixed upstream in kajson 0.7.0: its self-sufficient timezone wire format carries the UTC offset (decoding degrades gracefully to a fixed offset instead of raising, and plain UTC decodes with zero tz-database dependence), and kajson now declares tzdata as its own runtime dependency, so a tz database is always available for named zones.

Added

  • Protocol validate / models / version on PipelexMTHDSProtocolvalidate wraps validate_bundle (blueprints + per-pipe structures into the protocol ValidationReport); models wraps the builder's list_models into a ModelDeck; version reports protocol_version 0.6.0 with the installed pipelex version.

[v0.32.1] - 2026-06-09

Added

  • render_cost_report_for_output(pipe_output) submitter helper. A one-arg convenience over render_run_cost_report that unpacks pipeline_run_id and tokens_usages from a finished PipeOutput and derives the --costs gate from the output itself — pipe_output.tokens_usages is None is exactly the signal that cost reporting was off for the run, the decision the runner already resolved (with all --costs/--no-costs overrides applied) and recorded on the output. Embedders and the pipelex run CLI no longer re-derive the three primitive arguments by hand or re-read global config to reconstruct the gate. render_run_cost_report is unchanged as the low-level primitive for paths where the three values come from different places (the agent CLI's build_cost_summary JSON envelope, distributed reassembly).

Changed

  • typer is now capped (>=0.16,<0.27). pipelex subclasses typer's TyperGroup and overrides make_context, so it is coupled to typer/click internals and a future typer minor can break the CLI. The cap is tested through 0.26; a new dependency-canary CI job resolves the latest dependencies on a schedule so the bound is bumped deliberately rather than discovered in a user's install.

Fixed

  • CLI no longer crashes under newer typer/click. Every pipelex subcommand (build, run, validate, init, worker, …) raised RuntimeError: There is no active click context and exited 1 when run against typer >= 0.26 / click >= 8.4 — the versions a fresh pip install pipelex resolves. The root app_callback fetched the context via the global click.get_current_context() instead of the ctx Typer already injects; the global context stack isn't populated when a subcommand is dispatched under those versions. It now uses the injected ctx, which is version-robust. The sibling --traceback flag had the same flaw — is_traceback_requested() read it via click.get_current_context(), so under those versions it was silently ignored on handled errors — and is now recorded at parse time in a context-independent flag. A subprocess CLI smoke test (tests/unit/pipelex/cli/test_cli_entrypoint_smoke.py) guards every subcommand against regressing.

[v0.32.0] - 2026-06-09

This cycle reworks cost reporting so it stops leaking. Cost now rides on the run result instead of a side buffer: the success-path registry leak is gone by removal, and a new --costs switch decouples cost collection from --graph. It also extracts the framework-agnostic runtime bridge so any host runtime — not just Mistral Workflows — can embed Pipelex through one boundary.

Added

  • --costs / --no-costs (default on). pipelex run pipe|method|bundle gains a dedicated cost switch that emits usage tracing events and renders the end-of-run cost report. It rides the shared trace-event transport independently of --graph, so --no-graph --costs reports cost without building a graph and --graph --no-costs builds a graph with no cost report. It replaces the removed --cost-report (see Changed).

  • tokens_usages on PipeOutput. New tokens_usages: list[AnyTokensUsage] | None and usage_assembly_error: str | None fields, mirroring graph_spec / graph_assembly_error. Cost is now part of the run result and is exposed automatically wherever a PipeOutput is returned, including the Pipelex API response. Render it with CostRegistry.generate_report(tokens_usages=...).

  • --mock-inference. A LIVE run that fakes AI calls at the inference leaf with reportable synthetic usage, so cost reporting can be validated cheaply and deterministically without billing tokens. Mutually exclusive with --dry-run. It covers the LLM leaf; image-generation / extract / search under --mock-inference fail loud with MockInferenceUnsupportedError (pointing at --dry-run) rather than silently calling the real provider.

  • Cost report in the agent CLI JSON. pipelex-agent run ... --with-memory attaches a best-effort structured cost_report ({total_cost, by_model}, real USD) to its JSON envelope when the run did reportable work and summary aggregation succeeds — treat it as optional (absent for dry runs, --no-costs, the API-runner path, or an aggregation failure). The agent surface stays JSON-only — no Rich table on stderr; compact mode is unchanged.

Changed

  • Mistral Workflows integration extracted into a dedicated package. The optional pipelex[mistralai-workflows] extra and the pipelex.plugins.mistralai_workflows.* modules have been removed from pipelex. The Mistral Workflows integration now ships separately, as our Mistral Workflows plugin. The framework-agnostic runtime-bridge core (boundary types, run_pipe_via_bridge, PipelexExecutionMode, ensure_pipelex_booted) has been promoted from pipelex.plugins.mistralai_workflows.* to pipelex.runtime_bridge.* so any host runtime — not just Mistral Workflows — can embed Pipelex. No behavior changes; activities, boundary types, and execution modes are identical.

  • --cost-report removed, folded into --costs. Breaking: --cost-report/--no-cost-report is gone from run pipe|method|bundle. Use --costs (default on) instead.

  • Cost reporting is event-sourced; the submitter-side UsageRegistry is removed. The cost report is rendered from PipeOutput.tokens_usages, not from an in-process registry. ReportingProtocol (and ReportingNoOp) no longer expose open_registry / close_registry / generate_report / inject_tokens_usages, and the UsageRegistry model is gone. Embedders that rendered cost via get_report_delegate().generate_report() must render from the returned pipe_output.tokens_usages instead.

  • is_log_costs_to_console now defaults true. With --costs on, the CLI prints the cost table at end of run by default (parity with --graph producing visible output). Per-inference-job console logging is removed — the report renders once at the end, not per call. Library embedders who don't want console output set it false.

  • Dry runs never emit a cost report; free-model runs still do. Suppression keys on whether the run did reportable work (any tokens or any cost), not on total cost alone: a dry run (zero tokens, zero cost) is suppressed, while a real run on a free / zero-price model (e.g. Ollama) still reports its token usage with a zero total.

Fixed

  • Docs deploy crashed on a fresh runner with InferenceSetupRequiredError. pipelex-dev generate-error-pages (a prerequisite of every docs-* make target) bootstrapped Pipelex with needs_inference=True, so on a CI runner with the gateway enabled but no on-disk service config it hit the first-run inference-setup gate and exited non-zero — breaking the Deploy docs workflow on main. The command only introspects PipelexError subclasses to write markdown and never calls inference, so it now bootstraps with needs_inference=False.

  • UsageRegistry success-path leak. The per-run cost registry was opened during run setup but closed only on the failure path, so every successful run leaked its registry — and in a long-lived process (e.g. the Pipelex API) reusing a pipeline_run_id could collide on the orphaned registry. The registry is removed entirely, so the leak is structurally impossible.

[v0.31.0] - 2026-06-04

This release hardens Pipelex at its edges. The headliners: a full error-handling overhaul that gives every error a stable, RFC 7807-shaped identity and carries it all the way out to webhooks and external surfaces; lenient validation with pipe signatures, so you can sketch and dry-run a whole pipeline top-down before a single pipe is implemented; and a first, experimental cut of CSV tabular support for reading and writing typed lists straight from .csv.

Added

  • Stable error identity (RFC 7807). Every PipelexError now exposes title() and a stable type_uri() — a real, dereferenceable https://docs.pipelex.com/latest/errors/<error>/ URL — and every ErrorReport carries both as populated fields, so consumers read report.title / report.type_uri directly instead of humanizing class names. ErrorReport.to_problem_document() renders an application/problem+json document with no web-framework dependency, and a new DisclosureMode (VERBOSE / STRICT) controls how much leaks onto external surfaces: STRICT drops provider/model attribution and redacts any message that wasn't authored to be caller-facing, keeping only the stable identifiers.

  • Structured error payloads on failure webhooks. When a run fails, the delivery webhook now includes an error object — the full ErrorReport as a dict — so receivers can rehydrate it with ErrorReport.from_dict(...), render an RFC 7807 response, or route on error_domain / retryable. A WebhookTarget.payload that collides with a Pipelex-reserved key (pipeline_run_id, status, result_url, error) is now rejected at construction.

  • Per-class error reference pages. Every PipelexError subclass now has a generated reference page under docs/errors/, surfaced in the docs site as "Error Reference" — so a type_uri dereferences straight to a populated page. Regenerate with the new pipelex-dev generate-error-pages command (make generate-error-pages, alias make gep); pages a maintainer claims with a <!-- pipelex:authored --> marker are preserved across runs.

  • request_id on JobMetadata. An optional caller-supplied request id, threaded through the run and into the log context. Set it at dispatch with pipeline_run_setup(..., request_id="...") and read it back off job_metadata.request_id.

  • PipeSignature — contract-only pipes for top-down design. A new pipe type (type = "PipeSignature") declares a pipe's inputs, output, and description with no implementation, so an author or agent can sketch a complete pipeline before committing to the operator that will eventually do the work. At dry-run time a signature mints a mock output matching its declared type and multiplicity; at runtime it raises PipeSignatureNotExecutableError. The optional signature_for field hints which pipe type the stub stands in for. See Signature Pipes.

  • Lenient validation with --allow-signatures. pipelex validate pipe|bundle and every pipelex-agent validate subcommand take --allow-signatures (default off) to dry-run a pipeline that still contains PipeSignature stubs. Strict by default: without the flag, validation refuses any pipeline whose dependency graph reaches a signature and raises SignaturesNotAllowedError, reporting every reachable stub plus the controller chain that leads to it — so you know exactly which pipes are still placeholders.

  • CSV tabular support (experimental). Read a .csv straight into a typed ListContent[YourConcept]: point an inputs.json reference at a .csv whose column headers match the concept's field names, and each row becomes an instance, with cells coerced via Pydantic (birth_yearint, ISO dates → date; an empty cell becomes None, so the field must be optional). Write a flat-list output back out with the new --save-csv <path> flag on pipelex run pipe|bundle|method. v1 is deliberately narrow — local file paths only (remote URLs with a tabular suffix are rejected), the row concept must be flat (scalar fields only; a nested/list/concept-typed field is rejected with a clear error naming it), and .xlsx is recognized but routed to a "needs pipelex[tabular]" message (the Excel backend isn't built yet). See the CSV Input & Output guide.

  • --traceback CLI flag for full stack traces. By default CLI commands print a friendly one-line error; --traceback also prints the Rich-rendered stack trace before it. It's position-agnostic — pipelex run --traceback pipe ... and pipelex run pipe ... --traceback both work.

  • Test tooling for hanging runs. New make agent-test-debug (alias make atd) runs the suite with upfront stale-process cleanup, an outer wall-clock timeout, and live per-test logging — for when make agent-test hangs or fails opaquely. Paired with a debugging playbook at docs/agents/debugging-hanging-pytest-runs.md.

Changed

  • ErrorReport is now a frozen Pydantic BaseModel (was a frozen Pydantic dataclass) — still immutable and still round-trips through to_dict() / from_dict(), but an attempted mutation now raises pydantic.ValidationError instead of dataclasses.FrozenInstanceError.

  • Dry-run and validation consolidated into BundleValidator. The standalone dry_run_pipe / dry_run_pipes functions and the modules pipelex/pipe_run/dry_run.py, dry_run_with_graph.py, and dry_pipe_router.py are gone; their work now lives in BundleValidator, and validate_bundle / BundleValidator gained the allow_signatures: bool = False flag (strict by default). Validation ordering and outputs are unchanged — only the import surface moved. Callers importing from pipelex.pipe_run.dry_run must switch to BundleValidator.

  • validated_pipes now identifies every pipe by its qualified pipe_ref (domain.code). Previously validate pipe reported the bare code while validate bundle / validate all reported the namespaced ref, so the same pipe could appear under two identities depending on which command produced it. Every validate surface now emits the qualified ref, which can't collide across domains. Consumers that parsed pipe_code expecting the bare form (e.g. the MTHDS skills) must match on the namespaced ref.

  • Gemini deck refresh. Google shut down gemini-3-pro-preview, so the gemini-3.0-pro handle is removed across the google, portkey, and openrouter backends (and the test-profile collections) — use gemini-3.1-pro (→ gemini-3.1-pro-preview) instead, now registered on portkey too for parity with google. The gemini-3.0-flash-preview handle is renamed to gemini-3.0-flash. The Pipelex Gateway lists models from its own remote config, so drop gemini-3.0-pro there separately.

  • Filesystem path helpers moved to pathlib.Path. Helpers in pipelex.tools.misc.file_utils and pipelex.tools.misc.json_utils (save_text_to_path, load_json_from_path, load_binary, copy_file, ensure_directory_exists, get_incremental_file_path, and the rest) now take and return Path instead of str; path handling is Path-based throughout pipelex/, converting to/from str only at boundaries. Callers passing bare strings must wrap them with Path(...).

  • teardown_current_library() renamed to clear_current_library() in pipelex.hub, pairing it cleanly with set_current_library().

  • Dev tooling: pyright bumped 1.1.408 → 1.1.410 (drove two behavior-neutral internal adjustments).

Fixed

  • The generated MTHDS schema now requires type on every pipe. In the schema consumed by plxt lint and the VS Code Taplo LSP, each pipe blueprint variant declared type with a literal default — so it was optional, and because the Draft-4 export drops the union discriminator, a pipe table written without type matched several oneOf branches at once and was rejected with an ambiguous multi-match (worse, a type-less table carrying fields unique to one variant validated silently). type is now required on every variant, so a type-less pipe table fails with a clear "missing type" and a typed table resolves to exactly one variant. The patched set derives from PipeBlueprintUnion, so new pipe types are covered automatically. Regenerate with pipelex-dev generate-mthds-schema.

  • InputStuffSpecsFactoryError was shadowed by a duplicate class definition. input_stuff_specs_factory.py declared a local class with the same name as the canonical one in exceptions.py, leaving two distinct class objects in play so an except on one would miss the other. Consolidated to the single canonical class.

Security

  • urllib3 floor >=2.7.0 to patch two high-severity issues: CVE-2026-44431 (GHSA-qccp-gfcp-xxvc) forwards sensitive headers across origins on proxied low-level redirects, and CVE-2026-44432 (GHSA-mf9v-mfxr-j63j) bypasses the decompression-bomb safeguards in parts of the streaming API. Floor added to the runtime dependencies.
  • pymdown-extensions floor >=10.21.3 to patch CVE-2026-46338 (GHSA-62q4-447f-wv8h, medium): a regression in pymdownx.snippets reintroduced the sibling-prefix path-traversal bypass despite restrict_base_path. Docs-only; floor added to the docs extra.
  • idna floor >=3.15 (lockfile resolves to 3.18) to patch CVE-2026-45409 (GHSA-65pc-fj4g-8rjx, medium): specially crafted inputs to idna.encode() could bypass the CVE-2024-3651 fix. Floor added to the runtime dependencies.
  • Proactive floor bumps in the same hardening pass, past known-vulnerable releases: pillow >=12.1.1, protobuf >=6.33.5, python-dotenv >=1.2.2, requests >=2.33.0 (runtime); Pygments >=2.20.0 (docs extra).

[v0.30.3] - 2026-05-28

Added

  • claude-4.8-opus model added to the anthropic and bedrock backends. Adaptive thinking mode, supports text / images / pdf in and structured outputs, max_prompt_images = 100, max_tokens = 128000, costs { input = 5.0, output = 25.0 } per million tokens. Carries the temperature_unsupported constraint like claude-4.7-opus. Also available via the Pipelex Gateway.

[v0.30.2] - 2026-05-26

Changed

  • pipelex-agent markdown error envelope no longer includes the ## Error source stack-frame section. Markdown is the agent / human-facing channel; internal frames like LibraryError @ pipelex/libraries/library.py:140 are noise for an LLM trying to fix a .mthds file and forced every consumer (e.g. the mthds-plugins validate hook) to strip them. The error_source field remains in the JSON envelope (--error-format json) untouched for programmatic consumers — same shape, same cause-chain ordering — so any tooling that parses JSON keeps the full diagnostic surface. The ## Details section (which carries error_domain and other structured fields) is unchanged. No change to error categorization, error_domain values, message wording, or stdout/stderr routing.

[v0.30.1] - 2026-05-26

Fixed

  • pipelex-agent now silences every Python logger on stderr regardless of user TOML. The agent CLI is machine-consumed: stdout is reserved for the structured success envelope (JSON / markdown) and stderr for the structured error envelope. Free-floating log.* calls — a log.debug from telemetry_factory.py, a log.warning from validation_error_categorizer.py, or an INFO/WARNING line from any third-party dep (anthropic, httpx, botocore, openai, anything a transitive dep configures) — would corrupt the stderr channel for downstream parsers (mthds-js's PipelexRunner doing JSON.parse(stderr) on the validate hook). Two layers, defense-in-depth:
  • Layer 1 — pin pipelex's own logs off via config. make_pipelex_for_agent_cli injects config_overrides into Pipelex.make() that pin default_log_level = OFF and package_log_levels.pipelex = OFF from the very first log.configure call. A user setting [pipelex.log_config.package_log_levels] pipelex = "DEBUG" in ~/.pipelex/pipelex.toml can no longer leak its own DEBUG/INFO/WARNING lines.
  • Layer 2 — process-global cutoff that covers every logger. New silence_logging_for_agent_cli() calls logging.disable(sys.maxsize) — a process-global threshold checked inside Logger.isEnabledFor BEFORE any per-logger level. No record gets created for any logger at any level (including custom levels above CRITICAL), regardless of which package emits or what level the user configured. Wired into the Typer app_callback so every subcommand — including init and accept-gateway-terms, which bypass make_pipelex_for_agent_cli — silences logging before any command body runs; idempotent per-call invocations remain inside make_pipelex_for_agent_cli and agent_doctor_cmd as belt-and-braces for direct library callers.

A new e2e regression test pins the contract by setting anthropic, httpx, botocore, openai to DEBUG in the user TOML and asserting both stdout and stderr stay clean.

Changed

  • pipelex-agent no longer accepts --log-level. Log suppression is unconditional by design — there is no verbosity setting on the agent CLI. The log_level parameter is removed from make_pipelex_for_agent_cli() and apply_agent_cli_output_discipline(), and the corresponding ctx.obj["log_level"] plumbing is gone from every caller. For verbose debugging, use the human pipelex CLI, which honors the user's TOML log config.

  • pipelex/cli/commands/doctor_cmd.py::setup_doctor_runtime now deep_updates log_config_overrides into the loaded config (was a flat-merge that silently replaced nested dicts like package_log_levels). With the flat merge, pinning package_log_levels.pipelex = OFF for the agent doctor path would have wiped out all the third-party package levels (anthropic, asyncio, botocore, ...) shipped in the default config. The deep merge preserves them.

  • Removed ctx: typer.Context from 8 agent CLI commands that no longer used it. validate/{pipe,bundle,method}_cmd.py, inputs/{pipe,bundle,method}_cmd.py, models_cmd.py, check_model_cmd.py had ctx.obj["log_level"] as their only ctx usage; with --log-level gone, the parameter became dead weight. The run/ commands keep ctx because they still read ctx.obj["runner"]. Test callers (and the now-unused agent_ctx conftest fixture) updated to match.

[v0.30.0] - 2026-05-25

Fixed

  • console_log_target package default is now stderr (was stdout). Logs now stay off the data channel by default, matching the intent of PR #452 ("default to stderr for outputs happening before initialization"). Downstream tooling that parses pipelex / pipelex-agent stdout as JSON (e.g. mthds-js's PipelexRunner) is no longer at risk of stdout pollution from package-level logs — the bug was latent for stock installs because the agent-CLI JSON paths happen not to log at INFO+, but surfaced for anyone who raised package_log_levels.pipelex to DEBUG or added a setup-time log on the command path. The same flip is applied to the kit template (pipelex/kit/configs/pipelex.toml) that pipelex init copies to ~/.pipelex/. Note: console_print_target is intentionally left at stdout — the main pipelex CLI emits human-facing tables (show backends, show models, which, doctor) via that channel, and downstream piping (pipelex show backends > out.txt) must keep working.

  • pipelex-agent now pins both console targets to stderr regardless of user config. make_pipelex_for_agent_cli injects config_overrides into Pipelex.make() that force console_log_target = "stderr" and console_print_target = "stderr" from the very first log/print fired during init, so a user override of either knob in ~/.pipelex/pipelex.toml can no longer leak diagnostics onto the agent CLI's JSON data channel. Defense-in-depth post-init calls to log.redirect_to_stderr() and get_pipelex_hub().set_console_print_target(STDERR) remain. A new adversarial E2E test (tests/e2e/agent_cli/test_stdout_is_clean_json.py::test_models_json_stdout_resists_user_targets_override_to_stdout) pins the contract by overriding both targets to stdout and package_log_levels.pipelex to DEBUG and asserting json.loads(stdout) still parses.

[v0.29.1] - 2026-05-21

Fixed

  • pipelex run now prints the aggregated cost table when [pipelex.reporting_config].is_log_costs_to_console = true, with --cost-report/--no-cost-report to override per invocation. The cost-tracking.md and reporting-config.md docs both promised a summary cost table at the end of a CLI run, but pipelex/cli/commands/run/_run_core.py never called get_report_delegate().generate_report() — the flag only triggered log.verbose(...) lines per inference job, which the default INFO log level swallows, so the table never appeared. The CLI now calls generate_report() after a successful run when either is_log_costs_to_console or is_generate_cost_report_file_enabled is true, so the Rich table (one row per model, plus a totals row) prints right before the "✓ Pipeline execution completed successfully" recap — and the CSV export branch finally fires too. The new --cost-report/--no-cost-report tri-state flag (default unset → use config) lets you force the cost table on for a single invocation (--cost-report) or skip reporting entirely — no Rich table and no CSV file (--no-cost-report) — without touching .pipelex/pipelex.toml. Applies to pipelex run bundle, pipelex run pipe, and pipelex run method; works in dry-run mode as well (synthetic usage rows).

[v0.29.0] - 2026-05-20

Added

  • gemini-3.5-flash model added to the google backend. Adaptive thinking mode, supports text / images / pdf in and structured outputs, max_prompt_images = 3000, costs { input = 1.5, output = 9.0 } per million tokens.

  • New PipeStructure operator that turns text into a structured concept via a single LLM call. Takes one Text-compatible input (or a domain concept that refines = "Text"), produces any structured output with the usual multiplicity options (Foo, Foo[], Foo[N]). Useful whenever the source text comes from a PDF extraction, a search result, an upstream pipe, or any non-LLM origin. Documented at building-methods/pipes/pipe-operators/PipeStructure.md.

  • Documented render_js and include_raw_html on PipeExtract. Both fields were already shipping on PipeExtractBlueprint; only docs were missing. render_js = true asks the extraction backend to render JavaScript before fetching web-page content; include_raw_html = true populates each extracted Page's raw_html field with the fetched HTML. Added to building-methods/pipes/pipe-operators/PipeExtract.md.

  • Documented XHIGH value on ReasoningEffort. The enum already shipped seven levels; the under-the-hood/reasoning-controls.md table listed only six. XHIGH sits between HIGH and MAX and maps to provider-specific xhigh values where supported.

  • Bounded fan-out concurrency for PipeBatch. A PipeBatch over many items no longer spawns every branch — every coroutine, every deep-copied working memory, every inference call — at once. Branches now run in bounded chunks driven by the new [pipelex.pipeline_execution_config] setting max_concurrency (default 8; set to the literal "unbounded" for unbounded fan-out, the previous behavior). It keeps a large workload (one pipe over thousands of documents) from overwhelming asyncio, memory, and provider rate limits. Results still preserve input order, and a branch failure still propagates (first error by input index wins). PipeParallel, which fans over a fixed pipe-defined branch set, is unchanged.

  • Authoritative error_domain → HTTP-status mapping. pipelex/base_exceptions.py now owns the mapping that downstream HTTP APIs need to render an ErrorReport as an HTTP response: error_domain_to_http_status() (the pure domain table — INPUT → 422, CONFIG/RUNTIME/unknown → 500) and the ErrorReport.http_status property, which adds a provider-429 passthrough so the API can emit a Retry-After header from provider_metadata.retry_after_seconds. The library stays HTTP-agnostic — no web-framework dependency, just the mapping table; downstream FastAPI handlers call the helper instead of reinventing the contract.

  • AMBIGUOUS inference-error category for outcome-uncertain failures. New InferenceErrorCategory.AMBIGUOUS for failures where the error type is known but the outcome is not — the operation may or may not have committed (e.g. a connection dropped mid-request). It is non-retryable, like CONFIGURATION / CONTENT / CAPACITY, but semantically distinct from UNKNOWN, which means the error could not be classified at all. The azure_rest image-generation worker now raises AMBIGUOUS for mid-request transport failures — ReadError / WriteError / RemoteProtocolError and ReadTimeout / WriteTimeout — so an automatic-retry layer won't re-fire a non-idempotent, billable image submit whose outcome is unknown; pre-request failures (ConnectError / ConnectTimeout / PoolTimeout) stay TRANSIENT and retryable.

  • Explicit, uniform Tier 1 transport retry across every inference worker. A new [cogt] setting transport_max_retries (default 2) is now wired explicitly into every inference SDK client factory — Anthropic, OpenAI / Azure OpenAI, the Portkey-backed gateway clients, Mistral, and Google — instead of each factory silently inheriting whatever retry posture its SDK happens to default to. The two SDK families that default to no transport retry are brought up to the same floor: the Mistral client gets a bounded-backoff RetryConfig (retry_connection_errors=True) and the Google GenAI client gets HttpOptions(retry_options=...). The genuinely SDK-less path — the azure_rest image-generation worker, which talks to Azure over raw httpx — gets a tenacity-based transport-retry wrapper (new module pipelex/cogt/inference/transport_retry.py) that retries connection failures and transient HTTP statuses (408/409/429/5xx) and honors Retry-After. When no Retry-After header is present, the fallback backoff uses full jitter (wait_random_exponential) so a burst of failures retried together does not re-fire in lockstep. On a non-idempotent submit-style POST it narrows the retry to failures that prove the server did no billable work — a request that was never delivered, or a 408/429 rejection — withholding an ambiguous 5xx, a 409 conflict, and a post-delivery timeout such as a ReadTimeout. Transport retry is now a deliberate, configured, uniform policy rather than a per-provider accident. This is the transport-level floor of the retry model — pipeline execution makes a single attempt on top of it. (The portkey-ai SDK does not expose a retry knob — it carries its own internal retry — so the gateway's AsyncPortkey client is left as-is; only its underlying OpenAI clients are wired.)

  • Offline mode for Pipelex Gateway setup and dry-run. When the gateway is enabled but the remote config service is temporarily unreachable, Pipelex now falls back to a previously primed on-disk cache (~/.pipelex/cache/remote_config.json, schema-versioned) instead of failing setup outright. Dry-run, validation, and pipelex-agent run bundle --dry-run complete normally; only the actual inference call still needs the network at runtime. The cache is primed on every successful fetch and on pipelex init while online. When the gateway is disabled (BYOK), no remote fetch is attempted at all — setup is fully offline. A new RemoteConfigStaleWarning (UserWarning) is emitted whenever stale cache is in use; the agent CLI surfaces it on the JSON envelope as warnings: [{"type": "RemoteConfigStale", ...}]. Telemetry is suppressed (no-op) when running on a cached config so stale model identities don't pollute metrics. The doc/fixture generators (pipelex-dev update-gateway-models, preprocess_test_models_cmd) refuse the cache fallback via a new require_fresh=True flag, so committed reference docs and test fixtures never bake in stale data.

  • GatewayUnknownModelError (pipelex.cogt.exceptions). Raised at setup time when the active model deck references a gateway model handle that isn't present in the (fresh or cached) gateway specs. Carries the model name and the config source (RemoteConfigSource.FRESH | CACHED); the message branches on source so a cached-source failure suggests pipelex init while online and a fresh-source failure points at deck/typo fixes. Wired through both the Rich CLI (handle_gateway_unknown_model_error in error_handlers.py) and the agent CLI (AGENT_ERROR_HINTS / AGENT_ERROR_DOMAINS).

  • RemoteConfigUnavailableError (pipelex.system.pipelex_service.exceptions). User-facing offline-mode error: raised only when the network fetch fails AND no usable cached fallback exists. The message names the cache file path and the two remediation paths (run pipelex init while online to prime the cache; or disable pipelex_gateway in backends.toml for permanent BYOK operation). Distinct from the internal RemoteConfigFetchError, which is kept as the retry-layer exception.

  • PIPELEX_REMOTE_CONFIG_URL environment variable. Overrides the default remote-config URL. Useful for staging/testing environments; defaults to the production URL when unset.

Changed

  • Inference error handling refactored to Extract / Classify / Render (internal). Every inference worker's SDK-exception block now collapses to metadata = extract_*_metadata(exc); classification = classify_inference_error(metadata); raise render_*_error(...) from exc. New modules: pipelex/cogt/inference/error_classify.py (single shared classify_inference_error() returning ClassificationResult(category, user_action_kind, is_model_not_found)), pipelex/cogt/inference/error_render.py (single shared render_llm_error / render_img_gen_error / render_extract_error / render_search_error, picking the CogtError subclass from an InferenceErrorFamily tag plus the is_model_not_found flag), and pipelex/cogt/inference/provider_name.py (the ProviderName enum keying the extract-fn registry). ProviderErrorMetadata gains a message field plus is_quota_exhaustion / is_content_policy_violation / is_network_error @property accessors; the per-provider extract_*_metadata functions are now the only plugin-local piece — Classify and Render live once. Mistral and the gateway-search worker specialize HTTP 404 to ExtractModelNotFoundError / SearchModelNotFoundError; Azure img-gen keeps two worker-specific AMBIGUOUS branches for mid-request transport failures. Removed: the per-provider *_error_classification.py modules and their tests, AnthropicCredentialsError, GatewayFactory.classify_error_category / make_user_action_from_portkey_error / make_error_summary_from_portkey_error, and every inline _classify_*_error / _raise_categorized_* worker method. New unit-test tests/unit/pipelex/cogt/inference/test_provider_classification_parity.py walks every ProviderName against the extract-fn registry + worker-family map, so an unwired new provider fails fast. No user-facing API change — the structured ErrorReport contract is unchanged. Documented at under-the-hood/error-model.md.

  • instructor's structured-output retry no longer re-runs completions on transport errors. Passed a bare int, instructor's max_retries builds a retry loop whose predicate retries any exception — so the structured-output path (PipeLLM / PipeStructure) was re-running the whole completion on transport / API errors, a second retry loop nested on top of the SDK client's own transport retry. Each instructor call site now passes a tenacity.AsyncRetrying (built by the new pipelex/cogt/llm/instructor_retry.py helper) whose retry predicate matches only validation failures (pydantic.ValidationError, json.JSONDecodeError, and instructor's own validation-error types). A transport error now propagates immediately as the raw SDK exception for the worker's except clause to classify — transport retry is the SDK client floor (Tier 1) alone, and instructor's retry is confined to genuine schema re-ask. As part of this the Google and Mistral structured-generation workers gained an httpx.TransportError except clause: those SDKs let raw connection / timeout errors propagate outside their own exception hierarchies, so with instructor no longer wrapping them they must be caught and classified directly. The Mistral structured path also now gets schema re-ask at all — it previously passed no max_retries.

  • Inference schema-retry setting moved and renamed: [cogt.llm_config.llm_job_config] max_retries[cogt.llm_config] schema_reask_max_attempts (breaking). The setting is instructor's schema re-ask budget for structured-output validation failures. The old name max_retries gave no hint of that scope and collided conceptually with the new top-level cogt.transport_max_retries — which counts retries beyond the initial attempt, whereas this one is a total attempt count (stop_after_attempt). The new name makes both the scope (schema re-ask) and the unit (attempts, not retries) explicit. The single-key [cogt.llm_config.llm_job_config] sub-table is also dropped — the setting now sits directly under [cogt.llm_config]. Projects that override this key in their own pipelex.toml must move and rename it.

  • Agent CLI run / validate / init default to markdown output, with independent success/error format options (breaking). These commands previously always emitted JSON; they now accept --format markdown|json and default to markdown, matching models / check-model / doctor. A second flag --error-format markdown|json controls error reporting on stderr independently from success output — it defaults to the value of --format, so --format json still flips both, but the two can now be set separately (e.g. --format markdown --error-format json for human-readable success with machine-parseable errors). Internally, only the error format is carried in a ContextVar; the success format is threaded explicitly to agent_success_formatted(). The inputs / concept / pipe / fmt / lint / accept-gateway-terms commands are unaffected (always JSON / raw passthrough).

  • pipelex-agent validate bundle graph-format option renamed --format--graph-format (breaking). The --format/-f flag that selected the graph renderer (mermaidflow / reactflow / both) is now --graph-format/-f, freeing --format to be the uniform markdown/json output-format flag across every agent-CLI command.

  • structuring_method = "preliminary_text" on PipeLLM works again, via build-time elaboration. When PipelexInterpreter parses a .mthds file with a PipeLLM carrying structuring_method = "preliminary_text", the new BundleElaborator rewrites it before any pipe runs into a PipeSequence of two synthetic pipes: a PipeLLM producing Text (step 1, inheriting the original prompt + inputs + model) and a PipeStructure producing the original output (step 2, optionally using model_to_structure). The synthetic codes are tracked in a PipelexBundleBlueprint.elaboration_metadata side-table (excluded from serialization). Output multiplicity is preserved: step 1 always emits a single Text; step 2 emits Foo, Foo[], or Foo[N] according to the original output. Two LLM calls are issued per invocation. The user-facing pipe code is unchanged, so callers, main_pipe, and the run API keep working as before. Mechanism documented at under-the-hood/build-time-elaboration.md.

  • PipeLLM runtime no longer knows about structuring_method. The field is now a pure build-time directive. The runtime PipeLLM class no longer carries structuring_method, the validator that rejected mismatched output concepts has moved to PipeLLMBlueprint (where it fires at parse time), and the NotImplementedError previously raised at runtime when preliminary_text was selected is gone — the elaborator handles it. PipeLLMSpec exposes structuring_method so AI agents authoring via specs can still opt in.

  • StructuringMethod enum import path moved. The enum is now defined in pipelex.pipe_operators.llm.pipe_llm_blueprint (it lives next to the blueprint that consumes it) instead of pipelex.pipe_operators.llm.pipe_llm. Direct importers must update their from pipelex.pipe_operators.llm.pipe_llm import StructuringMethod line to from pipelex.pipe_operators.llm.pipe_llm_blueprint import StructuringMethod.

  • Retry removed from the gateway workers; [cogt.tenacity_config] removed (breaking). The tenacity-based retry inside GatewayExtractWorker and GatewaySearchWorker is gone, along with the [cogt.tenacity_config] config block and its TenacityConfig model. Because config models forbid unknown keys, an existing ~/.pipelex/pipelex.toml (or any layered override) that still carries [cogt.tenacity_config] will fail to load — remove that block from your config. Transient transport blips are left to the provider SDK clients' own retry; the tenacity library itself is still used elsewhere (FAL job polling, remote-config fetch) and remains a dependency.

  • PipeRouter.run() now reports CogtError failures to the observer. A CogtError raised out of pipe execution previously propagated past run() without an observe_after_failing_run notification (only PipeRunError was caught). It is now observed on the failing path, then re-raised as-is — the cause chain is preserved and it is not wrapped into PipeRouterError (only the PipeRunError path still wraps).

  • Provider HTTP 404s now raise dedicated *ModelNotFoundError across LLM, image-gen, extract, and search. A model-or-deployment-not-found 404 from any LLM or image-gen provider, or from the Pipelex Gateway extract / search workers, now raises a dedicated *ModelNotFoundError (LLMModelNotFoundError, ImgGenModelNotFoundError, ExtractModelNotFoundError, SearchModelNotFoundError — all ModelNotFoundError subclasses, CONFIGURATION category) instead of a generic LLMCompletionError / ImgGenGenerationError / ExtractJobFailureError / GatewaySearchResponseError. Because these are siblings of the generic errors — not subclasses — a 404 propagates past the operator's generic except to except ModelNotFoundError in PipeOperator._live_run_pipe, which re-raises PipeOperatorModelAvailabilityError carrying the unavailable model_handle.

  • RemoteConfigFetcher.fetch_remote_config() now returns a RemoteConfigResult carrying config, source (fresh | cached), and cached_at, instead of a bare RemoteConfig. Callers unwrap .config for the payload and may branch on .source to know whether the config is fresh or restored from cache. The fetcher accepts a new keyword-only require_fresh: bool = False — when True, a cached fallback raises RemoteConfigUnavailableError instead. RemoteConfigValidationError is never satisfied by the cache (server-side schema breaks must surface loudly).

  • ModelManager.setup() and BackendLibrary._load_gateway_model_specs() accept a new gateway_config_source: RemoteConfigSource | None parameter. Passed through from Pipelex.setup() so the deck-level gateway membership check can branch its error message on FRESH vs CACHED. GatewayConfig itself stays extra="forbid" and source-free — provenance is plumbed alongside, not baked in.

  • RemoteConfigUnavailableError message branches on whether the cache was refused vs absent. When require_fresh=True (dev-CLI generators) refuses to fall back to an existing cache, the message now reads "the local cache at <path> was refused because a fresh fetch is required" instead of the previously misleading "no local cache is available at <path>". The cache-truly-missing path keeps its original wording.

Fixed

  • Inference-failure ErrorReports now carry model and provider in production. A real LLM / image-gen / extract / search failure used to surface an ErrorReport with model = None and provider = None: the leaf errors (LLMCompletionError, ImgGenGenerationError, ExtractJobFailureError, SearchJobFailureError, ExtractOutputError) carry no model or provider of their own, and CogtError.to_error_report() only duck-typed whatever attributes happened to be set on the exception. Each inference worker family now fills model_handle / backend_name from the worker — where both are unambiguously known — at its public-method chokepoint (gen_text / gen_object, gen_image / gen_image_list, extract_pages, search_sourced_answer / search_structured), via the new CogtError.fill_model_and_provider(). The fill never overwrites a value an inner error already set and skips the "unknown" placeholder external plugins report. model_handle / backend_name are now declared on CogtError (so to_error_report() reads them directly instead of getattr), making them uniformly str | None across the exception hierarchy.

  • Two worker error-classification miscategorizations corrected. LinkupNoResultError (a search or fetch that returned nothing) had no explicit branch in either Linkup worker's _classify_linkup_error and fell through to the TRANSIENT catch-all — marking a query that cannot succeed by retrying as retryable. It is now classified CONTENT + CHANGE_INPUT in both the search and the extract worker. Behavior change: a no-result search is now non-retryable (CONTENT.is_retryable is False), so an automatic-retry layer no longer retries a query that returns nothing on every attempt. Separately, the FileNotFoundError branch in the Docling and pypdfium2 extract workers was classified CONFIGURATION — inconsistent with its sibling branches (CONTENT + CHANGE_INPUT) and with the rest of the codebase, where CONFIGURATION is reserved for setup problems. A missing input file is a content problem; it is now CONTENT. This second change does not alter retry behavior — CONFIGURATION and CONTENT are both non-retryable.

  • Wrapped exceptions now surface the underlying inference error's classification. PipelexError.to_error_report() enriches its report from the __cause__ chain, so a PipelineExecutionError — or any wrapper around a transient CogtError — now reports error_category, retryable, model, and provider instead of dropping them. Previously the agent-CLI JSON / markdown error output for a failed pipeline run lost the worker's category and retryability once the error had been wrapped by the pipe operator → router → runner layers, leaving an agent unable to tell a transient failure from a fatal one.

  • MTHDS JSON schema no longer leaks the elaboration_metadata side-table. PipelexBundleBlueprint.elaboration_metadata carried Field(exclude=True) so it stayed out of model_dump() / model_validate() round-trips, but Pydantic v2's exclude=True does not affect model_json_schema() — so derived/mthds_schema.json ended up declaring a top-level elaboration_metadata property plus $defs/ElaborationMetadata and $defs/StepRole entries, even though the side-table is process-local in the reference runtime. Wrapped the field type with SkipJsonSchema[...] so it is hidden from the schema. After regenerating, the three entries are gone from the public schema, matching the spec's silence on them.

  • A directory containing a .pipelex/ config dir is now recognized as a project root. .pipelex was added to PROJECT_ROOT_MARKERS, so project-level config (e.g. .pipelex/inference/backends.toml) is honored even when the directory has no .git, pyproject.toml, or other source-project marker. Previously such a directory fell through to the global ~/.pipelex/ config, silently ignoring the project's own overrides — so a backend disabled in the project's backends.toml could still demand credentials because the global config (where it was enabled) was loaded instead. The home directory remains excluded from project-root detection, so the global ~/.pipelex/ is unaffected.

  • PipeLLM outputting a concept that refines the native JSON concept no longer crashes with NameError: name 'Any' is not defined. On a LIVE run, such a concept resolves to a structured-output model carrying a dict[str, Any] field inherited from JSONContent. SchemaToModelFactory generates that model as source with from __future__ import annotations (every annotation becomes a string) and then rebuilds each class to resolve the string annotations. The rebuild namespace was assembled from the exec'd user types plus a hand-listed Literal, but typing.Any is a special form, not a type, so it was filtered out — model_rebuild then raised PydanticUndefinedAnnotation evaluating "dict[str, Any]". The rebuild namespace is now the exec namespace itself (minus __builtins__), so it carries exactly the names the generated source was written against and cannot drift as codegen emits other typing constructs. Fixed in pipelex/cogt/content_generation/schema_to_model_factory.py; covers both the sender path (make_from_json_schema) and the cross-process receiver path (make_types_from_source).

[v0.28.0] - 2026-05-13

Changed

  • Breaking (rendering): graph viewer assets are no longer bundled into generated HTML. The ReactFlow HTML output now references @pipelex/mthds-ui (the standalone IIFE bundle + its CSS) and elkjs from cdn.jsdelivr.net with pinned versions and sha384 Subresource Integrity hashes. Three external requests now load from jsDelivr at view time; the HTML itself is still generated offline by Pipelex — only viewing the rendered graph requires network access. The previous make sync-graph-ui / make check-graph-ui-sync workflow and the committed pipelex/graph/reactflow/assets/graph-viewer.{js,css} files have been removed; the manifest-style package.json and the Graph UI sync check GitHub Actions workflow are gone. A single Python module (pipelex/graph/reactflow/standalone_assets.py) is the source of truth for pinned versions and integrity hashes — the template reads them via Jinja2, so a bump touches one file plus the regenerated SRI constants. The new pipelex-dev refresh-graph-ui-sri command re-fetches the URLs, recomputes sha384, and rewrites the constants module; the update-graph-ui skill drives that flow. Previously, the "self-contained HTML" was already not offline (elkjs loaded from unpkg.com without integrity), and bumping the vendored bundle meant committing ~2 MB of regenerated JS+CSS on every mthds-ui release — SRI hashes give the same tamper-evidence with none of the diff churn.
  • Breaking: @var / @?var sigils must be alone on their own line, and inline @<ident> raises at load time only when <ident> collides with a declared input of the surrounding pipe. The @/@? rewriters wrap the value in a tag-shaped block envelope (<var>...</var> via the tag() filter), so the inline shapes never produced sensible output and are now rejected at load time with a new TemplateSigilSyntaxError — surfaced through pydantic validation with the line number, the offending span, and a migration hint. To stay friendly to coding agents authoring HTML/CSS templates (<style>@media (...) { ... }</style>, @font-face { ... }, Python/Java decorators like @deprecated / @Override), the validator is gated on the surrounding pipe's declared inputs: an inline @<ident> raises only when the candidate's root identifier matches a declared input (a real typo). Every inline candidate on a line is inspected, so a later collision can't hide behind an earlier CSS at-rule. Other inline @ candidates pass through silently. The line-bounded rewriter (alone-on-line @var{{ var|tag("var") }}, leading/trailing whitespace preserved) is unchanged and remains inputs-agnostic. The strict rule replaces the heuristic regex tightening from the prior CSS-collision fix: there are no residual rewrites for @font-face / @counter-style / @view-transition / @property --x, no special-case lookbehind/lookahead arms. The $var inline sigil is unchanged. API: preprocess_template(template, *, declared_inputs=...) gained a keyword-only declared_inputs: set[str] | None parameter (default None = lenient, no validation); a new pair of helpers validate_template_sigils(template, declared_inputs) and rewrite_template_sigils(template) exposes the validate and rewrite steps separately. render_template (runtime) calls rewrite_template_sigils directly — templates are already validated at load time, so the render path doesn't need the declared_inputs context. All pipe blueprints (PipeLLMBlueprint, PipeComposeBlueprint, PipeSearchBlueprint, PipeImgGenBlueprint, PipeComposeFactory) and template analyzers (TemplateDocumentAnalyzer, TemplateImageAnalyzer) thread their declared inputs into the validator; the base TemplateBlueprint and ConstructBlueprint-discovery validators remain lenient (no inputs context at that layer — the surrounding pipe's validator catches typos). Migration: for inline values, switch from @var to $var; for block-shaped content, move the sigil onto its own line; for literal @ or $ characters, use the @@ / $$ escapes documented below.

Added

  • @@ and $$ template escapes. Authors can opt out of sigil interpolation per occurrence: @@var renders as the literal string @var (no {{ ... }}), and $$var renders as the literal $var. The escapes are non-overlapping left-to-right (@@@@var@@var, two literal @s). Use these for any literal @/$ the author needs to keep in the rendered output — most commonly @@font-face, @@namespace, @@media inside CSS <style> blocks, or $$10 for a literal dollar amount. Cross-link: wip/template-preprocessor-line-bounded-at.md.
  • TemplateSigilSyntaxError (pipelex/cogt/templating/template_errors.py): raised by the preprocessor when a candidate @/@? sigil is not alone on its line. Pipe blueprints (PipeLLMBlueprint, PipeComposeBlueprint, PipeImgGenBlueprint, PipeSearchBlueprint, TemplateBlueprint, ConstructBlueprint, pipe_compose_factory) and the TemplateBlueprint validator catch and re-raise as pydantic ValueError with pipe-specific context, so the diagnostic surfaces through normal MTHDS validation errors and IDE/plxt check diagnostics.

Fixed

  • Template preprocessor: $<var> no longer substitutes when $ is adjacent to a word character on the left (e.g. micro$oft, user$host.com, P@ssw$rd123, a$b$c now pass through unchanged instead of producing mid-word {{ ... }} substitutions), and no longer emits invalid Jinja for shapes like $name.. (now renders {{ name|format() }}.. with both trailing dots preserved as literal punctuation). The $ arm now mirrors the @ candidate pattern's word-boundary lookbehind ((?<!\w)) and uses a strict segmented identifier [a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)* that rules out a leading digit and consecutive dots structurally. The trailing-dot kludge in the $ replacement function is gone (unreachable under the strict identifier shape). $var keeps its silent pass-through posture for non-candidate shapes — it does not raise like the @ arm does.
  • Project .pipelex/telemetry.toml created by pipelex init no longer silently disables globally-enabled telemetry. The kit previously shipped one telemetry.toml template carrying explicit enabled = false / mode = "off" defaults, and pipelex init telemetry copied it into both ~/.pipelex/ and the project's .pipelex/. Because the layered loader appends the project base file after ~/.pipelex/telemetry_override.toml, a user who enabled Langfuse/PostHog globally in their override still had it turned off in every initialized project. The kit now ships a separate telemetry.project.toml template with every setting commented out, used only by project-level init. The global init still copies the active telemetry.toml. As a safety net, telemetry_allowed_modes defaults (ci=False, cli=True, docker=True, fastapi=True, mcp=True, n8n=True, pytest=False, python=False) now live in the TelemetryConfig Pydantic model rather than the TOML template, so a project file that omits the section can't disable custom telemetry for everyone.
  • Global pipelex.toml and telemetry.toml overrides no longer silently disappear when a project .pipelex/ exists. ConfigLoader.load_config() previously sourced all override files (pipelex_local.toml, pipelex_{env}.toml, pipelex_{run_mode}.toml, pipelex_override.toml, pipelex_temporary_override.toml) from a single "effective config dir" — project .pipelex/ if present, else ~/.pipelex/. As a result, the user's machine-wide overrides in ~/.pipelex/ were ignored entirely the moment a project shipped its own .pipelex/. load_telemetry_config had the same shadowing behavior for telemetry.toml and telemetry_override.toml, which was particularly painful because telemetry holds Langfuse/PostHog/OTLP secrets that users typically set once globally. Both loaders now layer global → project: package defaults → global base → global overrides → project base → project overrides, deep-merged so machine-wide personal preferences keep applying unless explicitly overridden by the project. The unit-testing runmode behavior is preserved: ./tests/pipelex*{run_mode}.tomlis still the only run_mode file loaded under unit testing, replacing both layers to keep test runs hermetic. Migration: if a project previously relied on its.pipelex/to fully replace* the user's global config rather than merging over it, the project must now redeclare any keys it wants to set explicitly — silent inheritance from~/.pipelex/ is the new default.
  • pipelex build structures now emits domain-qualified class names and cross-references. ConceptFactory registers each concept's structure class under make_qualified_structure_class_name(domain, concept_code) (e.g. expense_validator__SpendingLimitCheck), but the CLI generator was passing the bare concept_code to StructureGenerator. Generated files therefore contained class ConceptCode(StructuredContent) and unqualified cross-imports/types, while the registry expected the qualified name. PipeFunc validation against concept.structure_class_name then rejected the mismatch with output concept expects structure class 'domain__X', but the function return type is 'X', breaking library load for any project that had regenerated structures. Fixed in pipelex/cli/commands/build/structures_cmd.py: the class-definition call sites now call make_qualified_structure_class_name(blueprint.domain, concept_code); _build_concept_ref_to_class_info qualifies cross-reference class names while keeping the file stem unqualified so output filenames stay domain__concept_code.py; the refines: branch resolves the refined concept's domain via QualifiedRef.parse_stripping_cross_package and qualifies the base-class name for non-native refines, mirroring ConceptFactory._handle_refines.
  • pipelex build structures cross-package refines now emit the base-class import. A concept that refines a class defined in another already-installed package (e.g. RefiningConcept with refines: some_alias->other_domain.OtherConcept) was generating a file that inherited from other_domain__OtherConcept without importing it. StructureGenerator._validate_execution injects the base class into exec_globals from the class registry, so codegen-time validation passed; the failure surfaced only when the generated structures module was later loaded via the normal Python import system, raising NameError: name 'other_domain__OtherConcept' is not defined. The generator now falls back to the class registry's __module__ when concept_ref_to_class_info has no entry for the base class and emits the corresponding import (guarded on cls.__name__ == base_class so we never emit a name the target module won't expose). Regression test in tests/unit/pipelex/cli/commands/build/test_structures_cmd_cross_package_refines.py was tightened to assert the import statement is present.

[v0.27.0] - 2026-05-07

Changed

  • RunEnvironment env var renamed from ENVIRONMENT to PIPELEX_ENV. The variable that selects the environment-specific pipelex_<env>.toml overlay (and is stamped on OTel spans as deployment.environment) is now namespaced to avoid collisions with unrelated ENVIRONMENT variables already set in deployment environments. Update any shell, CI, or container config that exported ENVIRONMENT=... to export PIPELEX_ENV=... instead.

[v0.26.4] - 2026-05-06

Fixed

  • clean_json_content() no longer crashes on nested Pydantic instances in working_memory_raw. When working_memory_raw (typed dict[str, Any]) held rehydrated BaseModel instances (e.g. PageContent from Page[] outputs), clean_json_content() walked only dicts/lists/scalars and let BaseModel instances reach json.dumps, which raised TypeError: Object of type PageContent is not JSON serializable. clean_json_content now reduces any BaseModel it encounters via model_dump(serialize_as_any=True) (the canonical smart_dump path) before continuing the recursive walk.

[v0.26.3] - 2026-05-06

Fixed

  • OpenAI SDK 2.34.0 compatibility for Portkey/Gateway clients. The new SDK adds both a constructor-time check (_enforce_credentials) and a request-time _validate_headers guard that reject an empty api_key. Pipelex's gateway and Portkey factories were passing api_key="" because auth is delivered via x-portkey-api-key and x-portkey-config headers, not the OpenAI Authorization header. Replaced the empty string with a non-empty placeholder ("unused-auth-via-portkey-headers") in gateway_completions_factory, gateway_responses_factory, portkey_completions_factory, and portkey_responses_factory. Same fix applied to openai_client_factory for the no-auth case (e.g. local Ollama backends), where backend.api_key is intentionally unset. Bumps the openai requirement to >=2.0.0.

[v0.26.2] - 2026-05-06

Fixed

  • choices fields no longer fail validation with 'EnumName.MEMBER_NAME' errors. A concept declared with choices = [...] produces a Literal[...] field on the dynamic Pydantic class. That schema is round-tripped through SchemaToModelFactory.make_from_json_schema (used to feed structured-output schemas to LLM providers). Previously the round-trip silently re-emitted the field as a plain Python Enum class — e.g. Literal["Strong Match", "Good Match", "Partial Match", "Poor Match"] became class Recommendation(Enum): Poor_Match = "Poor Match"; .... LLMs filling that schema then returned the enum's Python repr ("Recommendation.Poor_Match") instead of the literal string ("Poor Match"), which failed Pydantic validation against the original choice set with errors like Invalid choice errors: 'recommendation': got 'Recommendation.Poor_Match', expected one of 'Strong Match', 'Good Match', 'Partial Match' or 'Poor Match'. _generate_source_from_schema now passes enum_field_as_literal=LiteralType.All to datamodel-code-generator, so enum: [strings] schema nodes round-trip as Literal[...] instead of being regenerated as Enum classes. _exec_source_to_types now also exposes Literal in the rebuild namespace so model_rebuild resolves the deferred annotations.

[v0.26.1] - 2026-05-05

Changed

  • Live-run graph output: pipelex-agent run now emits the assembled GraphSpec as live_run_graph.json alongside live_run.json and the ReactFlow HTML.

[v0.26.0] - 2026-05-04

Highlights

  • Distributed tracing — pluggable event-log backends (NDJSON, DynamoDB, in-memory, buffering) emit TraceEvents that reassemble into a complete GraphSpec. Enabled by default; works in single-process mode too.

Added

Pipeline execution & delivery

  • PipeRun layer (pipelex/pipe_run/): new PipeRunProtocol with the in-process PipeRun implementation. Wraps pipe execution + delivery in a single orchestration unit.
  • Delivery framework: DeliveryAssignment model with webhook (HTTP POST) and storage (S3 / local) variants, run inline after a pipe completes.
  • LibraryCrate IR (pipelex/libraries/library_crate.py, library_crate_factory.py): serializable intermediate representation capturing the exact set of libraries (concepts, pipes, structures) required to execute a job — no shared filesystem assumed.
  • Schema-based dynamic model construction (pipelex/cogt/content_generation/schema_to_model_factory.py): rebuilds dynamic Pydantic models from the JSON schema embedded in ObjectAssignment, instead of relying on class identity. Cache bounded by an LRU (_SCHEMA_CACHE_MAX_SIZE = 1024); codegen serialized through a file lock to kill thundering-herd duplication when many distinct schemas arrive. Adds datamodel-code-generator>=0.55.0 as a runtime dependency.
  • Pre-generated pipeline_run_id support: pipeline_run_setup(), PipelineFactory, and PipelineManager accept an optional pipeline_run_id parameter for external run-record creation.

Distributed tracing (pipelex/tracing/)

  • EventLogProtocol with pluggable backends: NdjsonEventLog, DynamoDBEventLog, InMemoryEventLog, BufferingEventLog. Emits TraceEvents as pipes start/finish across processes. Configured via [pipelex.tracing_config] (backend = "ndjson" | "dynamodb"). Tracing is enabled by default — single-process runs also produce a trace.
  • pipelex[dynamodb] extra: pip install "pipelex[dynamodb]" for the DynamoDB event log backend.
  • writer_id on TraceEvent (default "primary"): every event records which writer (worker / process) produced it, propagated through every backend. Separate writer processes use a distinct writer_id so multiple writers can emit into the same backend partition without colliding on (workflow_id, sequence). Stamped at construction time (no copy-on-emit). Enables cross-writer dedup and per-writer aggregation.
  • NDJSON multi-writer key scheme: dedup key is (workflow_id, writer_id, type, sequence); sort key is (workflow_id, sequence, writer_id) — sequence is primary so the runner's UsageReportEvent does not sort before the router's PipeStartEvent. The file-handle cache key includes writer_id, so two writers for the same (pipeline_run_id, workflow_id) cannot share a stale handle.
  • DynamoDB sort key format: EVENT#{workflow_id}#{writer_id}#{sequence:010d}.
  • ContentGeneratorDry emits UsageReportEvents: invokes report_inference_job with a synthetic LLMTokensUsage so dry-run mode produces real events observable through the same backend path as live runs.
  • GraphSpecAssembler: rebuilds a complete GraphSpec offline from a stream of TraceEvents — equivalence with the live GraphTracer is pinned by tests.
  • UsageAggregator: aggregates per-pipeline LLM/extract/img-gen usage across workers from the event log.
  • Hub DI: PipelexHub.set_event_log() / get_event_log() for dependency injection of custom event log backends. make_event_log() factory selects the backend from config or hub injection; the 3 prior call sites that hardcoded NdjsonEventLog now go through it.

Inference error classification

  • InferenceErrorCategory enum (transient, configuration, content, capacity) with per-provider helpers that distinguish quota exhaustion from rate limits and detect content-policy violations — covers OpenAI, Anthropic, Google, Mistral, AWS Bedrock, and Pipelex Gateway.
  • All inference workers attach error category + user action: every LLM, image-gen, extract, and search worker across all providers raises exceptions with an InferenceErrorCategory and actionable user_action hint.
  • Structured ErrorReport: PipelexError.to_error_report() returns a dataclass with error type, category, retryable flag, user-action hint, model, and provider. CLI error handlers consume to_error_report() for consistent, structured display.
  • SecurityError(PipelexError): new base class in pipelex.base_exceptions for security-policy violations. UnsafeSchemaError now extends it (was ContentGenerationError) so security signals are not silently swallowed by domain except handlers.

Documentation

  • New "Under the Hood" pages: pipe-routing-and-execution.md and distributed-content-generation.md. Cover routing/dispatch and the dynamic-model serialization story.
  • New AGENTS.md at repo root, generated from the same source as CLAUDE.md via make rules.

Other

  • PipeOutput.graph_assembly_error: optional str field set when graph assembly raises, so consumers can distinguish "graph never produced" from "graph assembly failed". Previously the failure was logged as a warning and graph_spec silently stayed None.
  • pipe extract web URL support with full test coverage in tests/integration/pipelex/pipes/pipe_operators/pipe_extract/.
  • needs_inference flag on CLI commands: non-inference subcommands (e.g. doctor) skip inference setup, shaving cold-start time.
  • Environment-specific config: RunEnvironment is now loaded from the ENVIRONMENT env var (was ENV). Accepted values: local, dev, staging, prod.

Changed

  • Tightened inference quota classification patterns to reduce false positives on auth/setup errors:
  • Anthropic: bare "credit" token replaced with "credit balance", "out of credits", "insufficient credit". No longer misclassifies "your credit card was declined" or "we will credit your account" as quota exhaustion.
  • Google: bare "billing" token replaced with "billing limit", "billing quota", "billing exceeded", and "billing account". Continues to match real 429-class messages ("Billing account is not active", "billing limit reached"); stops matching unrelated billing-mentioning text.
  • Mistral / Gateway: bare "billing" likewise narrowed to "billing limit" / "billing quota". Mistral additionally distinguishes "out of credits" vs "insufficient credits".
  • Removed duplicate ContentGenerationError class: the unused pipelex.cogt.content_generation.exceptions.ContentGenerationError was deleted (had zero external imports). Its subclasses re-parented: NeitherUrlNorDataErrorPipelexError, UnsafeSchemaError → new SecurityError.
  • Storage providers unified: StorageConfig flattened to inheritance-based config so providers (local, s3) share a single shape across delivery and trace storage.
  • make rules now generates both CLAUDE.md and AGENTS.md by default: the pipelex.kit_config.preferred_agent_target setting has been renamed to preferred_agent_targets and is now a list. The default is ["claude", "agents"]. Cursor remains exclusive (["cursor"]) and cannot be combined with single-file targets. Downstream projects overriding this setting must rename the key and wrap the value in a list.
  • Cold start trimmed: heavy SDK imports (boto3, huggingface_hub, etc.) deferred behind TYPE_CHECKING or function-local guards across multiple plugins.
  • Graph viewer updated to mthds-ui v0.3.4: bumped from v0.3.0 — additional polish atop the resizable detail panel, escape-to-close, sticky header, prompt expand/collapse with copy button, concept refinement display.
  • Default models: small-vision and creative defaults now point to gemini-3.0-flash-preview. Added claude-4.6-sonnet and Bedrock token-auth support.
  • kajson upgraded from 0.3.1 to 0.5.0 — required for the dynamic-class source registry that backs schema-to-model reconstruction.
  • README install instructions: replaced step-by-step Claude Code setup with single copy-paste messages for Claude Code and Codex, added manual install section.

Fixed

  • Anthropic structured generation no longer flattens all errors to CONTENT. AnthropicLLMWorker._gen_object runs requests through instructor, which wraps SDK exceptions in InstructorRetryException. The previous handler categorized every wrapped error as CONTENT, so genuine RateLimitError, APITimeoutError, APIConnectionError, PermissionDeniedError, and AuthenticationError cases never reached the typed branches — they were reported as content failures (non-retryable) instead of TRANSIENT / CAPACITY / CONFIGURATION. The handler now unwraps InstructorRetryException.failed_attempts[-1].exception (with a __cause__/RetryError.last_attempt fallback) and routes recognized SDK exceptions through a shared categorization helper; truly unrecognized errors keep the CONTENT fallback.
  • Managed-deck detection no longer misclassifies user TOMLs. _is_managed_deck_filename previously treated any non-x_custom_*.toml as kit-managed, so a user file like my_overrides.toml dropped into the deck dir would have been reported (and on pipelex update overwritten) as if it were a kit file. Now requires the numbered ^\d+_.*\.toml$ prefix that the kit actually ships.
  • PipeLLM output structure prompt used the unresolved concept ref. When is_structure_prompt_enabled, get_output_structure_prompt was called with pipe_run_params.dynamic_output_concept_ref or output_stuff_spec.concept.concept_ref. The first form can be a bare code (no domain), which broke downstream concept resolution. Now passes the already-resolved output_stuff_spec.concept.concept_ref directly.
  • PIPELEX_NO_DECK_NOTICE suppression was too lax. Any non-empty value silenced the deck-staleness notice — including PIPELEX_NO_DECK_NOTICE=0, which users might reasonably expect to enable the notice. Now requires the documented PIPELEX_NO_DECK_NOTICE=1.
  • Shipped .kit_manifest.json carried kit_version: "0.25.0" for the 0.25.1 release, which would have triggered false stale-detection on fresh installs. Bumped to 0.25.1 and re-synced into pipelex/kit/configs/.
  • Stored-XSS guard: HTML escaped in fallback main_stuff.html.
  • GatewayExtractWorker teardown: guarded done-callbacks against cancelled tasks to stop noisy teardown errors.
  • URL checker: 401/403 are now treated as OK for auth-walled URLs.
  • Pipeline duplicate guard: registering the same pipeline twice raises explicitly instead of silently shadowing.

Documentation

  • docs/tools/cli/update.md no longer claims the deck advisory fires on every CLI invocation. Clarified that it is suppressed for login, init, doctor, update, and which.

Security

  • schema_to_model rejects x-python-* codegen extensions and restricts __import__ to an allowlist. datamodel-code-generator honors the x-python-import JSON Schema extension by emitting arbitrary from <module> import <name> statements with no sanitization. Combined with the prior gap that _make_restricted_builtins() did not block __import__, an attacker able to plant a crafted object_class_schema (where ObjectAssignment.object_class_schema: dict[str, Any] round-trips untouched) could cause arbitrary modules to be imported during exec() of generated code. Two-layer defense added: (1) _reject_unsafe_schema_extensions raises UnsafeSchemaError if any x-python-* key is present anywhere in the schema; (2) __import__ in the exec namespace is now wrapped to allow only pydantic, typing, typing_extensions, enum, datetime, decimal, uuid, __future__, collections, and re.
  • Restricted exec builtins, path sanitization, and atomic fingerprint caching in the schema-to-model pipeline.
  • No leaked exception strings on error paths; prefix guards on storage URIs.

[v0.25.2] - 2026-04-30

Fixed

  • pipelex update no longer flags user-added deck files for removal. The "managed file" filter accepted any .toml not prefixed with x_custom_, so project-local additions like pipelex-cookbook's cookbook.toml were reported as "removed upstream" with action "back up + remove". The filter now matches the documented numbered convention only (<digits>_*.toml); any other filename — cookbook.toml, x_custom_*.toml, etc. — is invisible to the update planner.

[v0.25.1] - 2026-04-29

Added

  • pipelex update command + model-deck staleness detection. New top-level command refreshes the installed model deck (~/.pipelex/inference/deck/) to match the kit shipped with the running pipelex version. Tracks per-file SHA-256 hashes plus the kit version in a .kit_manifest.json written next to the deck files. Supports --dry-run, --yes (non-interactive), --no-backup (skip .bak files for locally-modified deck files), and --local (project-local deck instead of global).
  • Boot-time deck staleness warn. Every pipelex CLI invocation prints a one-line yellow advisory when the installed deck's recorded kit version is older than the running pipelex (or when no manifest exists yet). Cost is one file read + one string compare. Suppress with PIPELEX_NO_DECK_NOTICE=1. Skipped automatically for pipelex login, init, doctor, update, and which.
  • pipelex doctor deck section. Reports per-file deck status (up_to_date, kit_added, kit_removed, clean_behind, locally_modified) and offers pipelex update as an auto-fix under --fix.
  • Manifest written by pipelex init. Fresh installs land with a current .kit_manifest.json so future updates can detect drift cleanly.
  • --dynamic-output-concept / -O flag on pipelex run. All three subcommands (run bundle, run pipe, run method) accept a concept ref (e.g. document_qa.ReferenceCount) used to resolve a pipe whose output is declared as Dynamic. Threaded through _run_core.execute_run to PipelexRunner.execute_pipeline(dynamic_output_concept_ref=...). Until now, Dynamic-output pipes were only callable from the Python runner.
  • Line-length-safe wrapping in the structures generator. pipelex build structures now wraps long descriptions so every emitted line stays under the 150-char ruff limit. Long class docstrings become a multi-line triple-quoted block; long Field descriptions become a parenthesized implicit-string-concatenation block (description=("first chunk " "second chunk")). Short descriptions still emit the compact single-line form. New unit tests in tests/unit/pipelex/core/concepts/structure_generation/test_structure_generator_wrapping.py cover both lengths and the combined long-everything case. Previously, descriptions above ~140 chars produced files that failed ruff check with E501.

Changed

  • Numbered deck files (N_*_deck.toml) are pipelex-managed. Each file now carries a header banner explaining that customizations belong in x_custom_*.toml (which pipelex never tracks or overwrites). Local edits to numbered files are preserved with a timestamped .bak.<UTC-timestamp> backup on pipelex update but will not survive future updates.

Fixed

  • PipeLLM Dynamic-output detection compared the wrong fields. pipe_llm.py checked self.output.concept.code == "native.Dynamic", but concept.code is the bare code ("Dynamic") not the qualified ref. The check never matched, so when a caller passed dynamic_output_concept_ref for a Dynamic-output pipe, the resolver branch was skipped silently: the output structure stayed DynamicContent (an empty StuffContent subclass), the LLM produced JSON shaped like the requested concept, and the result deserialized to {}. Detection now uses concept.code == NativeConceptCode.DYNAMIC and concept.domain_code == SpecialDomain.NATIVE.

  • Dynamic-output concept resolution rejected qualified refs. When the runtime override was supplied (e.g. "document_qa.ReferenceCount"), the previous code called make_concept_ref_with_domain(domain_code=self.domain_code, concept_code=output_concept_ref), producing "document_qa.document_qa.ReferenceCount" and a missing-concept lookup. Now uses make_concept_ref_with_domain_from_concept_ref_or_code, which extracts the domain from the input when it's already qualified and falls back to the pipe's domain only for bare codes. Callers can pass either form.

[v0.25.0] - 2026-04-28

Added

  • GPT-5.5 model support. New gpt-5.5 entry on the openai, azure_openai and gateway backends.

  • gpt-image-2 image generation (OpenAI). New model entry with img-gen routing and constraints on openai, azure_openai.

Fixed

  • PDF input declared on Azure OpenAI and gateway for the GPT-5.4 series. gpt-5.4, gpt-5.4-mini, gpt-5.4-nano, gpt-5.4-pro (and gpt-5.5) now advertise pdf in their inputs on azure_openai and the gateway, matching the existing OpenAI direct entries. Verified end-to-end with live document tests against both backends.

Changed

  • Img-gen taxonomies aligned for gpt-image-2. Convention: gpt_image for taxonomies/values shared across all OpenAI GPT Image models (legacy + gpt-image-2); gpt_image_legacy when the value applies only to gpt-image-1/-1-mini/-1.5 (gpt-image-2 uses unavailable where a legacy-only option does not apply). Renames:
  • AspectRatioTaxonomy.OPENAI_GPT_IMAGE_LEGACYGPT_IMAGE_LEGACY (value "openai_gpt_image_legacy""gpt_image_legacy")
  • AspectRatioTaxonomy.OPENAI_GPT_IMAGE_2GPT_IMAGE_2 (value "openai_gpt_image_2""gpt_image_2")
  • OutputFormatTaxonomy.GPTGPT_IMAGE_LEGACY (value "gpt""gpt_image_legacy")
  • OutputCompressionTaxonomy.GPT_IMAGEGPT_IMAGE_LEGACY (value "gpt_image""gpt_image_legacy")
  • InputFidelityTaxonomy.OPENAI_IMAGEGPT_IMAGE_LEGACY (value "openai_image""gpt_image_legacy")
  • NumImagesTaxonomy.GPTGPT_IMAGE (value "gpt""gpt_image")
  • InferenceTaxonomy.GPTGPT_IMAGE (value "gpt""gpt_image")
  • Removed dead AspectRatioTaxonomy.GPT.
  • InputImagesTaxonomy.GPT_IMAGE unchanged (already correct — shared across legacy and gpt-image-2).

  • HuggingFace and Fal img-gen workers honor model_choice rule. huggingface_img_gen and fal workers now pop "model" from args_dict instead of hardcoding inference_model.model_id, mirroring how prompt is already extracted. The model_choice = "model_id" rule is now required on every model under these backends; missing it raises ImgGenParameterError at call time. Rules added to qwen-image (HF) and to all Fal models (flux-pro, flux-pro/v1.1, flux-pro/v1.1-ultra, flux-2, fast-lightning-sdxl).

[v0.24.1] - 2026-04-22

Security

  • lxml floor >=6.1.0 to patch CVE-2026-41066 (GHSA-vfmq-68hx-4jfw): default configuration of iterparse() and ETCompatXMLParser() allowed XXE to local files (resolve_entities=True). lxml 6.1.0 changes the default to resolve_entities='internal'. Transitive via docling; floor added to the docling extra in pyproject.toml so downstream installs of pipelex[docling] cannot resolve a vulnerable version.
  • cryptography floor >=46.0.7 to patch CVE-2026-39892 (GHSA-p423-j2cm-9vmq): non-contiguous Python buffers passed to hashing APIs (e.g. Hash.update()) could read past the end of the buffer on Python >3.11. Transitive via google-auth (pulled by google, gcp-storage, google-genai extras) and moto (dev). Floor added to each affected extra in pyproject.toml — previous bump was lockfile-only, which did not protect downstream users resolving fresh from PyPI metadata.
  • pytest bumped to 9.0.3 to patch CVE-2025-71176 (GHSA-6w46-j5rx-g56g): vulnerable /tmp/pytest-of-{user} directory handling on UNIX could let a local user cause DoS or gain privileges. Dev-only dependency; pyproject.toml minimum bumped from >=9.0.2 to >=9.0.3.
  • transformers CVE-2026-1839 (GHSA-69w3-r845-3855) risk-accepted, alert dismissed. The vulnerability requires calling transformers.Trainer._load_rng_state() with an attacker-controlled checkpoint file. Pipelex only pulls transformers transitively through docling-ibm-models for PDF layout inference; the Trainer class is never imported or executed. Upgrade path is blocked upstream: docling-ibm-models 3.13.0 pins transformers!=5.0.*,!=5.1.*,!=5.2.*,!=5.3.*,<6.0.0,>=4.42.0, explicitly excluding the patched 5.0.0rc3 release. Revisit when docling-ibm-models adds support for transformers>=5.4.
  • Release-publishing GitHub Actions pinned to SHAs: pypa/gh-action-pypi-publish and sigstore/gh-action-sigstore-python in publish-pypi.yml are now pinned to full commit SHAs (version kept as a trailing comment) so a compromised tag on a third-party action cannot silently alter a PyPI release. Dependabot keeps them fresh.
  • .github/dependabot.yml added: declares pip and github-actions ecosystems, weekly cadence, with dev and runtime deps grouped to reduce PR noise. Security updates fire immediately regardless of schedule.
  • dependency-review.yml workflow added: runs GitHub's dependency-review-action on PRs to main, dev, and release branches. Fails the PR if it introduces a dependency with a moderate-or-higher CVE. Respects the existing transformers (GHSA-69w3-r845-3855) risk-acceptance via allow-ghsas. Enable as a required status check in branch protection for main to block vulnerable merges.

[v0.24.0] - 2026-04-16

Added

  • claude-opus-4-7 model: Registered on anthropic, bedrock, and gateway backends with 128k max output tokens, $5/$25 per MTok pricing, adaptive thinking, and PDF/vision support
  • XHIGH reasoning effort level: New effort tier across all providers, mapped to Anthropic's xhigh (recommended for coding/agentic work), OpenAI's xhigh, and best-available equivalents for Google and Mistral
  • TEMPERATURE_UNSUPPORTED constraint: New listed constraint for models that reject sampling parameters entirely, checked in both Anthropic and OpenAI completions workers
  • claude-4.6-sonnet model: Registered on anthropic, bedrock, and gateway backends
  • LLM deck cheap presets: Added cheap variants for writing-factual, retrieval, and engineering-code presets, with retrieval tiers from gemini-2.5-flash-lite to claude-4.7-opus
  • Bedrock bearer token authentication: New bedrock_access_variant config option supports "bedrock_token" auth using AWS_BEARER_TOKEN_BEDROCK env var, alongside the existing "aws_access" method (default)

Changed

  • Anthropic adaptive thinking rejects reasoning_budget: _build_thinking_params_for_budget now raises LLMCapabilityError for adaptive thinking models, guiding users to reasoning_effort instead — extended thinking (type: "enabled") is removed on Opus 4.7+
  • LLM deck: Updated best-claude alias to claude-4.7-opus, switched img-gen-prompting-cheap to @default-small-creative, removed deprecated builder presets, standardized cheap preset descriptions
  • Google MINIMAL reasoning level: ReasoningEffort.MINIMAL now maps to Google's ThinkingLevel.MINIMAL (was previously collapsed to LOW); GoogleThinkingLevel enum gains MINIMAL and the default google_config.effort_to_level_map updates accordingly

[v0.23.9] - 2026-04-14

Added

  • Graph UI asset sync workflow: package.json pins @pipelex/mthds-ui to a git tag, make sync-graph-ui clones and builds standalone assets, make check-graph-ui-sync verifies version alignment
  • CI check: graph-ui-check.yml workflow validates graph viewer assets match the pinned version on PRs to main
  • /update-graph-ui skill: automates bumping the mthds-ui version, syncing assets, and running tests

Changed

  • Graph viewer updated to mthds-ui v0.3.0: resizable detail panel, escape-to-close, sticky header, prompt expand/collapse with copy button, concept refinement display
  • README install instructions: Replaced step-by-step Claude Code setup with single copy-paste messages for Claude Code and Codex, added manual install section

[v0.23.8] - 2026-04-07

Changed

  • Standalone ReactFlow graph rendering: Replaced Jinja2 template-based HTML generation with a single standalone HTML asset, simplifying the graph rendering pipeline and removing the Jinja2 template dependency for ReactFlow output.

[v0.23.7] - 2026-04-06

Added

  • Graph tracing for pipe run data: Pipe run data and concept are now included inside the flowchart graph spec, enabling richer visualization of pipe execution results across all pipe types (LLM, extract, compose, search, image gen, sequence, condition, batch, parallel).
  • Assignment pipe: New pipe_assignment pattern for direct value assignment within pipe execution.

[v0.23.6] - 2026-04-06

Changed

  • Sub-pipe input normalization: Silently drop unsupported inputs field from step/branch dicts instead of logging a warning.

[v0.23.5] - 2026-04-04

Added

  • Gateway config: Introduced GatewayConfig to bundle gateway model specs with AWS region, propagating it through the backend library so bedrock backends use the correct region.
  • Config coverage tests: Integration tests that validate one model per Portkey config for each model type (LLM, image gen, extract, search), with all_configs_gw test profile and make ticc target.
  • nano-banana-2 model: Added gemini-3.1-flash-image-preview as nano-banana-2 with updated Google image gen costs.
  • DeepSeek models on bedrock: Added DeepSeek models to the bedrock backend configuration.

Changed

  • Image gen deck aliases: Updated aliases to nano-banana model variants and removed flux-2-pro.
  • Remote config: Bumped to v08.
  • Gateway model docs: Regenerated, removing retired models (claude-3.7-sonnet, deepseek-v3.1, deepseek-v3.2-speciale, flux-2-pro).

Fixed

  • deepseek-v3.1 structured output: Removed unsupported structured output capability from the bedrock deepseek-v3.1 model spec — the bedrock_aioboto3 worker does not implement object generation, so structured calls would fail at runtime.

[v0.23.4] - 2026-04-02

Changed

  • Pipe spec output alias: Removed output_type alias from parse_pipe_spec, keeping only output_concept as the single alias for the output field. Simplified the alias resolution logic accordingly.

[v0.23.3] - 2026-04-02

Changed

  • Pipe spec output aliases: parse_pipe_spec now accepts output_concept and output_type as aliases for the output field, with smart fallback when both alias and canonical field are present.

Fixed

  • Gateway terms check: Terms acceptance is now only required for inference operations, not for read-only operations like model spec fetching during validation.

[v0.23.2] - 2026-03-30

Changed

  • Claude Code plugin install command: Fixed as → /plugin install mthds@mthds-plugins across README and docs.
  • Claude Code plugin reload instructions: Added /reload-plugins as the primary method to activate the plugin, with exit/reopen as fallback.

[v0.23.1] - 2026-03-30

Changed

  • Concept spec: concept_code replaces the_concept_code as the canonical field name in concept specs and working memory factory.
  • Shared spec parsing: concept_cmd and pipe_cmd now delegate to the shared parse_concept_spec and parse_pipe_spec helpers, removing stale duplicate parsing logic while preserving compatibility and fixing alias/dict-mutation edge cases.
  • concept_ref / pipe_ref aliases: parse_concept_spec and parse_pipe_spec now accept concept_ref and pipe_ref as input aliases for better AI-agent compatibility.
  • Replace pip with uv in install commands across config files and error messages.
  • Docs links: Updated mthds.ai links to include /latest/ path.

Fixed

  • Concept alias bug: Concept alias handling previously listed concept_code as an alias instead of the_concept_code, causing valid input to be silently dropped. Fixed by the new shared parse_concept_spec helper.

[v0.23.0] - 2026-03-29

Added

  • Builder Operations module (pipelex/builder/operations/): New standalone operation functions for the build agent — concept_ops, inputs_ops, models_ops, output_ops, pipe_ops, runner_code_ops, validate_ops. These decouple build logic from CLI commands so it can be reused by agents and the API.
  • dry_run_pipeline (pipelex/pipe_run/dry_run_pipeline.py): New shared entrypoint to dry-run a pipeline from MTHDS contents and produce a GraphSpec. Used by both CLI graph commands and the API.

Changed

  • mthds_contentmthds_contents: Unified singular mthds_content: str | None into mthds_contents: list[str] | None across PipelexRunner, pipeline_run_setup, validate_bundle, CLI commands, and builder operations. Callers now pass a list of bundle content strings.
  • bundle_uribundle_uris: Renamed bundle_uri: str | None to bundle_uris: list[str] | None across PipelexRunner.__init__, pipeline_run_setup, dry_run_pipeline, and all CLI call sites. Multiple bundles can now be loaded simultaneously.
  • Bump mthds dependency to >=0.2.0 (from >=0.1.1) to match the updated RunnerProtocol interface.
  • Graph rendering refactor: Extracted dry-run logic from graph_rendering._dry_run_bundle into the new shared dry_run_pipeline function.
  • Agent CLI inputs: Refactored _inputs_core to delegate to builder.operations.inputs_ops.build_inputs_for_pipe instead of duplicating bundle validation and input rendering logic.
  • Agent CLI run: Added graph generation support with ReactFlow HTML output and side-effect metadata tracking in _run_core.
  • Deduplicate models_cmdmodels_ops: models_cmd.py is now a thin CLI wrapper delegating to list_models() and format_models_markdown() in models_ops.py, consistent with other agent CLI commands.

Removed

  • BuilderLoop and its iterative build-validate-fix cycle (builder_loop.py, builder.py, builder_errors.py). The build-agent CLI now drives spec construction directly.
  • pipelex build pipe CLI command and associated MTHDS workflow files (builder.mthds, agentic_builder.mthds, pipe_design.mthds, concept_fixer.mthds, synthesize_image.mthds).
  • pipelex-tools runtime dependency: Moved to dev-only dependency; plxt is now invoked via subprocess passthrough.

  • Talent system: Removed talent enums, config mappings (talent_preset_mappings), and talent preset tests. Pipe specs accept model presets directly via the model field, making the talent indirection unnecessary.

Fixed

  • Per-bundle dedup in pipeline run setup: Fixed duplicate bundle loading when the same bundle appears in multiple sources.
  • HTTP utils: Use HEAD-first strategy for URL validation to avoid downloading large payloads unnecessarily.
  • library_dirs passthrough: Fixed library_dirs not being forwarded in builder operations (inputs_ops, validate_ops).
  • Dry-run status: Fixed validation status reporting for dry-run results.

[v0.22.0] - 2026-03-25

Added

  • MiniMax Backend Support: Added integration for MiniMax models (M2, M2.1, M2.5, M2.7 series including high-speed variants) with an all_minimax routing profile.
  • check-model Agent CLI Command: Introduced pipelex-agent check-model to validate model references. Provides fuzzy matching suggestions, cross-collection suggestions, and wrong-sigil hints when an invalid model is provided.
  • Domain-Qualified Pipe References (pipe_ref): Added a pipe_ref property (domain.code) as the primary pipe index, allowing pipes with the same code to coexist across different domains. Bare code lookups remain supported as a fallback when unambiguous.
  • Duplicate Concept Detection: Added validation to warn about duplicate concept declarations across different bundles within the same library.
  • Shared TOML Formatter: Extracted a shared format_toml_string utility for consistent multi-line string formatting across the MTHDS factory and Agent CLI.

Changed

  • Agent CLI Output Formats: Overhauled pipelex-agent CLI output for LLM agents: concept and pipe commands now output raw TOML to stdout; models and doctor commands output Markdown by default (JSON still available via --format json).
  • Unified model Field in Pipe Specs: Replaced type-specific talent fields (llm_talent, extract_talent, img_gen_talent, search_talent) with a single model field accepting model presets directly. Talent mappings removed from pipelex-agent models output accordingly.
  • Initialization Checks: check_is_initialized now additionally verifies the presence of plxt.toml.
  • Documentation & Handoffs: Updated Claude Code skills plugin docs, CLI references.

Fixed

  • Domain Metadata Conflicts: Loading multiple bundles declaring the same domain with differing descriptions or system prompts now emits a warning and keeps the first declaration instead of throwing an error.
  • Cross-Domain Execution Tracking: Pipe controllers and the dry-run engine now track visited pipes using pipe_ref instead of bare codes, preventing false-positive loop detections when crossing domains.

Removed

  • assemble Agent CLI Command: Removed the pipelex-agent assemble command.

[v0.21.0] - 2026-03-19

Added

  • Web Page Extraction: PipeExtract now supports extracting content from web pages via the new linkup-fetch model, with render_js and include_raw_html parameters. Added @default-extract-web-page alias to the extraction model deck.
  • New AI Models: OpenAI / Azure: gpt-5.4, gpt-5.4-pro, gpt-5.4-mini, gpt-5.4-nano, gpt-5.2-codex; Mistral: mistral-small-4
  • DocumentContent Enhancements: Added public_url, filename, title, and snippet fields to support web pages and search citations.
  • AI Agent Documentation: Integrated mkdocs-llmstxt-md plugin to generate /llms.txt and /llms-full.txt for AI agents.
  • Claude Code Skills: Added internal .claude/skills (add-model, test-model) for registering and testing new inference models.

Changed

  • Web Fetching → Extract Domain: Moved web fetching from the Search domain into the Extract domain. Replaced FetchJob, FetchWorkerAbstract, and related classes with native handling in PipeExtract and a new GatewayExtractWorker. Split LinkupWorker into dedicated LinkupSearchWorker and LinkupExtractWorker.
  • Search Results Unification: SearchResultContent now uses DocumentContent instead of the removed SearchSourceContent.
  • Search Job Refactoring: Encapsulated SearchJob parameters into a new SearchJobParams object; updated SearchWorkerAbstract signatures accordingly.
  • Documentation Build: Replaced inline HTML generation with a docs/root-index.html template. Updated robots.txt to allow AI crawler access to llms.txt.

Fixed

  • Remote Image Fetching: Added error handling (httpx.HTTPStatusError, httpx.RequestError) in GeneratedContentFactory when downloading remote images, with graceful fallback to using the original URL as public_url.

Removed

  • page_image_captions parameter: Removed from PipeExtractSpec and ExtractJobParams.

[v0.20.13] - 2026-03-16

Removed

  • Remove unused ViewSpec class and related code from graph/reactflow — dead code that was no longer referenced.

[v0.20.12] - 2026-03-16

Fixed

  • Fix Google "Sitemap could not be read" error by adding Allow: /sitemap.xml to ROOT_ROBOTS_TXTDisallow: / was blocking Googlebot from fetching the root sitemap even though Sitemap: pointed to it.

[v0.20.11] - 2026-03-16

Fixed

  • Fix docs-deploy-root silently failing due to shell comments breaking the \ continuation chain in the Makefile recipe — root sitemap.xml and updated robots.txt were never deployed in v0.20.10.

[v0.20.10] - 2026-03-16

Fixed

  • Fix sitemap double-path bug (latest/0.20.9/page/) caused by site_url including /latest/ while mike also inserts the version prefix during deployment.
  • Override canonical URLs, og:url, and JSON-LD url to always point to /latest/ via template override.
  • Add root-level sitemap.xml generation with /latest/ URLs to docs-deploy-root.

[v0.20.9] - 2026-03-16

Changed

  • Reorganize documentation site architecture and navigation.
  • Add SEO meta descriptions to all doc pages.
  • Redesign README: lead with value, collapse details.

[v0.20.8] - 2026-03-13

  • Add needs_model_specs=True to agent CLI run and models commands.

[v0.20.7] - 2026-03-12

  • Bump mthds dependency to >=0.1.1.

[v0.20.6] - 2026-03-12

Added

  • needs_model_specs parameter: New option on Pipelex.make() and CLI factories to load real gateway model specs without enabling full inference. Validation commands (pipelex validate, pipelex-agent validate) now fetch live model specs, improving accuracy of pipe/method/bundle validation.

Removed

  • Deprecated pipelex_inference backend: Removed all traces of the legacy backend — config files, enum values, migration logic, deprecation warnings, and documentation admonitions. The transition to pipelex_gateway is complete.

Fixed

  • pipelex init inference --local: Fixed file filter to also copy .md files (e.g. Gateway model lists) to the backends directory, not just .toml files.
  • Gateway terms persistence: Hardened failure handling — when terms acceptance can't be persisted (e.g. unwritable global directory), gateway is now properly disabled in backends.toml as a fallback, preventing GatewayTermsNotAcceptedError at runtime.

[v0.20.5] - 2026-03-09

Changed

  • Config paths use pathlib.Path: All ConfigLoader properties and methods now return Path instead of str. Consumer code across the config system (doctor, init, backends, routing, credentials, telemetry, agent CLI) updated accordingly.
  • Layered config resolution for pipelex doctor and pipelex init: Config files are now resolved with project-first, global-fallback layering per file. Fixed replace_backend_file using CWD-relative path that broke when run from another directory.
  • Gateway terms are explicitly global: update_service_terms_acceptance now targets ~/.pipelex/ by default, since gateway terms are a user-level agreement, not project-level.
  • Tweaks to the pipelex-agent CLI to play well with the Claude Code plugin and skills.

[v0.20.4] - 2026-03-04

  • Improve README.md instructions for Claude Code and MTHDS skills.

[v0.20.3] - 2026-03-04

Fixed

  • Rewrote README with updated CV Batch Screening example, corrected CLI command, inputs schema, Python snippet, and pipe descriptions.

[v0.20.2] - 2026-03-04

Fixed

  • Fixed removal of the --reset flag from the init command docs.
  • Fixed the name of the PipeSequence results within a batch.

[v0.20.1] - 2026-03-03

Changed

  • Cost Report Consistency: Renamed platform_llm_id field to platform_model_id in LLMTokenCostReport, aligning with all other report types (ImgGen, Extract, Search, Fetch) that already use platform_model_id.
  • Test Coverage: Added linkup backend to the coverage test profile.

[v0.20.0] - 2026-03-03

Added

  • CLI Authentication: pipelex login command that initiates a browser-based OAuth flow (GitHub/Google) to authenticate with the Pipelex Gateway and save the API key locally.
  • Search Filtering: from_date, to_date, include_domains, and exclude_domains options on the PipeSearch operator, routed through a new GatewaySearchWorker.
  • Expanded Cost Reporting: Token usage and cost tracking now covers Search, Fetch, Extraction, and Image Generation jobs, in addition to LLMs.
  • New Models: Added gpt-5.3-codex (text, images, pdf inputs) and nano-banana-2.

Changed

  • Search Configuration: Renamed Linkup model IDs from linkup/standard to linkup-standard (and deep variant), simplified presets to $standard and $deep.
  • Default Pipeline Execution: Changed from in_memory to local.
  • Default Extraction Model: Changed from mistral-document-ai-2505 to azure-document-intelligence; removed mistral-document-ai-2505 from the supported Gateway models list.
  • Content Handling: TextAndImagesContent now supports an optional raw_html field.
  • Dev Experience: Makefile commands (generate-mthds-schema, update-gateway-models) run in quiet mode by default.

[v0.19.0] - 2026-03-02

Added

  • Web Search Integration: Introduced a new PipeSearch operator, native support for Linkup as a search backend provider, SearchResult and SearchResultContent concepts for handling answers with citations, and Model Deck support for search models.
  • Graph View Generation: Added a --view option to pipelex validate bundle that generates a ViewSpec JSON (compatible with ReactFlow) for client-side graph rendering without writing files to disk.

Changed

  • Test Configuration: Added a search pytest marker, excluded from default test runs.
  • mthds bumped to >=0.1.0.
  • pipelex-tools bumped to >=0.2.3.
  • linkup-sdk>=0.12.0 added as optional dependency for the Linkup search provider.

[v0.18.6] - 2026-03-01

Added

  • GitHub URL support for CLI method targets — All CLI commands accepting a method target (pipelex validate method, pipelex run method, pipelex build inputs method, etc.) now accept a public GitHub URL (e.g., https://github.com/org/repo/tree/main/methods/my-method). The repository is cloned automatically, and the method package is discovered and validated. Subdirectory URLs are supported.
  • Local path support for CLI method targets — Method targets can now also be local filesystem paths pointing to a directory containing a METHODS.toml. This works for both pipelex and pipelex-agent CLIs.
  • Added clone_default_branch() in mthds package for shallow-cloning a git repository's default branch.

[v0.18.5] - 2026-03-01

Changed

  • pipelex-agent assemble command now outputs JSON to stdout by default.

[v0.18.4] - 2026-03-01

Added

  • Introduced a lenient loading mode for InferenceBackendLibrary and RoutingProfileLoader: when enabled, logs warnings instead of raising errors for missing credentials, variable fallback failures, or disabled backends — skipping individual backends gracefully rather than crashing the entire load process.
  • Added needs_inference parameter to Pipelex.make, make_pipelex_for_cli, and make_pipelex_for_agent_cli to control whether full inference setup (credentials, gateway checks, telemetry) is required.
  • Added needs_inference_in_pipelex helper to shared_pytest_plugins to replace the inverted logic of the previous helper.

Changed

  • CLI Robustness: pipelex build, validate, show, graph, inputs, and which commands now run with needs_inference=False, allowing them to succeed even when backend credentials are missing or incomplete.
  • Refactored Pipelex.make: renamed disable_inference to needs_inference (default True). When False, enables lenient backend loading, uses a dummy remote config, disables telemetry, and skips validate_model_deck().

Deprecated

  • is_inference_disabled_in_pipelex in shared_pytest_plugins — migrate to needs_inference_in_pipelex.

[v0.18.3] - 2026-02-27

  • Updated pipelex-tools dependency to 0.2.1

[v0.18.2] - 2026-02-27

Added

  • Inputs path resolution — Relative file paths (e.g., "url": "data/invoice.pdf") inside inputs.json are now resolved relative to the inputs file's parent directory, making bundle directories self-contained and portable. Applies to both user and agent CLIs. Handles HTTP, data:, pipelex-storage://, and absolute paths by leaving them unchanged.

Changed

  • pipelex-agent init defaults to local project — The init command now targets the project-level .pipelex/ directory by default (at detected project root) instead of auto-detecting. The --local flag has been removed. Use --global/-g to target ~/.pipelex/. Errors out if no project root is found without -g.
  • pipelex-agent doctor supports --global/-g — The doctor command now accepts --global/-g to check the global ~/.pipelex/ directory. Without the flag, it auto-detects project .pipelex/ if present, else falls back to ~/.pipelex/.
  • pipelex-agent init --config improved help — The --config option help text now shows the JSON schema with field types upfront for better discoverability.

Fixed

  • pipelex-agent init -g service agreement — The --global flag now correctly writes the gateway service terms acceptance (pipelex_service.toml) to the target directory instead of always writing to the auto-detected config dir.
  • Deterministic file discovery orderfind_files_in_dir now sorts rglob/glob results for consistent ordering across platforms and Python versions. On Linux with Python < 3.13, rglob returned filesystem-order results, which could cause test_validate_all to fail by picking up a test package's METHODS.toml before pipelex internal bundles.

[v0.18.1] - 2026-02-25

Fixed

  • Fix docs deployment failure caused by shell metacharacters (parentheses) in Makefile PRINT_TITLE macro argument for docs-deploy-root target
  • Fix mike unable to find mkdocs binary by adding venv bin/ to PATH in all mike-based Makefile targets

[v0.18.0] - 2026-02-25

Highlights

  • Pipelex Gateway — The deprecated pipelex_inference backend is now replaced by pipelex_gateway, featuring remote model configuration fetching so you always have access to the latest models without updating Pipelex.

Getting your API key: 1. Get your API key at app.pipelex.com 2. Add it to your .env file: PIPELEX_GATEWAY_API_KEY=your-key-here

Gateway Supported models — included with the Free API Key - Language Models (LLM): - OpenAI: all models up to GPT-5.2 and Codex - Anthropic: Claude 3.7 Sonnet through Claude 4.5 Haiku/Sonnet/Opus - Google: Gemini 2.0/2.5 Flash, Gemini 2.5 Pro, Gemini 3.0 Flash/Pro - xAI: Grok 3/mini, Grok 4 (+ fast reasoning variants) - Open-source: Mistral Large 3, DeepSeek v3.1/3.2/Speciale, GPT-OSS 20B/120B1, Kimi K2, Phi-4, Qwen3-VL 235B - Document Extraction: Mistral Document AI, Azure Document Intelligence, DeepSeek OCR - Image Generation: GPT-Image 1/1.5, Flux 2 Pro, Nano Banana/Pro

Accepting the Terms of Service:

When you run pipelex init, you'll be prompted to accept the Gateway terms of service. By using Pipelex Gateway, telemetry is automatically enabled and identified by your API key (hashed for security) to monitor service quality and enforce fair usage. We only collect technical data (model names, token counts, latency, error rates)—never your prompts, completions, or business data. See our Privacy Policy.

⚠️ Migration deadline: If you were using pipelex_inference, please migrate soon—the legacy service will be shut down within few days. Get your new Gateway key at app.pipelex.com.

  • Execution Graph Visualization System (preview feature) — Comprehensive tracing and visualization for pipeline executions.

CLI: --graph flag on pipelex run generates execution graphs. New pipelex graph render <graph.json> command for post-run rendering.

Viewers: Interactive ReactFlow viewer (reactflow.html) with pan/zoom and node inspector. Mermaid diagrams (mermaidflow.html) with subgraphs and clickable nodes.

Makefile: make view-graph (vg) and make serve-graph (sg) to start a local graph viewer.

  • Pydantic Structure Generation — Two new CLI commands bridge Pipelex's declarative concepts with your Python code:

pipelex build structures /library_dir/ — Generates Pydantic models from all concept definitions found in the specified library directory. Now you have your structures as Python code: you can iterate on them, add custom validation functions, or use them as type hints in your code.

pipelex build runner — Now automatically generates both the Python runner file AND the required Pydantic structures. When you run this command, it creates a complete, ready-to-execute Python script that imports the generated structures, so you can immediately use typed objects in your pipeline code.

See the Build Commands documentation for usage examples.

  • New Backends & Models:
  • Hugging Face Inference — Support for Hugging Face Inference API, including qwen-image text-to-image model.
  • Google gemini-3.0-flash-preview
  • Mistral OCR latest model mistral-ocr-2512
  • Scaleway inference provider support for open-source models
  • Portkey AI backend integration for unified access to multiple models through a single API key

  • Document Support in PipeLLM — Include Document objects (like PDFs) directly in prompts using @variable or $variable syntax. Supports single documents, multiple documents, and lists, combinable with text and image inputs.

  • PipeCompose Construct Mode — New mode for deterministically building StructuredContent objects without an LLM. Compose fields from working memory variables, fixed values, templates, and nested structures.

  • Content Storage System — Configurable storage for generated artifacts (images, extracted pages). Supports local filesystem (.pipelex/storage/), in_memory, AWS S3 (pip install pipelex[s3]), and Google Cloud Storage (pip install pipelex[gcp-storage]). Cloud providers support both public URLs and time-limited signed URLs. Content referenced via stable pipelex-storage:// URIs.

  • Langfuse & OpenTelemetry Observability — New OpenTelemetry-based observability system enables powerful tracing and Evals through Langfuse integration. Also supports OTLP-compatible backends (Datadog, Honeycomb, etc.). Configured via .pipelex/telemetry.toml.

  • Python 3.14 Support — Officially tested and supported.

  • Agent CLI (pipelex-agent): New machine-first CLI for AI agents with structured JSON output for all commands (build, run, validate, inputs, concept, pipe, assemble, graph, models, doctor).

  • LLM Reasoning Controls: Unified support for "Thinking" models (Chain of Thought) with reasoning_effort, reasoning_budget, and thinking_mode parameters. Supports Anthropic Extended Thinking, Google Gemini Thinking, OpenAI Reasoning (o1/o3), and Mistral/Magistral models. Includes new presets: $deep-analysis and $quick-reasoning.
  • Image-to-Image Generation: PipeImgGen now supports input images via input_images field, with InputImagesTaxonomy for provider-specific handling and variable reference detection ({{ var }}, $var, @var) in prompts.
  • PipeCompose "Construct" Mode: New construct mode for building structured objects (dictionaries/Pydantic models) directly from variables.
  • Nested concepts in inline structures: You can now define nested structures for your concepts in your .plx files. Learn more here: Nested Concepts in Inline Structures.

Breaking Changes

  • CLI restructure: method / pipe subcommandspipelex run, pipelex validate, and all pipelex build subcommands (runner, inputs, output) now require an explicit method or pipe keyword. For example: pipelex run method my-method or pipelex run pipe scoring.compute. The old pipelex run <target> form is no longer supported. The agent CLI (pipelex-agent) follows the same structure.

Added

  • Method resolution (cli/method_resolver.py) — resolves installed method names to pipe codes and library directories. Integrates with mthds discovery module to find methods in ~/.mthds/methods/ and ./.mthds/methods/.
  • run method command — run an installed method by name, optionally overriding the pipe with --pipe.
  • validate method command — validate all bundles in an installed method.
  • build runner/inputs/output method commands — generate build artifacts for installed methods.
  • Persistent Credential Storage (~/.pipelex/.env): pipelex init now prompts for missing API keys after backend selection and saves them to ~/.pipelex/.env with 0600 permissions. Credentials are loaded automatically at startup (global defaults, overridden by project .env).
  • pipelex init credentials Command: New standalone focus to re-enter API keys without resetting configuration. Scans enabled backends, detects missing env vars, and prompts only for those.
  • Package Management System: Introduced a full package manager for MTHDS with METHODS.toml manifests, methods.lock lockfile generation, local path and remote Git-based dependency resolution (MVS), a local package cache (~/.mthds/packages), and a new pipelex pkg CLI command group (init, list, add, lock, install, update, publish, search, inspect, graph).
  • Hierarchical Domains: Support for dot-separated domain namespaces (e.g., legal.contracts.shareholder) and cross-package references via alias->domain.pipe syntax.
  • PipelexRunner: Introduced the PipelexRunner class as the primary entry point for executing pipelines, replacing PipelexClient and standalone execute_pipeline/start_pipeline functions.
  • Linting & Formatting: Integrated plxt (Pipelex Tools) for formatting and linting .mthds and .toml files, including CI/CD checks alongside ruff.
  • JSON Schema: Automatic generation of mthds_schema.json for standard definition, IDE validation, and auto-completion.
  • Dev CLI: Added pipelex-dev CLI for internal development tasks (e.g., schema generation).
  • CSP Nonce Support: Added Content Security Policy nonce support for generated HTML graphs (Mermaid/ReactFlow) to enable secure rendering in VS Code webviews.
  • OpenRouter Backend: Added OpenRouter as an inference backend with 337 chat model definitions and 14 image generation models.
  • Builder Auto-Repair: Self-healing capabilities including auto-generation of undeclared concepts, multiplicity mismatch fixes, and pruning of unreachable pipes/unused concepts.
  • Concept Field Features: Added choices support (compiles to Literal types/Enums) and explicit list type with item_type/item_concept_ref.
  • PipeFunc Execution Error Context: When a @pipe_func function fails during execution, the error message now includes detailed context: the function name, actual input values from working memory, expected output type, and the original error with its type. This makes debugging PipeFunc errors much easier.
  • pipelex build output CLI Command: New command to generate example output JSON for a pipe, complementing the existing pipelex build inputs command. Shows the expected output structure based on the pipe's output concept type, with multiplicity support. For pipes with native.Anything output (e.g., PipeCondition with different mapped pipe outputs), displays all possible outputs from mapped pipes.
  • Telemetry System: Introduced anonymous usage tracking and exception capture for CLI commands (graph render), reporting to both user-configured and Pipelex analytics endpoints.
  • PipeExtract Operator Validation: Added strict input validation that raises configuration errors for incompatible input types or when document-specific parameters are used with image inputs.
  • PipeCondition Output Auto-Fix in Builder Loop: The pipe builder now automatically fixes PipeCondition output concept errors during validation. If all mapped pipes have the same output, the PipeCondition output is set to that concept; otherwise it's set to native.Anything.
  • PipeFunc Return Type Validation: Added validation to ensure that a PipeFunc function's return type matches the output concept's structure class.
  • URL Validation on ImageContent and DocumentContent: Both models validate external resources (HTTP/HTTPS URLs via HEAD request, local file paths via existence check) through a validate_resources() method called during pipeline execution (validate_before_run) rather than at model instantiation. Internal URIs (data:, pipelex-storage://) skip validation entirely. Validation is skipped in dry-run mode where inputs use mock URLs.
  • Literal Type Support in Dry Run Mocks: DryRunFactory now detects Literal type annotations (including Optional[Literal[...]]) and generates valid mock values by randomly picking from the allowed choices instead of producing invalid random strings.
  • Literal Type Support in pipelex build inputs/pipelex build output: The concept representation generator now handles Literal fields by picking a random value from the allowed choices, and generates mock URL patterns for url fields.
  • Literal Error Handling in Validation Messages: Pydantic validation error formatting now recognizes literal_error types and displays them as "Invalid choice errors" with the actual value and expected options.
  • PipeRunError Catching in Bundle Validation: validate_bundle now catches PipeRunError during dry run and wraps it in ValidateBundleError with a clear message.
  • ValidationError Catching in Pipeline Execution: execute_pipeline now catches Pydantic ValidationError from input construction, formats the errors, and raises a PipeExecutionError with a clear message.
  • Broader Error Handling in CLI pipelex run: The CLI run command now catches PipelexError in addition to PipelineExecutionError, providing better error messages for failures that occur outside the pipeline execution itself.
  • Content Filenames: Added filename field to ImageContent and DocumentContent, with auto-population from local file paths via new extract_filename_from_uri helper.
  • Batch Validation Error Type: Introduced PipeValidationErrorType.BATCH_ITEM_NAME_COLLISION for naming conflicts in batch operations.
  • Documentation: Added naming convention rules to builder.plx and pipe_design.plx (batch input lists should be plural, item names singular).
  • --library-dir CLI Option: --library-dir / -L option for pipelex run, pipelex validate, and pipelex build subcommands (one-shot-pipe, partial-pipe) to specify additional directories for searching pipe definitions. Can be specified multiple times.
  • Automatic File Loading: The core pipeline execution functions (pipelex.execute_pipeline, pipelex.start_pipeline) can now directly load a pipeline from a file path via a new bundle_uri parameter.
  • Dry Run Mode: pipelex run --dry-run executes pipeline logic without API calls, useful for validating structure and generating orchestration graphs. Combine with --mock-inputs to generate mock data for missing required inputs.
  • | with_images Jinja2 Filter: Explicitly extract and include all nested images from complex data structures (e.g., Page objects or custom concepts with Image fields). Renders the object's text representation while making associated images available to the LLM.
  • System Prompt Media Support: Reference images and documents in system_prompt using the same $variable and @variable syntax as the user prompt.
  • Pipelex Gateway Service: Terms of service management via .pipelex/pipelex_service.toml and interactive acceptance flow in pipelex init and pipelex init agreement.
  • Gateway Available Models Documentation: Auto-generated reference of all LLM, Document Extraction, and Image Generation models available through the Gateway.
  • Configurable Retry Logic: Exponential backoff for inference API calls, configurable in pipelex.toml under [cogt.tenacity_config]
  • Context Manager Support: The Pipelex class now supports with Pipelex.make(): ... for graceful shutdown
  • Validation Improvements:
  • Pipelex Bundle concept keys: prevent bundles from re-creating a native concept
  • PipeSequence: output multiplicity must match the last step's output multiplicity
  • PipeFunc: output multiplicity must match the function return type (ListContent subclass for multiplicity=true)
  • Rendering Protocols: Three new @runtime_checkable protocols (ImageRenderable, TagRenderable, TextFormatRenderable) to formalize the interaction between data types and Jinja2 filters.
  • Unified URI Handling System: New pipelex.tools.uri module providing type-safe parsing for HTTP/HTTPS URLs, local file paths, file URIs, pipelex-storage:// URIs, and base64 data URLs.
  • Automatic Input Data Storage: Pipeline pre-processing step that converts large data: URLs in an ImageContent or DocumentContent into pipelex-storage:// URIs for improved performance, by storing the data with the configured storage provider. Configurable via is_normalize_data_urls_to_storage in pipelex.toml.
  • pipelex build inputs Command: New CLI command to generate example input JSON files for pipes. Supports --library-dir (-L) to specify library directories.
  • Pipe Code Syntax Validation: Bundle validation now checks that pipe codes and main_pipe values use valid snake_case syntax, with proper error categorization (INVALID_PIPE_CODE_SYNTAX).

Changed

  • CI Runner Optimization: Split self-hosted runners into dedicated test (D32) and lint (D4) pools with pre-baked Docker image (Python 3.10-3.14, UV, dependency cache) for faster job startup.
  • MTHDS Light Client Extraction: The light client protocol (runner, pipeline models, pipe output abstractions) has been extracted from pipelex into the new mthds package on PyPI. Pipelex now depends on mthds>=0.0.1 and implements its RunnerProtocol.
  • Global/Local Config Split: pipelex init now creates configuration in ~/.pipelex/ (global) by default. Use pipelex init --local to create project-level overrides in {project_root}/.pipelex/. Config loading merges: package defaults → global → project → overrides.
  • IDE Extension Detection: Extension check now uses code --list-extensions / cursor --list-extensions for reliable detection instead of folder scanning. Shows separate marketplace links for VS Code (Microsoft Marketplace) and Cursor (Open VSX Registry).
  • Quieter pipelex init: Removed verbose file listing and reset messages from config initialization output.
  • pipelex init Help Text: Detailed focus option descriptions now shown in pipelex init --help explaining each focus (all, config, credentials, inference, routing, telemetry, agreement).
  • File Extension: Pipeline definitions now use .mthds instead of .plx; the project refers to "Methods" (MTHDS) rather than "Pipelex workflows".
  • Syntax: In PipeParallel definitions, parallels field renamed to branches; plx_config in pipelex.toml renamed to mthds_config.
  • Documentation: Comprehensive update to reflect .mthds file format, package management, and new project structure.
  • Model Deck Updates: Default premium model now claude-4.6-opus; added Mistral models (mistral-small-3.2, mistral-large, magistral series) and gpt-5 placeholders.
  • Jinja2 Rendering: Image objects now replaced with placeholders (e.g., [Image 1]) during text generation; async filters only registered in async environments.
  • Backend Configurations: Added effort_to_level_map and effort_to_budget_maps for reasoning translation; disabled Google Vertex AI by default.
  • Dependencies: Updated pypdfium2, anthropic, and mistralai version constraints.
  • pipelex run --dry-run: No longer pretty prints the main_stuff output, matching the expected behavior for dry runs where no actual inference occurs.
  • pipelex build structures Command: Now uses a lightweight loading mechanism that only processes domains and concepts, skipping pipe loading and validation entirely. This fixes the chicken-and-egg problem where structure generation would fail due to pipe validation errors before the structures were even created. Added --force / -f flag to regenerate all structures without checking if classes already exist.
  • Test Profile System: Refactored integration tests to use a new configuration system (.pipelex/test_profiles.toml) with dev, ci, and full profiles for controlling which AI models are used in parametrized tests, replacing runtime filtering and hardcoded model lists.
  • pipelex run --graph Flag: Now acts as an override for pipelex.toml settings instead of defaulting to true.
  • Default Image Generation Models: Updated in base_deck.toml: base-img-gen: flux-2-pro, best-img-gen: nano-banana-pro, fast-img-gen: gpt-image-1-mini
  • Remote Configuration: Updated service URL to version 3.
  • GatewayExtractWorker: Now checks model capabilities before attempting image captioning.
  • Change the output validation of PipeCondition: If all mapped pipes have the same output concept, PipeCondition's output MUST be that same concept. If mapped pipes have different output concepts, PipeCondition's output MUST be the native concept Anything.
  • CLI: Changed pipelex validate all to pipelex validate --all (or -a).
  • StructuredContent.rendered_html(): Now recursively calls rendered_html() on nested StuffContent fields instead of using json2html conversion. Also skips None values and uses HTML table format.
  • Batch Pipe Validation: Enforced stricter naming rules for batch specs—input_item_name must differ from input_list_name and not shadow existing input keys, with clearer error messages suggesting plural/singular conventions.
  • Jinja2 Integration Refactored to Protocol-Based Approach: Replaced the Jinja2Registry singleton and handler functions with a decoupled protocol-based system, eliminating circular dependencies between the template layer and core domain logic.
  • StuffArtefact Redesigned as Delegation Adapter: Now a lightweight, immutable adapter that delegates attribute access directly to underlying Stuff and StuffContent objects, improving performance and providing more intuitive field access.
  • Image Extraction Moved to Content Types: StructuredContent, ListContent, ImageContent, and TextAndImagesContent now implement the ImageRenderable protocol, replacing centralized handler logic.
  • ⚠️ Breaking: User override config renamed from pipelex_super.toml to pipelex_override.toml.
  • Image Generation Architecture: Refactored to taxonomy-based approach. Standardizes parameter translation (aspect_ratio, quality, output_format) to provider-specific APIs.
  • Document Extraction Improvements: pypdfium2 extractor now extracts embedded images from PDFs. Response parsing uses dedicated Pydantic schemas for validation.
  • Default Model Change: extract_text_from_visuals deck now defaults to azure-document-intelligence
  • pipelex_inference replaced by pipelex_gateway: See Highlights for migration details. New PIPELEX_GATEWAY_API_KEY environment variable; default routing profiles updated to pipelex_gateway_first.
  • Telemetry System Split: Now two separate streams: 1. Pipelex Gateway telemetry for service monitoring (never collects prompts/completions/business data) 2. Custom telemetry to user-configured backends 3. Config updated accordingly (telemetry.toml):

    • Renamed [posthog] to [custom_posthog] to distinguish user's PostHog from Pipelex Gateway telemetry
    • Added new [custom_portkey] section with force_debug_enabled and force_tracing_enabled settings
  • Main Configuration Overrides Updated (.pipelex/pipelex.toml):

  • pipelex_override.toml (final override) renamed from pipelex_super.toml to pipelex_override.toml and moved from repo root to .pipelex/ directory
  • telemetry_override.toml (personal telemetry settings)
  • is_generate_cost_report_file_enabled default changed from true to false

  • Documentation:

  • Clarified Setup (first run) vs Configuration (TOML reference), added a Setup overview page, and added contributor docs for configuration defaults/overrides.
  • Added the "Under the Hood" page documenting the execution graph tracing system.
  • pipelex init: Now creates a documented telemetry.toml template instead of prompting for preferences
  • Model Catalog Updated: Latest models (gpt-5.1, claude-4.5-opus, gemini-3.0-pro, etc.) and updated waterfalls in base_deck.toml
  • Model Constraints Refactored: From simple lists to structured valued_constraints dictionaries (e.g., valued_constraints = { fixed_temperature = 1 })
  • OpenAI Responses API: New implementation now differentiates between openai_completions and openai_responses
  • CLI Initialization: Commands refactored to use centralized Pipelex initialization factory for improved error handling
  • pipelex doctor: Enhanced to detect outdated telemetry.toml formats and suggest fixes
  • --output-dir Option: The runner, structures, and inputs CLI commands now accept this option
  • Cost Report: Now displays a note clarifying that it only includes LLM costs
  • description Field Now Required: In PipeAbstract, PipeBlueprint, and PipeSpec classes.
  • Configuration: New [pipelex.pipeline_execution_config.graph_config] section in pipelex.toml for fine-grained control over graph generation, data embedding, and rendering options.
  • CLI: All pipelex commands now accept --no-logo to suppress the Pipelex banner in the terminal — useful to reduce tokens.

Fixed

  • find_project_root Home Directory Bug: The project root walker no longer considers the home directory (~) as a project root, even if it contains stray marker files like package.json.
  • Python 3.10 Compatibility: Fixed datetime.UTC import (Python 3.11+) to use datetime.timezone.utc.
  • Graph Rendering: Fixed dashed edge rendering for PipeBatch and PipeParallel relationships.
  • Image Generation Response Parsing: Hardened image response parsing to handle varied provider response formats more robustly.
  • Helpful Error for get_stuff_as(ListContent[T]): When users incorrectly call get_stuff_as("name", ListContent[Something]) instead of get_stuff_as_list("name", Something), the error message now explicitly suggests using get_stuff_as_list().
  • PipeFunc ListContent[T] Validation: Fixed validation rejecting valid ListContent[T] return types for array outputs (T[]). Previously, a function returning ListContent[Expense] would fail validation for output = "Expense[]" with a misleading error. The validation now correctly extracts and validates the generic type parameter from Pydantic's metadata.
  • PipeFunc Class Name Matching: Fixed validation failing when the return type class and concept structure class are logically the same but loaded from different contexts. The validation now uses class name matching as a fallback, allowing ListContent[Expense] to match Expense[] even if the Expense class objects differ.
  • Fixed PipeImgGen not properly converting ImageContent to custom subclasses (e.g., Receipt(ImageContent)). The pipe now uses smart_dump() before model_validate() to correctly instantiate the output concept's structure class.
  • Corrected output directory creation logic in pipelex run to properly respect the --no-graph flag and configuration settings.
  • Fixed a bug when trying to print HTML content in a TextContent object.
  • Fixed the Pipelex CLI for generating structures, inputs, runner files.
  • Fixed @pipe_func decorated functions showing "function not found" instead of explaining why the function is ineligible (e.g., missing return type annotation).
  • Fixed PipeLLM with list output (e.g., output = "Item[]") not producing ListContent when run inside a nested PipeSequence with batch_over.
  • Duplicate Pipe Error Message: When a pipe code is declared in multiple .plx files (or twice in the same file), the error message now shows which bundle file(s) contain the conflicting declarations instead of a misleading message about "running the same pipe twice in the same pipeline".
  • Fixed pipelex build runner and pipelex build inputs generating string placeholders (e.g., "number_int | float") instead of numeric values for Number concepts with int | float union type fields.
  • Fixed structure generation failing with PydanticUserError when a concept structure references native concepts (e.g., native.Html). The generator now properly resolves native concept refs to their content classes (e.g., HtmlContent) with correct imports.
  • Nested Image Handling: Images nested within structured data are now properly replaced with [Image N] tokens. The | with_images filter and ImageRegistry system correctly extract images from complex nested structures.
  • PipeCondition Validation: Output multiplicity validation now works correctly.
  • Error Reporting: PipeCompose validation errors now include formatted details and failing field values.
  • Duplicate Pipeline Registration: Running a pipeline from a file that was also part of a pre-loaded library (via PIPELEXPATH) no longer causes a duplicate domain registration error. The system now tracks absolute paths of loaded library files and skips files already loaded.
  • pipelex build structures: Corrected output file naming and resolved import path generation.
  • ConceptFactory.make_from_blueprint: Now correctly handles native concepts.

Removed

  • Legacy Client & Execution Modules: Removed PipelexClient, protocol files, and standalone pipelex.pipeline.execute/pipelex.pipeline.start modules (replaced by PipelexRunner and mthds package).
  • Legacy Config: Removed plx_config.py and .plx-specific configuration references.
  • pipelex kit Command: The kit commands have been removed from the main CLI. They are now internal tools for Pipelex contributors only, available via pipelex-dev kit rules.
  • pipelex kit migrations Command: Removed entirely.
  • pipelex kit remove-rules Command: Removed entirely.
  • PLX Syntax Agent Rules: Removed write_pipelex.md and run_pipelex.md agent rules. These PLX syntax guides are no longer installed in client projects.
  • [pipelex.kit_config] Configuration: Removed from client project configuration (.pipelex/pipelex.toml).
  • openai_utils Module: Removed pipelex.plugins.openai.openai_utils; logic now in centralized image preparation utilities.
  • Pipeline Tracking feature: Removed entirely, including the pipelex/pipeline/track module, PipelineTracker components, related configuration, tracker calls in pipe controllers, and associated documentation.
  • Flow Generator: The old flow generator has been removed.

Security

  • CSP Nonce Support: Added Content Security Policy nonce for generated HTML graph outputs.

Deprecated

  • pipelex_inference backend in favor of pipelex_gateway (marked as "🛑 Legacy" in configuration template)

For Contributors

  • Technical Documentation: Added a new "Under the Hood" page documenting the StuffArtefact delegation pattern and image rendering architecture.
  • Enhanced Testing: Added extensive unit and integration tests for the protocol-based rendering system, including nested image extraction and filter error conditions.
  • Agent Rules: Added pipelex_standards.md outlining standards for the Pipelex configuration system, also included as rules for AI development agents.
  • Agent Rules: New target make agent-check for faster linting.
  • ⚠️ Breaking — Content Handling Overhaul: GeneratedImage replaced by internal GeneratedImageRawDetails; ImageContent is now standard (without base_64 field). PipeExtract outputs PageContent list directly. Content persistence now handled automatically by storage system.
  • ⚠️ Breaking — Image Prompt Representation: Redesigned PromptImage models—consolidated PromptImagePath and PromptImageUrl into PromptImageUri; now uses Pydantic discriminated union for URI, base64, and raw bytes sources.
  • Centralized Image Preparation: Moved image fetching and base64 conversion logic to pipelex.cogt.image.prompt_image_utils, simplifying LLM provider plugins (Anthropic, Google, Mistral, OpenAI).
  • Unified Resource Loading: Updated all file/URL reading components (PDF renderers, document extractors) to use the new URI handling system, replacing the legacy pipelex.tools.misc.path_utils module.
  • Async HTTP Fetching: Renamed fetch_file_from_url_httpx_async to fetch_file_from_url_httpx; removed redundant synchronous version.
  • PreparedImage Abstraction: New models (PreparedImageHttpUrl, PreparedImageBase64) representing images ready for LLM provider APIs.
  • Pipelex Gateway Model Management: New CLI commands (pipelex-dev update-gateway-models, pipelex-dev check-gateway-models) and corresponding make targets (ugm, cgm) to generate and verify gateway model catalog. CI now validates this documentation is up-to-date.
  • Test Suite Pre-flight Check: Verifies Gateway terms acceptance before running tests, providing clear error messages.
  • Content Rendering: StuffContent.rendered_* methods now provide both synchronous and asynchronous variants.

Migration Notes

  • Telemetry configuration migration: If you have an existing telemetry.toml, rename:
  • [posthog][custom_posthog]
  • [posthog.tracing][custom_posthog.tracing]
  • [posthog.tracing.capture][custom_posthog.tracing.capture]
  • Or run pipelex init telemetry --reset to regenerate the file with the new structure

Refactored

  • Anthropic Backend: Internal streaming for standard completions to prevent SDK timeouts, configurable structured output timeout (structured_output_timeout_seconds), improved error mapping, and increased Bedrock Claude max_tokens (8K → 64K) with removal of max_output_tokens_limit constraint.
  • ⚠️ Breaking — Pipe I/O Specification: The output (and inputs) of a pipe is now a StuffSpec object that holds the concept and the multiplicity.
  • Naming Convention: Renamed domain to domain_code where relevant.
  • Dry Run Methods: Refactored the dry run methods of the PipeAbstract class.

[v0.18.0b4] - 2026-02-23

Added

  • Persistent Credential Storage (~/.pipelex/.env): pipelex init now prompts for missing API keys after backend selection and saves them to ~/.pipelex/.env with 0600 permissions. Credentials are loaded automatically at startup (global defaults, overridden by project .env).
  • pipelex init credentials Command: New standalone focus to re-enter API keys without resetting configuration. Scans enabled backends, detects missing env vars, and prompts only for those.
  • Package Management System: Introduced a full package manager for MTHDS with METHODS.toml manifests, methods.lock lockfile generation, local path and remote Git-based dependency resolution (MVS), a local package cache (~/.mthds/packages), and a new pipelex pkg CLI command group (init, list, add, lock, install, update, publish, search, inspect, graph).
  • Hierarchical Domains: Support for dot-separated domain namespaces (e.g., legal.contracts.shareholder) and cross-package references via alias->domain.pipe syntax.
  • PipelexRunner: Introduced the PipelexRunner class as the primary entry point for executing pipelines, replacing PipelexClient and standalone execute_pipeline/start_pipeline functions.
  • Linting & Formatting: Integrated plxt (Pipelex Tools) for formatting and linting .mthds and .toml files, including CI/CD checks alongside ruff.
  • JSON Schema: Automatic generation of mthds_schema.json for standard definition, IDE validation, and auto-completion.
  • Dev CLI: Added pipelex-dev CLI for internal development tasks (e.g., schema generation).
  • CSP Nonce Support: Added Content Security Policy nonce support for generated HTML graphs (Mermaid/ReactFlow) to enable secure rendering in VS Code webviews.
  • OpenRouter Backend: Added OpenRouter as an inference backend with 337 chat model definitions and 14 image generation models.

Changed

  • CI Runner Optimization: Split self-hosted runners into dedicated test (D32) and lint (D4) pools with pre-baked Docker image (Python 3.10-3.14, UV, dependency cache) for faster job startup.
  • MTHDS Light Client Extraction: The light client protocol (runner, pipeline models, pipe output abstractions) has been extracted from pipelex into the new mthds package on PyPI. Pipelex now depends on mthds>=0.0.1 and implements its RunnerProtocol.
  • Global/Local Config Split: pipelex init now creates configuration in ~/.pipelex/ (global) by default. Use pipelex init --local to create project-level overrides in {project_root}/.pipelex/. Config loading merges: package defaults → global → project → overrides.
  • IDE Extension Detection: Extension check now uses code --list-extensions / cursor --list-extensions for reliable detection instead of folder scanning. Shows separate marketplace links for VS Code (Microsoft Marketplace) and Cursor (Open VSX Registry).
  • Quieter pipelex init: Removed verbose file listing and reset messages from config initialization output.
  • pipelex init Help Text: Detailed focus option descriptions now shown in pipelex init --help explaining each focus (all, config, credentials, inference, routing, telemetry, agreement).
  • File Extension: Pipeline definitions now use .mthds instead of .plx; the project refers to "Methods" (MTHDS) rather than "Pipelex workflows".
  • Syntax: In PipeParallel definitions, parallels field renamed to branches; plx_config in pipelex.toml renamed to mthds_config.
  • Documentation: Comprehensive update to reflect .mthds file format, package management, and new project structure.

Fixed

  • find_project_root Home Directory Bug: The project root walker no longer considers the home directory (~) as a project root, even if it contains stray marker files like package.json.
  • Python 3.10 Compatibility: Fixed datetime.UTC import (Python 3.11+) to use datetime.timezone.utc.
  • Graph Rendering: Fixed dashed edge rendering for PipeBatch and PipeParallel relationships.
  • Image Generation Response Parsing: Hardened image response parsing to handle varied provider response formats more robustly.

Removed

  • Legacy Client & Execution Modules: Removed PipelexClient, protocol files, and standalone pipelex.pipeline.execute/pipelex.pipeline.start modules (replaced by PipelexRunner and mthds package).
  • Legacy Config: Removed plx_config.py and .plx-specific configuration references.

Security

  • CSP Nonce Support: Added Content Security Policy nonce for generated HTML graph outputs.

[v0.18.0b3] - 2026-02-11

Highlights

  • Agent CLI (pipelex-agent): New machine-first CLI for AI agents with structured JSON output for all commands (build, run, validate, inputs, concept, pipe, assemble, graph, models, doctor).
  • LLM Reasoning Controls: Unified support for "Thinking" models (Chain of Thought) with reasoning_effort, reasoning_budget, and thinking_mode parameters. Supports Anthropic Extended Thinking, Google Gemini Thinking, OpenAI Reasoning (o1/o3), and Mistral/Magistral models. Includes new presets: $deep-analysis and $quick-reasoning.
  • Image-to-Image Generation: PipeImgGen now supports input images via input_images field, with InputImagesTaxonomy for provider-specific handling and variable reference detection ({{ var }}, $var, @var) in prompts.
  • PipeCompose "Construct" Mode: New construct mode for building structured objects (dictionaries/Pydantic models) directly from variables.
  • Nested concepts in inline structures: You can now define nested structures for your concepts in your .plx files. Learn more here: Nested Concepts in Inline Structures.

Added

  • Builder Auto-Repair: Self-healing capabilities including auto-generation of undeclared concepts, multiplicity mismatch fixes, and pruning of unreachable pipes/unused concepts.
  • Concept Field Features: Added choices support (compiles to Literal types/Enums) and explicit list type with item_type/item_concept_ref.
  • PipeFunc Execution Error Context: When a @pipe_func function fails during execution, the error message now includes detailed context: the function name, actual input values from working memory, expected output type, and the original error with its type. This makes debugging PipeFunc errors much easier.
  • pipelex build output CLI Command: New command to generate example output JSON for a pipe, complementing the existing pipelex build inputs command. Shows the expected output structure based on the pipe's output concept type, with multiplicity support. For pipes with native.Anything output (e.g., PipeCondition with different mapped pipe outputs), displays all possible outputs from mapped pipes.
  • Telemetry System: Introduced anonymous usage tracking and exception capture for CLI commands (graph render), reporting to both user-configured and Pipelex analytics endpoints.
  • PipeExtract Operator Validation: Added strict input validation that raises configuration errors for incompatible input types or when document-specific parameters are used with image inputs.
  • PipeCondition Output Auto-Fix in Builder Loop: The pipe builder now automatically fixes PipeCondition output concept errors during validation. If all mapped pipes have the same output, the PipeCondition output is set to that concept; otherwise it's set to native.Anything.
  • PipeFunc Return Type Validation: Added validation to ensure that a PipeFunc function's return type matches the output concept's structure class.
  • URL Validation on ImageContent and DocumentContent: Both models validate external resources (HTTP/HTTPS URLs via HEAD request, local file paths via existence check) through a validate_resources() method called during pipeline execution (validate_before_run) rather than at model instantiation. Internal URIs (data:, pipelex-storage://) skip validation entirely. Validation is skipped in dry-run mode where inputs use mock URLs.
  • Literal Type Support in Dry Run Mocks: DryRunFactory now detects Literal type annotations (including Optional[Literal[...]]) and generates valid mock values by randomly picking from the allowed choices instead of producing invalid random strings.
  • Literal Type Support in pipelex build inputs/pipelex build output: The concept representation generator now handles Literal fields by picking a random value from the allowed choices, and generates mock URL patterns for url fields.
  • Literal Error Handling in Validation Messages: Pydantic validation error formatting now recognizes literal_error types and displays them as "Invalid choice errors" with the actual value and expected options.
  • PipeRunError Catching in Bundle Validation: validate_bundle now catches PipeRunError during dry run and wraps it in ValidateBundleError with a clear message.
  • ValidationError Catching in Pipeline Execution: execute_pipeline now catches Pydantic ValidationError from input construction, formats the errors, and raises a PipeExecutionError with a clear message.
  • Broader Error Handling in CLI pipelex run: The CLI run command now catches PipelexError in addition to PipelineExecutionError, providing better error messages for failures that occur outside the pipeline execution itself.
  • Content Filenames: Added filename field to ImageContent and DocumentContent, with auto-population from local file paths via new extract_filename_from_uri helper.
  • Batch Validation Error Type: Introduced PipeValidationErrorType.BATCH_ITEM_NAME_COLLISION for naming conflicts in batch operations.
  • Documentation: Added naming convention rules to builder.plx and pipe_design.plx (batch input lists should be plural, item names singular).

Changed

  • Model Deck Updates: Default premium model now claude-4.6-opus; added Mistral models (mistral-small-3.2, mistral-large, magistral series) and gpt-5 placeholders.
  • Jinja2 Rendering: Image objects now replaced with placeholders (e.g., [Image 1]) during text generation; async filters only registered in async environments.
  • Backend Configurations: Added effort_to_level_map and effort_to_budget_maps for reasoning translation; disabled Google Vertex AI by default.
  • Dependencies: Updated pypdfium2, anthropic, and mistralai version constraints.
  • pipelex run --dry-run: No longer pretty prints the main_stuff output, matching the expected behavior for dry runs where no actual inference occurs.
  • pipelex build structures Command: Now uses a lightweight loading mechanism that only processes domains and concepts, skipping pipe loading and validation entirely. This fixes the chicken-and-egg problem where structure generation would fail due to pipe validation errors before the structures were even created. Added --force / -f flag to regenerate all structures without checking if classes already exist.
  • Test Profile System: Refactored integration tests to use a new configuration system (.pipelex/test_profiles.toml) with dev, ci, and full profiles for controlling which AI models are used in parametrized tests, replacing runtime filtering and hardcoded model lists.
  • pipelex run --graph Flag: Now acts as an override for pipelex.toml settings instead of defaulting to true.
  • Default Image Generation Models: Updated in base_deck.toml: base-img-gen: flux-2-pro, best-img-gen: nano-banana-pro, fast-img-gen: gpt-image-1-mini
  • Remote Configuration: Updated service URL to version 3.
  • GatewayExtractWorker: Now checks model capabilities before attempting image captioning.
  • Change the output validation of PipeCondition: If all mapped pipes have the same output concept, PipeCondition's output MUST be that same concept. If mapped pipes have different output concepts, PipeCondition's output MUST be the native concept Anything.
  • CLI: Changed pipelex validate all to pipelex validate --all (or -a).
  • StructuredContent.rendered_html(): Now recursively calls rendered_html() on nested StuffContent fields instead of using json2html conversion. Also skips None values and uses HTML table format.
  • Batch Pipe Validation: Enforced stricter naming rules for batch specs—input_item_name must differ from input_list_name and not shadow existing input keys, with clearer error messages suggesting plural/singular conventions.

Fixed

  • Helpful Error for get_stuff_as(ListContent[T]): When users incorrectly call get_stuff_as("name", ListContent[Something]) instead of get_stuff_as_list("name", Something), the error message now explicitly suggests using get_stuff_as_list().
  • PipeFunc ListContent[T] Validation: Fixed validation rejecting valid ListContent[T] return types for array outputs (T[]). Previously, a function returning ListContent[Expense] would fail validation for output = "Expense[]" with a misleading error. The validation now correctly extracts and validates the generic type parameter from Pydantic's metadata.
  • PipeFunc Class Name Matching: Fixed validation failing when the return type class and concept structure class are logically the same but loaded from different contexts. The validation now uses class name matching as a fallback, allowing ListContent[Expense] to match Expense[] even if the Expense class objects differ.
  • Fixed PipeImgGen not properly converting ImageContent to custom subclasses (e.g., Receipt(ImageContent)). The pipe now uses smart_dump() before model_validate() to correctly instantiate the output concept's structure class.
  • Corrected output directory creation logic in pipelex run to properly respect the --no-graph flag and configuration settings.
  • Fixed a bug when trying to print HTML content in a TextContent object.
  • Fixed the Pipelex CLI for generating structures, inputs, runner files.
  • Fixed @pipe_func decorated functions showing "function not found" instead of explaining why the function is ineligible (e.g., missing return type annotation).
  • Fixed PipeLLM with list output (e.g., output = "Item[]") not producing ListContent when run inside a nested PipeSequence with batch_over.
  • Duplicate Pipe Error Message: When a pipe code is declared in multiple .plx files (or twice in the same file), the error message now shows which bundle file(s) contain the conflicting declarations instead of a misleading message about "running the same pipe twice in the same pipeline".
  • Fixed pipelex build runner and pipelex build inputs generating string placeholders (e.g., "number_int | float") instead of numeric values for Number concepts with int | float union type fields.
  • Fixed structure generation failing with PydanticUserError when a concept structure references native concepts (e.g., native.Html). The generator now properly resolves native concept refs to their content classes (e.g., HtmlContent) with correct imports.

Removed

  • pipelex kit Command: The kit commands have been removed from the main CLI. They are now internal tools for Pipelex contributors only, available via pipelex-dev kit rules.
  • pipelex kit migrations Command: Removed entirely.
  • pipelex kit remove-rules Command: Removed entirely.
  • PLX Syntax Agent Rules: Removed write_pipelex.md and run_pipelex.md agent rules. These PLX syntax guides are no longer installed in client projects.
  • [pipelex.kit_config] Configuration: Removed from client project configuration (.pipelex/pipelex.toml).

[v0.18.0b2] - 2026-01-20

Highlights

  • Pipelex Gateway — The deprecated pipelex_inference backend is now replaced by pipelex_gateway, featuring remote model configuration fetching so you always have access to the latest models without updating Pipelex.

Getting your API key: 1. Get your API key at app.pipelex.com 2. Add it to your .env file: PIPELEX_GATEWAY_API_KEY=your-key-here

Gateway Supported models — included with the Free API Key - Language Models (LLM): - OpenAI: all models up to GPT-5.2 and Codex - Anthropic: Claude 3.7 Sonnet through Claude 4.5 Haiku/Sonnet/Opus - Google: Gemini 2.0/2.5 Flash, Gemini 2.5 Pro, Gemini 3.0 Flash/Pro - xAI: Grok 3/mini, Grok 4 (+ fast reasoning variants) - Open-source: Mistral Large 3, DeepSeek v3.1/3.2/Speciale, GPT-OSS 20B/120B1, Kimi K2, Phi-4, Qwen3-VL 235B - Document Extraction: Mistral Document AI, Azure Document Intelligence, DeepSeek OCR - Image Generation: GPT-Image 1/1.5, Flux 2 Pro, Nano Banana/Pro

Accepting the Terms of Service:

When you run pipelex init, you'll be prompted to accept the Gateway terms of service. By using Pipelex Gateway, telemetry is automatically enabled and identified by your API key (hashed for security) to monitor service quality and enforce fair usage. We only collect technical data (model names, token counts, latency, error rates)—never your prompts, completions, or business data. See our Privacy Policy.

⚠️ Migration deadline: If you were using pipelex_inference, please migrate soon—the legacy service will be shut down within few days. Get your new Gateway key at app.pipelex.com.

  • Execution Graph Visualization System (preview feature) — Comprehensive tracing and visualization for pipeline executions.

CLI: --graph flag on pipelex run generates execution graphs. New pipelex graph render <graph.json> command for post-run rendering.

Viewers: Interactive ReactFlow viewer (reactflow.html) with pan/zoom and node inspector. Mermaid diagrams (mermaidflow.html) with subgraphs and clickable nodes.

Makefile: make view-graph (vg) and make serve-graph (sg) to start a local graph viewer.

  • Pydantic Structure Generation — Two new CLI commands bridge Pipelex's declarative concepts with your Python code:

pipelex build structures /library_dir/ — Generates Pydantic models from all concept definitions found in the specified library directory. Now you have your structures as Python code: you can iterate on them, add custom validation functions, or use them as type hints in your code.

pipelex build runner — Now automatically generates both the Python runner file AND the required Pydantic structures. When you run this command, it creates a complete, ready-to-execute Python script that imports the generated structures, so you can immediately use typed objects in your pipeline code.

See the Build Commands documentation for usage examples.

  • New Backends & Models:
  • Hugging Face Inference — Support for Hugging Face Inference API, including qwen-image text-to-image model.
  • Google gemini-3.0-flash-preview
  • Mistral OCR latest model mistral-ocr-2512
  • Scaleway inference provider support for open-source models
  • Portkey AI backend integration for unified access to multiple models through a single API key

  • Document Support in PipeLLM — Include Document objects (like PDFs) directly in prompts using @variable or $variable syntax. Supports single documents, multiple documents, and lists, combinable with text and image inputs.

  • PipeCompose Construct Mode — New mode for deterministically building StructuredContent objects without an LLM. Compose fields from working memory variables, fixed values, templates, and nested structures.

  • Content Storage System — Configurable storage for generated artifacts (images, extracted pages). Supports local filesystem (.pipelex/storage/), in_memory, AWS S3 (pip install pipelex[s3]), and Google Cloud Storage (pip install pipelex[gcp-storage]). Cloud providers support both public URLs and time-limited signed URLs. Content referenced via stable pipelex-storage:// URIs.

  • Langfuse & OpenTelemetry Observability — New OpenTelemetry-based observability system enables powerful tracing and Evals through Langfuse integration. Also supports OTLP-compatible backends (Datadog, Honeycomb, etc.). Configured via .pipelex/telemetry.toml.

  • Python 3.14 Support — Officially tested and supported.

Added

  • --library-dir CLI Option: --library-dir / -L option for pipelex run, pipelex validate, and pipelex build subcommands (one-shot-pipe, partial-pipe) to specify additional directories for searching pipe definitions. Can be specified multiple times.
  • Automatic File Loading: The core pipeline execution functions (pipelex.execute_pipeline, pipelex.start_pipeline) can now directly load a pipeline from a file path via a new bundle_uri parameter.
  • Dry Run Mode: pipelex run --dry-run executes pipeline logic without API calls, useful for validating structure and generating orchestration graphs. Combine with --mock-inputs to generate mock data for missing required inputs.
  • | with_images Jinja2 Filter: Explicitly extract and include all nested images from complex data structures (e.g., Page objects or custom concepts with Image fields). Renders the object's text representation while making associated images available to the LLM.
  • System Prompt Media Support: Reference images and documents in system_prompt using the same $variable and @variable syntax as the user prompt.
  • Pipelex Gateway Service: Terms of service management via .pipelex/pipelex_service.toml and interactive acceptance flow in pipelex init and pipelex init agreement.
  • Gateway Available Models Documentation: Auto-generated reference of all LLM, Document Extraction, and Image Generation models available through the Gateway.
  • Configurable Retry Logic: Exponential backoff for inference API calls, configurable in pipelex.toml under [cogt.tenacity_config]
  • Context Manager Support: The Pipelex class now supports with Pipelex.make(): ... for graceful shutdown
  • Validation Improvements:
  • Pipelex Bundle concept keys: prevent bundles from re-creating a native concept
  • PipeSequence: output multiplicity must match the last step's output multiplicity
  • PipeFunc: output multiplicity must match the function return type (ListContent subclass for multiplicity=true)
  • Rendering Protocols: Three new @runtime_checkable protocols (ImageRenderable, TagRenderable, TextFormatRenderable) to formalize the interaction between data types and Jinja2 filters.
  • Unified URI Handling System: New pipelex.tools.uri module providing type-safe parsing for HTTP/HTTPS URLs, local file paths, file URIs, pipelex-storage:// URIs, and base64 data URLs.
  • Automatic Input Data Storage: Pipeline pre-processing step that converts large data: URLs in an ImageContent or DocumentContent into pipelex-storage:// URIs for improved performance, by storing the data with the configured storage provider. Configurable via is_normalize_data_urls_to_storage in pipelex.toml.
  • pipelex build inputs Command: New CLI command to generate example input JSON files for pipes. Supports --library-dir (-L) to specify library directories.
  • Pipe Code Syntax Validation: Bundle validation now checks that pipe codes and main_pipe values use valid snake_case syntax, with proper error categorization (INVALID_PIPE_CODE_SYNTAX).

Fixed

  • Nested Image Handling: Images nested within structured data are now properly replaced with [Image N] tokens. The | with_images filter and ImageRegistry system correctly extract images from complex nested structures.
  • PipeCondition Validation: Output multiplicity validation now works correctly.
  • Error Reporting: PipeCompose validation errors now include formatted details and failing field values.
  • Duplicate Pipeline Registration: Running a pipeline from a file that was also part of a pre-loaded library (via PIPELEXPATH) no longer causes a duplicate domain registration error. The system now tracks absolute paths of loaded library files and skips files already loaded.
  • pipelex build structures: Corrected output file naming and resolved import path generation.
  • ConceptFactory.make_from_blueprint: Now correctly handles native concepts.

Changed

  • Jinja2 Integration Refactored to Protocol-Based Approach: Replaced the Jinja2Registry singleton and handler functions with a decoupled protocol-based system, eliminating circular dependencies between the template layer and core domain logic.
  • StuffArtefact Redesigned as Delegation Adapter: Now a lightweight, immutable adapter that delegates attribute access directly to underlying Stuff and StuffContent objects, improving performance and providing more intuitive field access.
  • Image Extraction Moved to Content Types: StructuredContent, ListContent, ImageContent, and TextAndImagesContent now implement the ImageRenderable protocol, replacing centralized handler logic.
  • ⚠️ Breaking: User override config renamed from pipelex_super.toml to pipelex_override.toml.
  • Image Generation Architecture: Refactored to taxonomy-based approach. Standardizes parameter translation (aspect_ratio, quality, output_format) to provider-specific APIs.
  • Document Extraction Improvements: pypdfium2 extractor now extracts embedded images from PDFs. Response parsing uses dedicated Pydantic schemas for validation.
  • Default Model Change: extract_text_from_visuals deck now defaults to azure-document-intelligence
  • pipelex_inference replaced by pipelex_gateway: See Highlights for migration details. New PIPELEX_GATEWAY_API_KEY environment variable; default routing profiles updated to pipelex_gateway_first.
  • Telemetry System Split: Now two separate streams: 1. Pipelex Gateway telemetry for service monitoring (never collects prompts/completions/business data) 2. Custom telemetry to user-configured backends 3. Config updated accordingly (telemetry.toml):

    • Renamed [posthog] to [custom_posthog] to distinguish user's PostHog from Pipelex Gateway telemetry
    • Added new [custom_portkey] section with force_debug_enabled and force_tracing_enabled settings
  • Main Configuration Overrides Updated (.pipelex/pipelex.toml):

  • pipelex_override.toml (final override) renamed from pipelex_super.toml to pipelex_override.toml and moved from repo root to .pipelex/ directory
  • telemetry_override.toml (personal telemetry settings)
  • is_generate_cost_report_file_enabled default changed from true to false

  • Documentation:

  • Clarified Setup (first run) vs Configuration (TOML reference), added a Setup overview page, and added contributor docs for configuration defaults/overrides.
  • Added the "Under the Hood" page documenting the execution graph tracing system.
  • pipelex init: Now creates a documented telemetry.toml template instead of prompting for preferences
  • Model Catalog Updated: Latest models (gpt-5.1, claude-4.5-opus, gemini-3.0-pro, etc.) and updated waterfalls in base_deck.toml
  • Model Constraints Refactored: From simple lists to structured valued_constraints dictionaries (e.g., valued_constraints = { fixed_temperature = 1 })
  • OpenAI Responses API: New implementation now differentiates between openai_completions and openai_responses
  • CLI Initialization: Commands refactored to use centralized Pipelex initialization factory for improved error handling
  • pipelex doctor: Enhanced to detect outdated telemetry.toml formats and suggest fixes
  • --output-dir Option: The runner, structures, and inputs CLI commands now accept this option
  • Cost Report: Now displays a note clarifying that it only includes LLM costs
  • description Field Now Required: In PipeAbstract, PipeBlueprint, and PipeSpec classes.
  • Configuration: New [pipelex.pipeline_execution_config.graph_config] section in pipelex.toml for fine-grained control over graph generation, data embedding, and rendering options.
  • CLI: All pipelex commands now accept --no-logo to suppress the Pipelex banner in the terminal — useful to reduce tokens.

Removed

  • openai_utils Module: Removed pipelex.plugins.openai.openai_utils; logic now in centralized image preparation utilities.
  • Pipeline Tracking feature: Removed entirely, including the pipelex/pipeline/track module, PipelineTracker components, related configuration, tracker calls in pipe controllers, and associated documentation.
  • Flow Generator: The old flow generator has been removed.

Deprecated

  • pipelex_inference backend in favor of pipelex_gateway (marked as "🛑 Legacy" in configuration template)

For Contributors

  • Technical Documentation: Added a new "Under the Hood" page documenting the StuffArtefact delegation pattern and image rendering architecture.
  • Enhanced Testing: Added extensive unit and integration tests for the protocol-based rendering system, including nested image extraction and filter error conditions.
  • Agent Rules: Added pipelex_standards.md outlining standards for the Pipelex configuration system, also included as rules for AI development agents.
  • Agent Rules: New target make agent-check for faster linting.
  • ⚠️ Breaking — Content Handling Overhaul: GeneratedImage replaced by internal GeneratedImageRawDetails; ImageContent is now standard (without base_64 field). PipeExtract outputs PageContent list directly. Content persistence now handled automatically by storage system.
  • ⚠️ Breaking — Image Prompt Representation: Redesigned PromptImage models—consolidated PromptImagePath and PromptImageUrl into PromptImageUri; now uses Pydantic discriminated union for URI, base64, and raw bytes sources.
  • Centralized Image Preparation: Moved image fetching and base64 conversion logic to pipelex.cogt.image.prompt_image_utils, simplifying LLM provider plugins (Anthropic, Google, Mistral, OpenAI).
  • Unified Resource Loading: Updated all file/URL reading components (PDF renderers, document extractors) to use the new URI handling system, replacing the legacy pipelex.tools.misc.path_utils module.
  • Async HTTP Fetching: Renamed fetch_file_from_url_httpx_async to fetch_file_from_url_httpx; removed redundant synchronous version.
  • PreparedImage Abstraction: New models (PreparedImageHttpUrl, PreparedImageBase64) representing images ready for LLM provider APIs.
  • Pipelex Gateway Model Management: New CLI commands (pipelex-dev update-gateway-models, pipelex-dev check-gateway-models) and corresponding make targets (ugm, cgm) to generate and verify gateway model catalog. CI now validates this documentation is up-to-date.
  • Test Suite Pre-flight Check: Verifies Gateway terms acceptance before running tests, providing clear error messages.
  • Content Rendering: StuffContent.rendered_* methods now provide both synchronous and asynchronous variants.

Migration Notes

  • Telemetry configuration migration: If you have an existing telemetry.toml, rename:
  • [posthog][custom_posthog]
  • [posthog.tracing][custom_posthog.tracing]
  • [posthog.tracing.capture][custom_posthog.tracing.capture]
  • Or run pipelex init telemetry --reset to regenerate the file with the new structure

Refactored

  • Anthropic Backend: Internal streaming for standard completions to prevent SDK timeouts, configurable structured output timeout (structured_output_timeout_seconds), improved error mapping, and increased Bedrock Claude max_tokens (8K → 64K) with removal of max_output_tokens_limit constraint.
  • ⚠️ Breaking — Pipe I/O Specification: The output (and inputs) of a pipe is now a StuffSpec object that holds the concept and the multiplicity.
  • Naming Convention: Renamed domain to domain_code where relevant.
  • Dry Run Methods: Refactored the dry run methods of the PipeAbstract class.

[v0.17.6] - 2026-02-14

Added

  • Claude Code GitHub Actions: Added claude.yml workflow for interactive Claude Code assistance on issues and PR comments, and claude-code-review.yml workflow for automated code review on pull requests.

[v0.17.5] - 2026-01-16

  • Added target make docs-deploy-404 to deploy the 404.html file to the gh-pages root for versionless URL redirects.

[v0.17.4] - 2026-01-16

Added

  • Added the mike dependency to support mutiple docs versions. version plugin added to the MkDocs configuration, make targets and CI scripts.

[v0.17.3] - 2025-12-01

Fixed

  • Fixed the issue with the find_files_in_dir force including virtual environment directories: Now it force includes the pipelex.builder directory.
  • Fixed a bug with the comparison of Concept structures.

[v0.17.2] - 2025-12-01

Added

  • New AI models support: Added GPT-5.1, Claude 4.5 Opus, and Gemini 3 Preview to the available models.
  • Codex Cloud support: Added support for running Pipelex in Codex Cloud environments with appropriate configuration and testing capabilities.
  • Enhanced file discovery: Added force_include_dirs parameter to the find_files_in_dir function. This allows specific directories to be force included in the search even when they are nested within excluded directories. For example, you can now exclude .venv while still including .venv/lib/python3.11/site-packages/pipelex for loading Pipelex libraries from installed packages.
  • Added validation of the PipeLLM inputs at the blueprint level.
  • Added a xfailed test for PipeCondition: if one of the outcome of the pipe is continue, it does nothing, but the main stuff still points towards the last step. Therefore when trying to get the main stuff out of the working memory as a specific type, it fails.

Changed

  • Backend fallback now only activates when explicitly opted-in, giving users more control over model selection.
  • Renamed and improved the Azure Image Generation SDK implementation.
  • Enhanced language spec examples, operator details, and added Viewpoint documentation.

Fixed

  • Fixed kit rules to be idempotent and work correctly across multiple executions.

[v0.17.1] - 2025-11-27

Fixed

  • Fixed a bug in the find_files_in_dir function.

[v0.17.0] - 2025-11-27

Highlights

  • Previously, in the pipelex config files (.toml files in the .pipelex/ directory, such as .pipelex/pipelex.toml, but also the routing profiles files, backends, etc.), when an array was overridden, the new array was concatenated to the old array. Now, the new array overrides the old array.

Fixed

  • Relaxed concept structure field naming restrictions: Users can now use field names like content, stuff_code, stuff_name, and concept in their concept structures without conflicts. Internal metadata fields in stuff artefacts now use underscore prefixes (_stuff_name, _content_class, _concept_code, _stuff_code, _content) to avoid collisions with user-defined fields. Reserved field names (Pydantic BaseModel attributes like model_config, model_fields, etc.) and field names starting with underscore remain forbidden with improved error messages that clearly specify which fields are problematic.

Changed

  • Modified the GHA version-check.yml so that the check of the version is only applying to release branches.
  • Removed the pyproject.toml file from the build.
  • No more implicit concepts. A concept reference has a domain and a code. If there is no domain, it should be a native concept, or it is declared in the same bundle.

Refactored

  • The find_files_in_dir function was coded in 3 different places, now it's in pipelex/tools/misc/file_utils.py, and accepts excluded_dirs.
  • Refactored the Pipe factories: Centralized everything in the PipeFactory class.

[v0.16.0] - 2025-11-25

Highlights

  • Library manager now supports multiple libraries. You can now have multiple libraries in your project, each with its own set of concepts, pipes, and stuffs. You can run the same pipe at the same times as much as you want, with different inputs. Side effets: Unit tests now run in 30s.

Fixed

  • Fixed some issues with inputs of pipes: The validation methods was not detecting misconceptions with implicit concepts.

Changed

  • Improved pipe builder by auto-fixing errors, forcing consistency in the inputs and outputs of the pipes.

Refactor

  • PipeCondition: Moved the expression/expression_template choosing to the factory.
  • Moved a lot of validation to blueprints instead of pipe instances.
  • Refactored the Blueprint validation errors, and validation functions.
  • Refactored the PipelexInterpreter validation errors.
  • Refactored the pipe builder validation loop.
  • Reorganized the unit tests, and added new ones.
  • Reorganized the config files.
  • Refactored methods execute_pipeline and start_pipeline.
  • Moved dev_cli to cli.dev_cli.

[v0.15.7] - 2025-11-18

Fixed

  • Fixed issue with get_console() function returning None if Pipelex is not initialized. Now always defaults to stderr if not set.

[v0.15.6] - 2025-11-18

Contributors

  • Welcome to our new contributor @0x090909 (yup, that's his github username) for his work on Groq support in PR #445! 🎉

Added

  • Improved configuration repair: New --fix option for pipelex doctor command that interactively detects and repairs outdated or invalid backend configuration files using latest templates from the Pipelex kit.
  • Developer CLI & tooling: New internal pipelex-dev CLI for project maintenance with check-config-sync command to verify user-facing configuration templates (.pipelex/) are synchronized with package's internal kit configs. Includes make check-config-sync command and CI check (lint-check.yml) to enforce synchronization.
  • Enhanced test infrastructure: Integration tests now automatically parameterized to run against all supported backend routing profiles. Tests are intelligently skipped at collection time if a model is not supported by the active backend profile, with a summary of skipped tests provided at session end.
  • New routing profiles: Added all_groq and all_pipelex_inference routing profiles.
  • Vision support flag: Added is_vision_supported property to LLMWorkerAbstract class for explicit checks of model vision capabilities.
  • New type of StuffContent: JSONContent to support an arbitrary JSON object as input or output of a pipe.
  • Azure image generation: Support for image generation models via Azure OpenAI backend using gpt-image-1.

Changed

  • Unified structured output: Complete overhaul of structured generation settings. Replaced global configuration setting with new structure_method parameter in backend .toml files (configurable at backend level in [defaults] or per individual model). Expanded StructureMethod enum to include dozens of modes supported by instructor, enabling fine-grained control over provider-specific features like OpenAI Structured Outputs or Anthropic Tools, and various JSON-based modes.
  • Groq integration: Updated to use standard openai SDK, simplifying integration.
  • Default configurations: All official backend providers now enabled by default after pipelex init. Default prompting style changed from ticks to xml.
  • Code & test organization: Unit test suite reorganized from tests/unit/core and other directories into unified tests/unit/pipelex/ structure. Integration test fixtures modularized from conftest.py into separate files within tests/integration/pipelex/fixtures/.
  • Console output settings: Added console_print_target and console_log_target settings in pipelex.toml for redirecting output to stdout or stderr, with CLI and logging refactored to use centralized console instance. This makes it easier to support MCP communication based on stdio.

Fixed

  • Improved Pydantic validation error messages when loading backend configurations to clearly indicate the specific file and model containing the error.

Removed

  • Perplexity backend: Default configuration for Perplexity AI backend (perplexity.toml) removed from kit (it was obsolete, it will come back).
  • Groq plugin: Dedicated pipelex/plugins/groq plugin removed (now uses standard openai SDK).
  • Global instructor config: Global is_openai_structured_output_enabled setting, replaced by per-model structure_method approach.

[v0.15.4] - 2025-11-12

Added

  • Enhanced pipelex build Command: Now generates a self-contained directory (e.g., results/pipeline_01/) containing bundle.plx, inputs.json, run_{pipe_code}.py, bundle_view.html, and bundle_view.svg. New CLI options: --output-name (-o) for custom base name, --output-dir for custom directory, and --no-extras to generate only the .plx file.
  • CLI Readiness Check: Verifies that a virtual environment is active for development installations.
  • Model Deck Presets: Added llm_for_creativity and cheap_llm_for_creativity model waterfalls, plus [cogt.model_deck_config] section in pipelex.toml for configuring model fallback behavior.
  • WIP: Groq Inference Backend Support: Integrated full support for the Groq API with configuration file (.pipelex/inference/backends/groq.toml), model specifications, costs, capabilities, new model aliases (base-groq, fast-groq, vision-groq), and routing profile (all_groq).

Changed

  • CLI Output and Visualization: Overhauled command-line output with rich, table-based layouts for pipeline components. Final output of pipelex run is now pretty-printed and adapts to content type.
  • Documentation: Updated "Get Started" and "Build Reliable AI Workflows" to reflect new directory-based build output and CLI options.
  • Internal Code Refactoring: Reorganized exception hierarchy into dedicated exceptions.py files per module, centralized validation logic into validation.py modules, added ValueError to blueprints, and removed unused exceptions for improved maintainability.
  • Updated pytest to >=9.0.1 to support their new pyproject.toml config format.

Fixed

  • Adjusted default temperature for llm_for_testing_gen_object preset from 0.5 to 0.1 for more deterministic structured data generation.
  • Corrected LLM_FOR_VISUAL_DESIGN skill in pipe_llm_spec to point to cheap_llm_for_creativity preset.
  • Standardized input variable names in pipe_llm_vision.plx from imageA/imageB to image_a/image_b.

Removed

  • Deleted pipelex/core/validation_errors.py file as part of exception hierarchy refactoring.

[v0.15.3] - 2025-11-07

Fixed

  • Fixed weird import issues with posthog and StrEnum

[v0.15.2] - 2025-11-07

Fixed

  • Fixed resetting routing profile when calling with --reset flag in pipelex init

[v0.15.1] - 2025-11-07

Fixed

  • Bumped OpenAI dependency to >=1.108.1 to support their breaking change: "change optional parameter type from NotGiven to Omit"
  • get_selected_backend_keys() now correctly considers backends enabled by default (like before v0.15.0)

[v0.15.0] - 2025-11-07

Highlights

This release dramatically simplifies onboarding with interactive CLI setup, comprehensive documentation relaunch, and intelligent model fallbacks, making Pipelex more accessible and resilient than ever.

Added

  • Model Waterfalls: Define prioritized model lists in base_deck.toml (e.g., smart_llm = ["gpt-4o", "claude-4.5-sonnet", "grok-3"]). Pipelex automatically falls back to the next model if the preferred one is unavailable.
  • Advanced Routing Profiles: New capabilities in routing_profiles.toml: fallback_order (Global fallback sequence specifying which backends to try if a model isn't found) and optional_routes (Routes that activate only when their target backend is enabled)
  • New Models: Anthropic claude-4.5-haiku (Pipelex Inference, Anthropic, and Bedrock backends) and Azure OpenAI o3
  • Comprehensive Documentation Relaunch: Complete restructure under /home/ with new "Get Started" guides for pipelex build and manual workflows, plus in-depth sections on Domains, Bundles, Concepts, and Pipe lifecycle.
  • Enhanced CLI: pipelex init now interactively guides backend selection and automatically configures routing profiles, including primary backend and fallback order. Added pipelex init routing focus.
  • Enhanced CLI: Improved error reporting across all commands (build, validate, run, show) with clear, actionable feedback for configuration errors, missing models, and invalid presets.
  • Enhanced CLI: pipelex doctor now validates model deck configuration.
  • New Routing Profiles: Full suite of all_* profiles (e.g., all_openai, all_anthropic, all_google) to route all requests to a single provider.

Changed

  • BREAKING: for inline concept structures, the fields are now optional by default: the required property defaults to false. Explicitly set required = true to make fields mandatory, which we discourage as it increases risks of hallucinations.
  • LLM Presets Overhaul: Rationalized and renamed default presets in base_deck.toml. Single-model aliases replaced with waterfall aliases. Key renames: llm_for_complex_reasoningengineering-structured, llm_to_answer_hard_questionsllm_to_answer_questions, llm_to_write_questionsllm_for_writing_cheap. Removed redundant older presets.
  • Stricter Configuration Validation: Pipelex validates model deck on startup and raises errors if presets reference unavailable models.

Fixed

  • Local OpenAI-Compatible Endpoints: OpenAI plugin now handles empty API keys, enabling seamless integration with local servers like Ollama.

Removed

  • Old Documentation Structure: Previous /pages/ directory documentation removed in favor of new structure.

[v0.14.3] - 2025-10-29

Added

  • Image generation models via BlackBoxAI backend: flux-pro, flux-pro/v1.1, flux-pro/v1.1-ultra (Black Forest Labs), fast-lightning-sdxl (ByteDance), and nano-banana (Google). Implemented using new openai_alt_img_gen SDK worker with chat completion-style API.
  • Language model: claude-4.5-sonnet (Anthropic) via BlackBoxAI backend.
  • Routing profile: all_blackboxai profile routes all supported model requests to BlackBoxAI backend.

Changed

  • Model aliases in base_deck.toml: base-img-genflux-pro/v1.1-ultra, best-img-gennano-banana, llm_for_large_codebase now includes claude-4.5-sonnet.
  • Configuration file: BLACKBOX_RULES.md renamed to .blackboxrules.

Fixed

  • Image generation schema: ImgGenJobParams.seed field now explicitly defined with default=None.
  • CLI bundle validation: pipelex validate command now accepts bundle path (.plx file) which are in the package and already loaded and performs dry run on all pipes in the bundle.

[v0.14.2] - 2025-10-29

Chaged

  • Improved pipe builder.

Added

  • CLI to generate inputs JSON.

[v0.14.1] - 2025-10-27

Added

  • Tutorial GIF on the README.md file.

[v0.14.0] - 2025-10-27

Added

  • pipelex doctor command: Diagnoses and fixes common configuration issues including missing files, invalid telemetry settings, and unset environment variables for enabled backends.
  • Interactive backend selection in pipelex init: Multi-select menu for enabling/disabling inference backends (OpenAI, Anthropic, Amazon Bedrock, etc.).
  • JSON input support: pipelex run --inputs flag accepts a JSON file path for passing structured data to pipelines.
  • pretty_print methods: Added to PipeSpec, ConceptSpec, and Stuff objects for readable debugging output.
  • VS Code debug configuration: "Debug run pipe" launch configuration for debugging pipeline executions.
  • display_name attribute: Added to all inference backends in backends.toml for better UI presentation.
  • Documentation headers: All default .toml configuration files now include headers with links to documentation and support channels.

Changed

  • pipelex init redesign: Transformed into a unified, interactive setup wizard with rich terminal UI for configuration files, backend selection, and telemetry preferences. Telemetry is now configured here instead of via first-run prompt.
  • README.md rewrite: Complete overhaul featuring a simplified 5-step quick-start guide highlighting the pipelex build command.
  • Documentation updates: "Quick Start" guide renamed to "Writing Workflows" with simplified content. Python examples updated to use JSON input method, removing manual Stuff and WorkingMemory object creation boilerplate. Developer guides and AI assistant rules now recommend pipelex validate over make validate. Added instructions emphasizing .venv activation before running commands.
  • Error handling improvements: Pipelines now validate required inputs upfront and fail early with PipeRunInputsError. pipelex run prints full rich-formatted exception tracebacks on error.
  • Default enabled backends: Amazon Bedrock, Google AI, and Google Vertex AI are now enabled by default.
  • Naming consistency: "AWS Bedrock" renamed to "Amazon Bedrock" throughout codebase, configuration, and documentation.

Fixed

  • Some documentation links were broken.

[v0.13.2] - 2025-10-25

Added

  • Added the n8n documentation page for the n8n-nodes-pipelex package.
  • Added optional telemetry system with first-run interactive prompt offering three modes: off (no data collected), anonymous (usage data without identification), and identified (usage data with user identification). Automatically respects DO_NOT_TRACK environment variable and redacts sensitive data (prompts, responses, file paths, URLs). Configuration stored in .pipelex/telemetry.toml.
  • Added telemetry documentation: user-friendly setup guide and comprehensive configuration reference.

Changed

  • Updated the PipelexClient and changed the route of the API calls to v1/pipeline/execute and v1/pipeline/start.
  • Changed the parameter input_memory to inputs in the documentaton.

[v0.13.1] - 2025-10-22

Changed

  • Changed the pydanticdependency from ==2.10.6 to >=2.10.6,<3.0.0 to avoid compatibility issues.

[v0.13.0] - 2025-10-21

Highlights

This release focuses on making Pipelex more accessible and easier to use, with major improvements to the CLI, simplified syntax for multiplicity, and a complete documentation overhaul:

  • New CLI commands: Run pipelines directly with pipelex run, generate Python runners with pipelex build runner, and inspect your AI backend configuration with pipelex show backends
  • Simplified pipeline inputs: The new inputs parameter replaces input_memory and accepts strings, lists, or content objects directly - no more complex dictionary structures
  • Getting started faster: Completely rewritten quick-start guide and new documentation sections help you go from installation to your first pipeline in minutes

Added

  • CLI command pipelex run: Top-level command to execute pipelines directly from the CLI. Can run pipes from the package or from any .plx bundle file, with options to provide inputs from a JSON file and save the output
  • CLI command pipelex build runner: Generates Python script with imports and example input structures for any pipe
  • CLI command pipelex show backends: Displays configured AI providers, their status, and active routing rules
  • Model presets: Added task-oriented presets including llm_to_write_questions, llm_to_code, llm_for_basic_vision, llm_for_visual_analysis
  • Documentation: Complete quick-start guide rewrite, new guides for "Understanding Multiplicity", "API Guide", "Executing Pipelines with Inputs", and updated README with video demo
  • Migration guide: Updated guide at pipelex/kit/migrations/migrate_0.11.0_0.12.x.md

Changed

  • Unified bracket notation for multiplicity: Single items use "Concept", variable lists use "Concept[]", fixed-count lists use "Concept[3]". Applies to both inputs and output fields in .plx files
  • Pipeline input format: input_memory parameter renamed to inputs; now accepts strings, lists of strings, StuffContent objects, or explicit concept dictionaries instead of CompactMemory
  • Bundle main_pipe attribute: Pipelex bundles (.plx files) now support a main_pipe attribute to designate the primary entry point of the bundle. Used by pipelex run and pipelex build runner commands to simplify execution
  • Model preset names: llm_to_reasonllm_for_complex_reasoning, base_ocr_mistralextract_text_from_visuals, base_extract_pypdfium2extract_text_from_pdf, base_img_gengen_image_basic, fast_img_gengen_image_fast, high_quality_img_gengen_image_high_quality
  • Unified model parameter: PipeExtract and PipeImgGen now use model parameter for consistency across all operator pipes
  • PipeExtract operator: Output is now consistently validated to be the Page concept, simplifying its usage for document processing
  • CLI improvements: pipelex run and pipelex validate now auto-detect pipe code vs .plx bundle files; pipelex validate promoted to top-level command with improved error reporting and syntax-highlighted code snippets
  • CLI reorganization: Main command-line interface restructured for better usability with improved help texts and more logical command order
  • Python API: Pipelex.make() now accepts dependency injection arguments directly
  • Python coding standards: Updated internal coding standards to recommend declaring variables with a type but no default value to better leverage linters for bug detection
  • Default configuration: Azure and AWS inference backends now disabled by default in template configuration

Fixed

  • Structure generation: Special characters (double quotes, backslashes) in concept field descriptions or default values no longer produce invalid Python code

Removed

  • Legacy multiplicity syntax: nb_output, multiple_output parameters, and complex input dictionary syntax with multiplicity field
  • Pipe-specific model parameters: ocr parameter from PipeExtract and img_gen parameter from PipeImgGen
  • prompt_template_to_structure and system_prompt_to_structure configurations at the pipe and domain level
  • Project Name discovery from Configuration
  • Temporary design document for the new inference backend system (feature now fully implemented and documented)

[v0.12.0] - 2025-10-15

Highlights

Moving fast and breaking things:

  • Added the new builder pipeline system for auto-generating Pipelex bundles from user briefs
  • it's a pipeline to generate pipelines, and it works!
  • the pipeline definitions are in pipelex_libraries/pipelines/base_library/builder/
  • removed the previous draft which was named meta_pipeline.plx

Breaking changes... for good!

We tried to group all the renamings we wanted to do which impact our language, so that you get one migration to apply and then we will be way more stable in the future releases.

This is all in the spirit of making Pipelex a declarative language, where you express what you want to do, and the system will figure out how to do it. So our focus inwas to make the Pipelex language easier to understand and use for non-technical users, and at the same time use more consistent and obvious words that developers are used to.

💡 Pro tip: To make migration easier, pass the migration guide to your favorite SWE agent (Cursor, Claude Code, github copilot, etc.) and let it handle the bulk of the changes!

  • Removed centralized pipelex_libraries folder system
  • Pipelines are now auto-discovered from anywhere in your project—no special directory required
  • No config path parameters needed in Pipelex.make() or CLI commands (just call Pipelex.make())
  • Custom functions require @pipe_func() decorator for auto-discovery
  • Structure classes auto-discovered (must inherit from StructuredContent)
  • Configuration stays at repository root in .pipelex/ directory
  • See migration guide for details on reorganizing your project structure

  • General changes

  • renamed definition fields to description across all cases

  • Renamed PipeJinja2 to PipeCompose

  • the fact that our templating engine is Jinja2 is a technnical detail, not fundamental to the language, especially since we included a pre-processor enabling insertion of variables in prompts using @variable or $variable, in addition to the jinja2 syntax {{ variable }}
  • renamed jinja2 field to template for the same reason
  • for more control, instead of providing a string for the template field, you can also use a nested template section with template, category and templating_style fields

  • Renamed PipeOCR to PipeExtract

  • this is to account for various text extraction techniques from images and docs, including but not only OCR; e.g. we now have integrated the pypdfium2 package which can extract text and images from PDF, when it's actually real text (not an image), and soon we'll add support for other document extraction models solutions
  • removed obligation to name your document input ocr_input, it can now be named whatever you want as long as it's a single input and it's either an Image or a PDF or some concept refining PDF or Image
  • renamed ocr_page_contents_from_pdf to extract_page_contents_from_pdf
  • renamed ocr_page_contents_and_views_from_pdf to extract_page_contents_and_views_from_pdf
  • introduced model settings and presets for extract models like we had for LLMs
  • renamed ocr_model to model for choice of model, preset, or explicit setting and introduced base_ocr_mistral as an alias to mistral-ocr

  • PipeLLM field renames

  • image inputs must now be tagged in the prompt like all other inputs; you can just drop their names at the beginning or end of the prompt, or you can reference them in meaningful sentences to guide the Visual LLM, e.g. "Analyze the colors in $some_photo and the shapes in $some_painting."
  • renamed prompt_template field to prompt
  • renamed llm field to model
  • renamed llm_to_structure field to model_to_structure

  • PipeImgGen field renames

  • renamed img_gen field to model for choice of model, preset, or explicit setting
  • removed some technical settings such as nb_steps from the pipe attributes, instead you can set these as model settings or model presets
  • introduced model settings and presets for image generation models like we had for LLMs

  • PipeCondition field renames

  • renamed pipe_map to outcomes
  • renamed default_pipe_code to default_outcome and it's now a required field, because we need to know what to do if the expression doesn't match any key in the outcomes map; if you don't know what to do in that case, then it's a failure and you can use the fail value

  • Configuration file changes (.pipelex/ directory)

  • Renamed parameter llm_handle to model across all LLM presets in deck files
  • Renamed parameter img_gen_handle to model across all image generation presets in deck files
  • Renamed parameter ocr_handle to model in extraction presets
  • Renamed ocr section to extract throughout configuration files
  • Renamed ocr_config to extract_config in pipelex.toml
  • Renamed base_ocr_pypdfium2 to base_extract_pypdfium2
  • Renamed is_auto_setup_preset_ocr to is_auto_setup_preset_extract
  • Renamed nb_ocr_pages to nb_extract_pages
  • Updated pytest marker from 'ocr' to 'extract'

Added

  • Added cheap-gpt model alias for gpt-4o-mini
  • Added cheap_llm_for_vision preset using gemini-2.5-flash-lite
  • Added llm_for_testing_vision and llm_for_testing_vision_structured presets for vision testing
  • Added is_dump_text_prompts_enabled and is_dump_response_text_enabled configuration flags to have the console display everything that goes in and out of the LLMs
  • Added generic_templates section in llm_config with structure extraction prompts
  • Added useful error messages with migration configuration maps pin-pointing the fields to rename for config and plx files
  • Added improved error message for PipeFunc when function not found in registry, mentioning @pipe_func() decorator requirement since v0.12.0
  • Added pytest filterwarnings to ignore deprecated class-based config warnings
  • Added Flow class that represents the flow of pipe signatures
  • Added pipe-builder command flow to generate flow view from pipeline brief
  • Added FlowFactory class to create Flow from PipelexBundleSpec or PLX files
  • Added sort_pipes_by_dependencies() function for topological sorting of pipes
  • Added pipe_sorter.py module for pipe dependency sorting utilities
  • Added search_for_nested_image_fields_in_structure_class() method to Concept class
  • Added image_field_search.py module with utilities to search for image fields in structure classes
  • Added pipe_dependencies property to PipeBlueprint and controller blueprints
  • Added ordered_pipe_dependencies property to PipeBlueprint for ordered dependencies
  • Added get_native_concept() function to hub
  • Added get_pipes() function to hub
  • Added remove_concepts_by_codes() method to ConceptLibraryAbstract
  • Added remove_pipes_by_codes() method to PipeLibraryAbstract
  • Added template preprocessing with preprocess_template() function
  • Added better dependency checking for optional SDK packages (anthropic, mistralai, boto3, aioboto3)
  • Added MissingDependencyError exception for missing optional dependencies
  • Added library_utils.py module with utility functions for PLX file discovery using importlib.resources
  • Added class_utils.py module with are_classes_equivalent() and has_compatible_field() functions
  • Added comprehensive unit tests for CostRegistry, WorkingMemory, and ModuleInspector
  • Added ScanConfig class with configurable excluded directories for library scanning
  • Added CSV export capabilities to CostRegistry with save_to_csv() and to_records() methods
  • Added default configuration template in pipelex/kit/configs/pipelex.toml

Changed

  • Replaced package toml by tomli which is more modern and faster
  • Updated Gemini 2.0 model from gemini-2.0-flash-exp to gemini-2.0-flash with new pricing (input: $0.10, output: $0.40 per million tokens)
  • Updated Gemini 2.5 Series comment from '(when available)' to stable release
  • Updated best-claude from claude-4-sonnet to claude-4.5-sonnet across all presets
  • Updated kajson dependency from version 0.3.0 to 0.3.1
  • Updated httpx dependency to >=0.23.0,<1.0.0 for broader compatibility
  • Cleanup env example and better explain how to set up keys in README and docs
  • Changed Gemini routing from google backend to pipelex_inference backend
  • BREAKING: Major module reorganization - moved tools/config/, tools/exceptions.py, tools/environment.py, tools/runtime_manager.py to system/ package structure (system/configuration/, system/exceptions.py, system/environment.py, system/runtime.py)
  • BREAKING: Reorganized registry modules from tools/ to system/registries/ (affects class_registry_utils, func_registry, func_registry_utils, registry_models)
  • BREAKING: Split pipelex.core.stuffs.stuff_content module into individual files per content type (affects imports: StructuredContent, TextContent, ImageContent, ListContent, PDFContent, PageContent, NumberContent, HtmlContent, MermaidContent, TextAndImagesContent)
  • BREAKING: Renamed package pipelex.pipe_works to pipelex.pipe_run and moved PipeRunParams classes into it
  • BREAKING: Cost reporting changed from Excel (xlsx) to CSV format using native Python csv module instead of pandas
  • Renamed ConfigManager to ConfigLoader
  • Renamed PipelexRegistryModels to CoreRegistryModels
  • Renamed PipelexTestModels to TestRegistryModels
  • Renamed generate_jinja2_context() to generate_context() in WorkingMemory and ContextProviderAbstract
  • Renamed ConceptProviderAbstract to ConceptLibraryAbstract
  • Renamed DomainProviderAbstract to DomainLibraryAbstract
  • Renamed PipeProviderAbstract to PipeLibraryAbstract
  • Renamed PipeInputSpec to InputRequirements
  • Renamed PipeInputSpecFactory to InputRequirementsFactory
  • Renamed pipe_input.py to input_requirements.py
  • Renamed pipe_input_factory.py to input_requirements_factory.py
  • Renamed pipe_input_blueprint.py to input_requirement_blueprint.py
  • Changed hub methods from get_*_provider() to get_*_library() pattern
  • Changed hub methods from set_*_provider() to set_*_library() pattern
  • Changed PipeLLM validation to check all inputs are in required variables
  • Updated LLMPromptSpec to handle image collections (lists/tuples) in addition to single images
  • Changed Mermaid diagram URL generation from /img/ to /svg/ endpoint
  • Changed PipeLLMPromptTemplate.make_llm_prompt() to private method _make_llm_prompt()
  • Updated pipe-builder prompts to include concept specs for better context
  • Updated PipelexBundleSpec.to_blueprint() to sort pipes by dependencies before creating bundle
  • Changed exception base class from PipelexError to PipelexError throughout codebase
  • Updated Makefile pyright target to use --pythonpath flag correctly
  • Enhanced LibraryManager to use importlib.resources for reliable PLX file discovery across all installation modes (wheel, source, relative path)
  • Simplified FuncRegistryUtils to exclusively register functions with @pipe_func decorator (removed decorator_names and require_decorator parameters)
  • Updated ReportingManager to get config directly instead of via constructor parameter
  • Updated PipeFunc documentation to reflect @pipe_func() decorator requirement and auto-discovery from anywhere in project
  • Added warnings about module-level code execution during auto-discovery to PipeFunc and StructuredContent documentation

Fixed

  • Fixed Makefile target pyright to use correct pythonpath flag
  • Fixed bug with inputs of the PipeLLM where image inputs couldn't be used and tagged in prompts
  • Fixed image input handling in LLMPromptSpec to support both single images and image collections
  • Fixed template preprocessing to handle jinja2 templates correctly
  • Fixed hard dependencies by moving imports to function scope in model_lists.py
  • Updated README badge URL to point to main branch instead of feature/pipe-builder branch

Removed

  • Removed centralized pipelex_libraries folder system and pipelex init libraries command
  • Removed config path parameters from Pipelex.make() (relative_config_folder_path, config_folder_path, from_file)
  • Removed Gemini 1.5 series models: gemini-1.5-pro, gemini-1.5-flash, and gemini-1.5-flash-8b
  • Removed base_templates.toml file (generic prompts moved to pipelex.toml)
  • Removed gpt-5-mini from possible models in pipe-builder
  • Removed useless functions in LLMJobFactory: make_llm_job_from_prompt_factory(), make_llm_job_from_prompt_template(), make_llm_job_from_prompt_contents()
  • Removed add_or_update_pipe() method from PipeLibrary
  • Removed get_optional_library_manager() method from PipelexHub
  • Removed get_optional_domain_provider() and get_optional_concept_provider() methods from hub
  • Removed unused test fixtures (apple, cherry, blueberry, concept_provider, pretty) from conftest.py
  • Removed some Vision/Image description pipes from the base library, because we doubt they were useful as they were
  • Removed pandas and openpyxl dependencies (including stubs: pandas-stubs, types-openpyxl)
  • Removed Excel file generation for cost reports and to_dataframe() method from CostRegistry
  • Removed should_warn_if_already_registered parameter from func_registry.register_function()
  • Removed decorator_names and require_decorator parameters from FuncRegistryUtils methods
  • Removed _find_plx_files_in_dir() and _get_pipelex_plx_files_from_dirs() methods from LibraryManager (refactored to library_utils module)
  • Removed hardcoded excluded directories from ClassRegistryUtils and FuncRegistryUtils (now use ScanConfig)
  • Removed are_classes_equivalent() and has_compatible_field() methods from ClassRegistryUtils (moved to class_utils module)

[v0.11.0] - 2025-10-01

Highlights

  • New pipe builder pipeline to generate Pipes based on a brief in natural language: use the cli pipelex build pipe "Your task" to build the pipe.
  • New observer system: inject your own class to observe and trace all details before and after each pipe run. We also provide a local observer that dumps the payloads to local JSONL files = new-line delilmited json, i.e. one json object per line.
  • Full refactoring of OCR and Image Generation to use the same patterns as LLM workers and pipes.

Added

  • Added claude-4.5-sonnet to the model deck.
  • Added a badge on the README.md to display the number of tests.
  • Added new test cases for environment variable functions
  • Added new documentation for PipeFunc on how to register functions.
  • Added pipelex show models [BACKEND_NAME] command to list available models from a specific backend.

Changed

  • Renamed llm_deck terminology to model_deck throughout codebase and documentation, now that it's also used for OCR and Image Generation models
  • Renamed is_gha_testing property to is_ci_testing in RuntimeManager
  • Refactored all_env_vars_are_set() function to only accept a list of keys, single string support now uses is_env_var_set()
  • Modified any_env_var_is_placeholder() to use new placeholder detection logic
  • Updated test environment setup to use dynamic placeholder generation instead of hardcoded values

Fixed

  • Fixed logic error in any_env_var_is_placeholder() function - now correctly returns False when no placeholders are found

Removed

  • Removed get_rooted_path() and get_env_rooted_path() utility functions which were not used
  • Removed hardcoded placeholder dictionary and ENV_DUMMY_PLACEHOLDER_VALUE constant in test setup
  • Removed function run_pipe_code in pipe router because it was not relevant (used mostly in tests)
  • Remove the use of PipeCompose in PipeCondition, to only use jinja2 directly, through the ContentGenerator
  • Remove the template libraries from the pipelex libraries.
  • Removed claude-3.5-sonnet and claude-3.5-sonnet-v2 from the model deck.

[v0.10.2] - 2025-09-18

Added

  • Unified OCR system using model handles instead of separate OcrHandle enum
  • ModelType enum supporting LLM and TEXT_EXTRACTOR types
  • Enhanced error handling in library loading with better validation messages
  • Config template management with config-template and cft Makefile targets to update templates from the .pipelex/ directory

Changed

  • ⚠️ Breaking changes:
  • Renamed ocr_handle to ocr_model in PipeExtract blueprint, so you'll need to update your PLX code accordingly
  • Updated .env.example file with slightly modified key names (more standard).
  • OCR system now uses InferenceModelSpec with unified model handles
  • Renamed get_llm_deck() to get_model_deck() and updated parameter names from llm_handle to model_handle
  • Simplified OCR worker factory using plugin SDK matching
  • Enhanced plugin system compatibility with InferenceModelSpec
  • Improved error messages throughout system
  • Improved management of placeholder environment variables for unit tests

Removed

  • Legacy OCR classes: OcrHandle, OcrPlatform, OcrEngine, OcrEngineFactory
  • Obsolete configuration fields and setup methods
  • PipelexFileError exception class

[v0.10.1] - 2025-09-17

Changed

  • Enabled all backends, still required to pass all unit tests.
  • A few tweaks to the base model deck.

[v0.10.0] - 2025-09-17

Highlights

New Inference Backend Configuration System — We've completely redesigned how LLMs are configured and accessed in Pipelex, making it more flexible and easier to get started:

  • Get started in seconds with Pipelex Inference: Use a single API key to access all major LLM providers (OpenAI, Anthropic, Google, Mistral, and more)
  • Flexible backend configuration: Configure multiple inference backends (Azure OpenAI, Amazon Bedrock, Vertex AI, etc.) through simple TOML files in .pipelex/inference/
  • Smart model routing: Automatically route models to the right backend using routing profiles with pattern matching
  • User-friendly aliases: Define shortcuts like best-claudeclaude-4.1-opus with optional fallback chains
  • Cost-aware model specs: Each model includes detailed pricing, capabilities, and constraints for better cost management

For complete details, see the Inference Backend Configuration documentation.

Added

  • New inference backend configuration system in .pipelex/inference/ directory
  • Support for 10+ inference backends: OpenAI, Anthropic, Azure OpenAI, Amazon Bedrock, Mistral, Vertex AI, XAI, BlackboxAI, Perplexity, Ollama, and Pipelex Inference
  • Model routing profiles with pattern matching (*model*, model*, *model)
  • Model aliases with waterfall fallback chains
  • Environment variable and secret substitution in TOML configs (${VAR} and ${secret:KEY})
  • Comprehensive model specifications with detailed cost categories
  • Unified plugin SDK registry for all backends
  • CI environment detection with automatic placeholder API keys for testing
  • Improved pipelex init config command to copy entire configuration template directory structure to .pipelex/ with smart file handling (skips existing files, shows clear progress messages)
  • Added FuncRegistryUtils to register functions in a pipelex folder that have a specific signature.
  • Added mistral-medium and mistral-medium-2508 to the Mistral backend configuration.
  • Added gemini-2.5-flash to the VertexAI backend configuration.

Changed

  • LLM configuration moved from pipelex_libraries/llm_deck/ to .pipelex/inference/deck/
  • LLM handles simplified to direct model names or user-defined aliases
  • Model deck completely redesigned with inference models, aliases, and presets
  • Plugin system refactored to use backend-specific TOML configuration
  • Token categories renamed to cost categories with expanded types

Fixed

  • Improved error messages for missing environment variables
  • Enhanced TOML configuration validation
  • More robust model routing and backend selection

Removed

  • Legacy LLM model library system (llm_integrations/ directory)
  • Platform-specific configuration classes (AnthropicConfig, OpenAIConfig, etc.)
  • Deprecated LLM engine blueprint and factory classes
  • Old LLM platform and family enumerations

Security

  • Enhanced secret management with secure fallback patterns
  • Improved API key handling through centralized backend configuration

[v0.9.5] - 2025-09-12

Highlights

  • Pinned instructor to version <1.10.0 to avoid errors with mypy

Added

  • Added PIPELEX_INFERENCE LLM family enum value
  • Added support for PIPELEX_INFERENCE in OpenAI LLM worker
  • Added Azure OpenAI platform support for Grok models (grok-3 and grok-3-mini)
  • Added debug logging for PipeParallel output contents
  • Added TOML file filtering in LLM model library loading
  • Added error handling for Unicode decode errors in LLM model library
  • Added new test model configurations for pipelex and vertex_ai platforms

Changed

  • Improved error messages in StuffFactory to include concept code and stuff name
  • Disabled is_gen_object_supported for all Grok models (grok-3, grok-3-mini, grok-3-fast)
  • Updated test configurations to use different LLM models and platforms
  • Modified Jinja2 filter to use default TagStyle.TICKS instead of raising error
  • Added proper error handling for Unicode decode errors when loading model libraries
  • Improved error handling in Anthropic plugin tests with specific AuthenticationError handling
  • Image handling in AnthropicFactory now converts image URLs to base64 data URLs with proper MIME type prefix
  • Put back Discord link in README.md

Fixed

  • Pinned instructor to version <1.10.0 to avoid errors with mypy

[v0.9.4] - 2025-09-06

Added

  • Added support for BlackboxAI models

[v0.9.3] - 2025-09-06

Added

  • Better support for BlackboxAI IDE
  • VS Code extensions recommendations file with Pipelex, Ruff, and MyPy extensions
  • File association for .plx files in VS Code settings

[v0.9.2] - 2025-09-05

Fixed

  • Fix the rules of all agents.

Added

  • Added agent rule for copilot
  • Added a rule to forbidden structuring basic text concepts

[v0.9.1] - 2025-09-05

Fixed

  • Fixed many inconsistencies in the documentation.

[v0.9.0] - 2025-09-02

Refacto

  • Changed the pipeline file extension from .toml to .plx: Updated the LibraryManager in consequence.

Fixed

  • Fixed the structuring_method behavior in the PipeLLM pipe: Putting it to preliminary_text, the PipeLLM will always generate text before generating the structure -> Reliability increased by a lot.

Fixed

  • Fixed a bug in the needed_inputs method of the PipeSequence pipe.

Changed

  • dry_run_pipe now returns a DryRunOutput object instead of a str with additional information.
  • Updated cocode dependency from version v0.0.10 to v0.0.15.

Added

  • Added the FuncRegistryUtils class to register functions in the library.

[v0.8.1] - 2025-08-27

Bugfix

  • Bugfix: Fixed the PipeFunc output concept code and structure class name in the dry run.

[v0.8.0] - 2025-08-27

Refactor

  • Refactored the concepts: Blueprints are now more explicit, and hold only concept strings or code. Pipes hold concept instances.
  • Organized code: Created subfolders for controller and operator pipes.
  • Say goodbye to PipeLLMPrompt.
  • Removed the PipeCompose and PipeLLMPrompt from the PipeLLM.

Added

  • Added a lot of unit tests.
  • Loading the library can now be done from toml file or from PipelexBundleBlueprint.

Fixed

  • Backported backports.strenum to >=1.3.0 to support Python 3.10 now in dependencies and not in optional dependencies.

[v0.7.0] - 2025-08-20

Refactor

  • Refactored the Blueprints. Introduces the PipelexInterpreter that interprets the Pipelex language and creates the Pipelex Blueprints (and vice versa)
  • Modified the way we declare pipes. Use the field type = "PipeLLM" instead of field PipeLLM. (Same for all pipes)
  • Refactored the LibraryManager.
  • Refactored CLI commands and added new ones. Modified CLI command structure:
  • pipelex init - Initialization commands
    • pipelex init libraries [DIRECTORY] - Initialize pipelex libraries (creates pipelex_libraries folder)
    • pipelex init config - Initialize pipelex configuration (creates pipelex.toml)
  • pipelex validate - Validation and dry-run commands
    • pipelex validate all -c pipelex/libraries - Validate all libraries and dry-run all pipes
    • pipelex validate pipe PIPE_CODE - Dry run a single pipe by its code
  • pipelex show - Show and list commands
    • pipelex show config - Show the pipelex configuration
    • pipelex show pipes - List all available pipes with descriptions
    • pipelex show pipe PIPE_CODE - Show a single pipe definition
  • pipelex migrate - Migration commands
    • pipelex migrate run - Migrate TOML files to new syntax (with --dry-run and --backups options)
  • pipelex build - Build artifacts like pipeline blueprints
    • pipelex build draft PIPELINE_NAME - Generate a draft pipeline
    • pipelex build blueprint PIPELINE_NAME - Generate a pipeline blueprint
  • Organized concept, pipe, working_memory, stuff files into folders.

Changed

  • Allow aiofiles version >=23.2.1
  • GHA Cla assistant fixed with Github App

Added

  • New LLM families LLMFamily.GPT_5, LLMFamily.GPT_5_CHAT and LLMFamily.CLAUDE_4_1
  • Added support for Claude 4.1 and GPT 5 models (inc. mini, nano, chat)
  • New Pipe that generates pipe. Pipe code: build_blueprint
  • New tests. Especially for the PipelexInterpreter.
  • Migration files and cli commands to migrate Pipelex language to new syntax.
  • Introduces PipelexBundle, which correspond to the python paradigm of the Pipelex TOML syntax.

[v0.6.10] - 2025-08-02

Added

  • New test file for source code manipulation functions (tests/cases/source_code.py)
  • New integration test for PipeFunc functionality (tests/integration/pipelex/pipes/pipe_operator/pipe_func/test_pipe_func.py)
  • New package structure file for pipe_func tests (init.py)
  • Simplified input memory creation for native concepts (Text, Image, PDF) in pipeline execution
  • Added Pipeline requests link to GitHub issue template config

Changed

  • Updated pipeline execution documentation and examples to use input_memory instead of working_memory
  • Renamed pipeline from 'extract_page_contents_from_pdf' to 'ocr_page_contents_from_pdf'
  • Renamed pipeline from 'extract_page_contents_and_views_from_pdf' to 'ocr_page_contents_and_views_from_pdf'
  • Updated cocode dependency from version 0.0.6 to 0.0.9

Fixed

  • Fixed typo in pipeline description ('aspage views' to 'as full page views')

Removed

  • Removed WorkingMemoryFactory and StuffFactory imports from pipeline execution examples
  • Removed working memory creation code from pipeline examples

[v0.6.9] - 2025-07-26

Changed

Simplified input memory:

  • The concept code can now be provided with arg named concept in addition to concept_code
  • You can pass a simple string to create a Text stuff

[v0.6.8] - 2025-07-25

Added

  • New method make_stuff_using_concept_name_and_search_domains in StuffFactory for creating stuff using concept names and search domains.
  • New method make_stuff_from_stuff_content_using_search_domains in StuffFactory for creating stuff from stuff content using search domains.
  • New method make_from_implicit_memory in WorkingMemoryFactory for creating working memory from implicit memory.
  • New method create_mock_content in WorkingMemoryFactory for creating mock content for requirements.

Changed

  • Refactored PipeInput to use InputRequirement and TypedNamedInputRequirement classes instead of plain strings for input specifications.
  • Updated WorkingMemoryFactory to handle PipelineInputs instead of CompactMemory.
  • Replaced ExecutePipelineException with PipelineInputError in execute_pipeline function.
  • Updated PipeBatch, PipeCondition, PipeParallel, PipeSequence, PipeFunc, PipeImgGen, PipeCompose, PipeLLM, and PipeExtract classes to use InputRequirement for input handling.
  • Updated PipeInput creation in various test files to use make_from_dict method.
  • Updated pyproject.toml to exclude pypdfium2 version 4.30.1.
  • Updated Jinja2TemplateCategory to handle HTML and Markdown templates differently.

Fixed

  • Corrected error messages in StuffFactory and StuffContentFactory to provide more detailed information about exceptions.

[v0.6.7] - 2025-07-24

Removed

  • Removed the structure_classes parameter from the Pipelex class.

[v0.6.6] - 2025-07-24

Added

  • Added a new method verify_content_type in the Stuff class to verify and convert content to the expected type.
  • Added cocode==0.0.6 to the development dependencies in pyproject.toml.

Changed

  • Updated Stuff class methods to use the new verify_content_type method for content verification.
  • Updated vertexai.toml to change LLM IDs from preview models to released models: gemini-2.5-pro and gemini-2.5-flash.

Removed

  • Removed reinitlibraries, rl, v, and init targets from the Makefile.

[v0.6.5] - 2025-07-21

Fixed

  • In the documentation, fixed the use of execute_pipeline.

[v0.6.4] - 2025-07-19

  • Fixed the README.md link to the documentation

[v0.6.3] - 2025-07-18

Changed

  • Enhanced Stuff.content_as() method with improved type validation logic - now attempts model validation when isinstance check fails

[v0.6.2] - 2025-07-18

Added

  • New dry-run-pipe cli command to dry run a single pipe by its code
  • New show-pipe cli command to display pipe definitions from the pipe library
  • New dry_run_single_pipe() function for running individual pipe dry runs

Changed

  • Updated init-libraries command to accept a directory argument and create pipelex_libraries folder in specified location
  • Updated validate command to use -c flag for the config folder path

[v0.6.1] - 2025-07-16

  • Can execute pipelines with input_memory: It is a CompactMemory: Dict[str, Dict[str, Any]]

[v0.6.0] - 2025-07-15

Changed

  • Enhanced Pipelex.make() method: Complete overhaul of the initialization method with new path configuration options and robust validation:
  • Added relative_config_folder_path and absolute_config_folder_path parameters for flexible config folder specification
  • The from_file parameter controls path resolution: if True (default), relative paths are resolved relative to the caller's file location; if False, relative to the current working directory (useful for CLI scenarios)
  • Renamed Makefile targets like make doc to make docs for consistency

Added

  • Added github action for inference tests
  • load_json_list_from_path function in pipelex.tools.misc.file_utils: Loads a JSON file and ensures it contains a list.
  • Added issue templates
  • Updated Azure/OpenAI integrations, using dated deployment names systematically

[v0.5.2] - 2025-07-11

  • log a warning when dry running a PipeFunc
  • Update Readme.md

[v0.5.1] - 2025-07-09

Fixed

  • Fixed the ConceptFactory.make_from_blueprint method: Concepts defined in single-line format no longer automatically refine TextContent when a structure class with the same name exists
  • ConceptFactory.make_concept_from_definition is now ConceptFactory.make_concept_from_definition_str

Added

  • Bumped kajson to v0.3.0: Introducing MetaSingleton for better singleton management
  • Unit tests for ConceptLibrary.is_compatible_by_concept_code

[v0.5.0] - 2025-07-01

Highlights

Vibe Coding an AI workflow becomes a reality — Create AI workflows from natural language without writing code: the combination of Pipelex's declarative language, comprehensive Cursor rules, and robust validation tools enables AI assistants to autonomously iterate on pipelines until all errors are resolved and workflows are ready to run.

Added

  • Complete Dry Run & Static Validation System - A comprehensive validation framework that catches configuration and pipeline errors before any expensive inference operations.
  • WorkingMemoryFactory Enhancement: New make_for_dry_run() method creates working memory with realistic mock objects for zero-cost pipeline testing
  • Enhanced Dry Run System: Complete dry run support for all pipe controllers (PipeCondition, PipeParallel, PipeBatch) with mock data generation using polyfactory
  • Comprehensive Static Validation: Enhanced static validation with configurable error handling for missing/extraneous input variables and domain validation
  • TOML File Validation: Automatic detection and prevention of trailing whitespaces, formatting issues, and compilation blockers in pipeline files
  • Pipeline Testing Framework: New dry_run_all_pipes() method enables comprehensive testing of entire pipeline libraries
  • Enhanced Library Loading: Improved error handling and validation during TOML file loading with proper exception propagation

Configuration

  • Dry Run Configuration: New allowed_to_fail_pipes setting allows specific pipes (like infinite loop examples that fail on purpose) to be excluded from dry run validation
  • Static Validation Control: Configurable error reactions (raise, log, ignore) for different validation error types

Documentation & Development Experience

  • Cursor Rules Enhancement: Comprehensive pipe controller documentation covering PipeSequence, PipeCondition, PipeBatch, and PipeParallel, improved PipeOperator documentation for PipeLLM, PipeOCR
  • Pipeline Validation CLI: Enhanced pipelex validate all -c pipelex/libraries command with better error reporting and validation coverage
  • Improved Error Messages: Better formatting and context for pipeline configuration errors

Changed

  • Error Message Improvements: Updated PipeCondition error messages to reference expression_template instead of deprecated expression_jinja2

[v0.4.11] - 2025-06-30

  • LLM Settings Simplification: Streamlined LLM choice system by removing complex for_object_direct, for_object_list, and for_object_list_direct options. LLM selection now uses a simpler fallback pattern: specific choice → text choice → overrides → defaults.
  • Image Model Updates: Renamed image_bytes field to base_64 in PromptImageTypedBytes for better consistency. Updated to use CustomBaseModel base class to benefit from bytes truncation when printing.

[v0.4.10] - 2025-06-30

  • Fixed a bad import statement

[v0.4.9] - 2025-06-30

Highlights

Plugin System Refactoring - Complete overhaul of the plugin architecture to support external LLM providers.

Added

  • External Plugin Support: New LLMWorkerAbstract base class for integrating custom LLM providers, and we don't mean only an OpenAI-SDK-based LLM with a custom endpoint, now the implementation can be anything, as long as it implements the LLMWorkerAbstract interface.
  • Plugin SDK Registry: Better management of SDK instances with proper teardown handling
  • Enhanced Error Formatting: Improved Pydantic validation error messages for enums

Changed

  • Plugin Architecture: Moved plugin system to dedicated pipelex.plugins package
  • LLM Workers: Split into LLMWorkerInternalAbstract (for built-in providers) and LLMWorkerAbstract (for external plugins)
  • Configuration: Plugin configs moved from main pipelex.toml to separate pipelex_libraries/plugins/plugin_config.toml (⚠️ breaking change)
  • Error Handling: Standardized credential errors with new CredentialsError base class

[v0.4.8] - 2025-06-26

  • Added StorageProviderAbstract
  • Updated the changelog of v0.4.7: Moved Added StorageProviderAbstract to v0.4.8

[v0.4.7] - 2025-06-26

  • Added an API serializer: introducing the compact_memory, a new way to encode/decode the working memory as json, for the API.
  • When creating a Concept with no structure specified and no explicit refines, set it to refine native.Text
  • JobMetadata: added job_name. Removed top_job_id and wfid
  • PipeOutput: added pipeline_run_id

[v0.4.6] - 2025-06-24

  • Changed the link to the doc in the README.md: https://docs.pipelex.com

[v0.4.5] - 2025-06-23

Changed

  • Test structure overhaul: Reorganized test directory structure for better organization:
  • Tests now separated into unit/, integration/, and e2e/ directories
  • Created tests/cases/ package for pure test data and constants
  • Created tests/helpers/ package for test utilities
  • Cleaned up test imports and removed empty __init__.py files
  • Class registry refactoring: Updated kajson from 0.1.6 to 0.2.0, adapted to changes in Kajson's class registry with new ClassRegistryUtils (better separation of concerns)
  • Dependency updates:
  • Added pytest-mock to dev dependencies for improved unit testing

Added

  • Coverage commands: New Makefile targets for test coverage analysis:
  • make cov: Run tests with coverage report
  • make cov-missing (or make cm): Show coverage with missing lines
  • Test configuration: Set xfail_strict = true in pytest config for stricter test failure handling
  • Pydantic validation errors: Enhanced error formatting to properly handle model_type errors

Fixed

  • External links: Removed broken Markdown target="_blank" syntax from MANIFESTO.md links
  • Variable naming consistency: Fixed redundant naming in OpenAI config (openai_openai_config → openai_config)
  • Makefile optimization: Removed parallel test execution (-n auto) from codex-tests, works better now

Tests

  • Unit tests added: New comprehensive unit tests for:
  • ClassRegistryUtils
  • FuncRegistry
  • ModuleInspector
  • File finding utilities

[v0.4.4] - 2025-06-20

Fixed

  • Changed the allowed base branch names in the GHA guard-branches.yml: doc -> docs
  • Fixed kajson dependency (see kajson v0.1.6 changelog)

Cursor rules

  • Added Cursor rules for coding best practices and standards (including linting methods). Added TDD (Test Driven Development) rule on demand.
  • Various changes

Documentation

  • Added documentation for referencing images in PipeLLM.
  • Fixed typos

Refactor

  • Removed the images field from PipeLLM - images can now be referenced directly in the inputs
  • Moved the list-pipes CLI function to the PipeLibrary class.

[v0.4.3] - 2025-06-19

Fixed

  • Removed deprecated Gemini 1.5 models: Removed gemini-1.5-flash and gemini-1.5-pro from the VertexAI integration as they are no longer supported
  • Fixed multiple import statements across the codebase

Documentation

  • Enhanced MkDocs search: Added search functionality to the documentation site
  • Proofreading improvements: Fixed various typos and improved clarity across documentation

Refactor

  • Mini refactor: changed kajson dependency to kajson==0.1.5 (instead of >=) to tolerate temporary breaking changes from kajson

[v0.4.2] - 2025-06-17

  • Fixed the inheritance config manager method (Undocumented feature, soon to be removed)
  • Fixed the deploy-doc.yml GitHub Action
  • Grouped the mkdocs dependencies in a single group docs in the pyproject.toml file

[v0.4.1] - 2025-06-16

  • Changed discord link to the new one: https://go.pipelex.com/discord
  • Added hello-world example in the cookbook-examples of the documentation.

[v0.4.0] - 2025-06-16

Highlights

Complete documentation overhaul:

  • MkDocs setup for static web docs generation
  • Material for MkDocs theme, custom styling and navigation
  • Other plugins: meta-manager, glightbox
  • GitHub Pages deployment, mapped to docs.pipelex.com
  • Added GHA workflows for documentation deployment and validation
  • Added to docs:
  • Manifesto explaining the Pipelex viewpoint
  • The Pipelex Paradigm explaining the fundamentals of Pipelex's solution
  • **Cookbook examples** presented and explained, commented code, some event with mermaid flow charts
  • And plenty of details about using Pipelex and developing for Pipelex, from structured generation to PipeOperators (LLM, Image generation, OCR…) to PipeControllers (Sequence, Parallel, Batch, Condition…), workflow optimization, workflow static validation and dry run… there's still work to do, but we move fast!
  • Also a major update of Cursor rules

Tooling Improvements

  • Pipeline tracking: restored visual flowchart generation using Mermaid
  • Enhanced dry run configuration: added more granular control with nb_list_items, nb_extract_pages, and image_urls
  • New feature flags: better control over pipeline tracking, activity tracking, and reporting
  • Improved OCR configuration: handle image file type for Mistral-OCR, added default_page_views_dpi setting
  • Enhanced LLM configuration: better prompting for structured generation with automatic schema insertion for two-step structuring: generate plain text and then structure via Json
  • Better logging: Enhanced log truncation and display for large objects like image bytes (there are still cases to deal with)

Refactor

Concept system refactoring

  • Improved concept code factory with better domain handling, so you no longer need the native domain prefix for native domains, you can just call them by their names: Text, Image, PDF, Page, Number
  • Concept refines attribute can now be a string for single refined concepts (the most common case)

Breaking Changes

  • File structure changes: documentation moved from doc/ to docs/
  • Configuration changes: some configuration keys have been renamed or restructured
  • StuffFactory.make_stuff() argument concept_code renamed to concept_str to explicitly support concepts without fully qualified domains (e.g., Text or PDF implicitly native )
  • Some method signatures have been updated

Tests

  • Added Concept refinement validation: TestConceptRefinesValidationFunction and TestConceptPydanticFieldValidation ensure proper concept inheritance and field validation

[v0.3.2] - 2025-06-13

  • Improved automatic insertion of class structure from BaseModel into prompts, based on the PipeLLM's output_concept. New unit test included.
  • The ReportingManager now reports costs for all pipeline IDs when no pipeline_run_id is specified.
  • The make_from_str method from the StuffFactory class now uses Text context by default.

[v0.3.1] - 2025-06-10

Added

  • New pytest marker dry_runnable for tests that can run without inference.
  • Enhanced make targets with dry-run capabilities for improved test coverage:
  • make test-xdist (or make t): Runs all non-inference tests plus inference tests that support dry-runs - fast and resource-efficient
  • make test-inference (or make ti): Runs tests requiring actual inference, with actual inference (slow and costly)
  • Parallel test execution using pytest-xdist (-n auto) enabled for:
  • GitHub Actions workflows
  • Codex test targets

Changed

  • Domain validation is now less restrictive in pipeline TOML: the description attribute is now Optional

[v0.3.0] - 2025-06-09

Highlights

  • Structured Input Specifications: Pipe inputs are now defined as a dictionary mapping a required variable name to a concept code (required_variable -> concept_code). This replaces the previous single input field and allows for multiple, named inputs, making pipes more powerful and explicit. This is a breaking change.
  • Static Validation for Inference Pipes: You can now catch configuration and input mistakes in your pipelines before running any operations. This static validation checks PipeLLM, PipeExtract, and PipeImgGen. Static validation for controller pipes (PipeSequence, PipeParallel…) will come in a future release.
  • Configure the behavior for different error types using the static_validation_config section in your settings. For each error type, choose to raise, log, or ignore.
  • Dry Run Mode for Zero-Cost Pipeline Validation: A powerful dry-run mode allows you to test entire pipelines without making any actual inference calls. It's fast, costs nothing, works offline, and is perfect for linting and validating pipeline logic.
  • The new dry_run_config lets you control settings, like disabling Jinja2 rendering during a dry run.
  • This feature leverages polyfactory to generate mock Pydantic models for simulated outputs.
  • Error handling for bad inputs during run_pipe has been improved and is fully effective in dry-run mode.
  • One limitation: currently, dry running doesn't work when the pipeline uses a PipeCondition. This will be fixed in a future release.

Added

  • native.Anything Concept: A new flexible native concept that is compatible with any other concept, simplifying pipe definitions where input types can vary.
  • Added dependency on polyfactory for mock Pydantic model generation in dry-run mode.

Changed

  • Refactored Cognitive Workers: The abstraction for LLM, ImgGen, and Ocr workers has been elegantly simplified. The old decorator-based approach (..._job_func) has been replaced with a more robust pattern: a public base method now handles pre- and post-execution logic while calling a private abstract method that each worker implements.
  • The b64_image_bytes field in PromptImageBytes was renamed to base_64 for better consistency.

Fixed

  • Resolved a logged error related to the pipe stack when using PipeParallel.
  • The pipe tracker functionality has been restored. It no longer crashes when using nested object attributes (e.g., my_object.attribute) as pipe inputs.

Tests

  • A new pytest command-line option --pipe-run-mode has been added to switch between live and dry runs (default is dry). All pipe tests now respect this mode.
  • Introduced the pipelex_api pytest marker for tests related to the Pipelex API client, separating them from general inference or llm tests.
  • Added a make test-pipelex-api target (shorthand: make ta) to exclusively run these new API client tests.

Removed

  • The llm_job_func.py file and the associated decorators have been removed as part of the cognitive worker refactoring.

[v0.2.14] - 2025-06-06

  • Added a feature flag for the ReportingManager in the config:
[pipelex]
[pipelex.feature_config]
is_reporting_enabled = true
  • Moved the reporting config form the cogtconfig to the Pipelex config.

[v0.2.13] - 2025-06-06

  • Added Discord badge on the Readme. Join the community! -> https://go.pipelex.com/discord
  • Added a client for the Pipelex API. Join the waitlist -> https://www.pipelex.com/signup
  • Removed the run_pipe_code function. Replaced by execute_pipeline in pipelex.pipeline.execute.
  • Added llm deck llm_for_img_to_text.
  • Renamed InferenceReportManager to ReportingManager: It can report more than Inference cost. Renamed InferenceReportDelegate to ReportingProtocol.
  • Added an injection of dependency for ReportingManager
  • pipelex cli: fixed some bugs

[v0.2.12] - 2025-06-03

  • pipelex cli: Split pipelex init into 2 separate functions: pipelex init-libraries and pipelex init-config
  • Fixed the inheritance config manager method
  • Rename Mission to Pipeline
  • Enable to start a pipeline and let in run in the background, getting it's run id, but not waiting for the output
  • Makefile: avoid defaulting pytest to verbose. Setup target make test-xdist = Run unit tests with xdist, make it the default for shorthand make t. The old make t is now make tp (test-with-prints)
  • Added mistral-small-3.1 and qwen3:8b
  • Fix template pre-processor: don't try and substitute a dollar numerical like $10 or @25
  • Refactor with less "OpenAI" naming for non-openai stuff that just uses the OpenAI SDK

[v0.2.11] - 2025-06-02

  • HotFix for v0.2.10 👇 regarding the new pipelex/pipelex_init.toml`

[v0.2.10] - 2025-06-02

Highlights

Python Support Expansion - We're no longer tied to Python 3.11! Now supporting Python 3.10, 3.11, 3.12, and 3.13 with full CI coverage across all versions.

Major Model Additions - Claude 4 (Opus & Sonnet), Grok-3, and GPT-4 image generation are now in the house.

Pipeline Base Library update

  • New pipe - ocr_page_contents_and_views_from_pdf transferred from cookbook to base library (congrats on the promotion!). This pipe extracts text, linked images, AND page_view images (rendered pages) - it's very useful if you want to use Vision in follow-up pipes

Added

  • Template preprocessor - New @? token prefix for optional variable insertion - if a variable doesn't exist, we gracefully skip it instead of throwing exceptions
  • Claude 4 support - Both Opus and Sonnet variants, available through Anthropic SDK (direct & Bedrock) plus Bedrock SDK. Includes specific max_tokens limit reduction to prevent timeout/streaming issues (temporary workaround)
  • Grok-3 family support - Full support via OpenAI SDK for X.AI's latest models
  • GPT-4 image generation - New gpt-image-1 model through OpenAI SDK, available via PipeImgGen. Currently saves local files (addressing in next release)
  • Gemini update - Added latest gemini-2.5-pro to the lineup
  • Image generation enhancements - Better quality controls, improved background handling options, auto-adapts to different models: Flux, SDXL and now gpt-image-1

Refactored

  • Moved subpackage plugin to the same level as cogt within pipelex for better visibility
  • Major cleanup in the unit tests, hierarchy significantly flattened
  • Strengthened error handling throughout inference flows and template preprocessing
  • Added make test-quiet (shorthand tq) to Makefile to run tests without capturing outputs (i.e. without pytest -s option)
  • Stopped using Fixtures for pipe_router and content_generator: we're now always getting the singleton from pipelex.hub

Fixed

  • Perplexity integration - Fixed breaking changes from recent updates

Dependencies

  • Added pytest-xdist to run unit tests in parallel on multiple CPUs. Not yet integrated into the Makefile, so run it manually with pytest -n auto (without inference) or pytest -n auto -m "inference" (inference only).
  • Swapped pytest-pretty for pytest-sugar - because readable test names > pretty tables
  • Updated instructor to v1.8.3
  • All dependencies tested against Python 3.10, 3.11, 3.12, and 3.13

Tests

  • TestTemplatePreprocessor
  • TestImgGenByOpenAIGpt
  • TestImageGeneration
  • TestPipeImgGen

[v0.2.9] - 2025-05-30

  • Include pyproject.toml inside the project build.
  • Fix ImgGenEngineFactory: image generation (imgg) handle required format is platform/model_name
  • pipelex cli: Added list-pipes method that can list all the available pipes along with their descriptions.
  • Use a minimum version for uv instead of a fixed version
  • Implement AGENTS.md for Codex
  • Add tests for some of the tools.misc
  • pipelex cli: Rename pipelex run-setup to pipelex validate all -c pipelex/libraries

[v0.2.8] - 2025-05-28

  • Replaced poetry by uv for dependency management.
  • Simplify llm provider config: All the API keys, urls, and regions now live in the .env.
  • Added logging level OFF, prevents any log from hitting the console

[v0.2.7] - 2025-05-26

  • Reboot repository

[v0.2.6] - 2025-05-26

  • Refactor: use ActivityManagerProtocol, rename BaseModelTypeVar

[v0.2.5] - 2025-05-25

  • Add custom LLM integration via OpenAI sdk with custom base_url

[v0.2.4] - 2025-05-25

  • Tidy tools
  • Tidy inference API plugins
  • Tidy WIP feature ActivityManager

[v0.2.2] - 2025-05-22

  • Simplify the use of native concepts
  • Include "page views" in the outputs of Ocr features

[v0.2.1] - 2025-05-22

  • Added OcrWorkerAbstract and MistralOcrWorker, along with PipeExtract for OCR processing of images and PDFs.
  • Introduced MissionManager for managing missions, cost reports, and activity tracking.
  • Added detection and handling for pipe stack overflow, configurable with pipe_stack_limit.
  • More possibilities for dependency injection and better class structure.
  • Misc updates including simplified PR template, LLM deck overrides, removal of unused config vars, and disabling of an LLM platform id.

[v0.2.0] - 2025-05-19

  • Added OCR, thanks to Mistral
  • Refactoring and cleanup

[v0.1.14] - 2025-05-13

  • Initial release 🎉