v0.39.0
[v0.39.0] - 2026-07-14
Highlights
- From resolved library to typed clients:
pipelex resolveandpipelex codegen—pipelex resolvedistills a bundle's whole closure into the normalized library crate — a flat, fully-qualified, fingerprinted snapshot — andpipelex codegen typesprojects it into TypeScript (ts-zod) or Python (python-pydantic,python-structures) artifacts. A trust chain of stamped headers and acodegen.lockmakes drift detectable offline:pipelex codegen checkverifies 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_fixobject in agent and API payloads. The newpipelex fix bundle/pipelex-agent fix bundlecommands apply those fixes in place through a shared fix loop, with--diffto 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 forImage/Documentinputs, and generated input templates default to the light bare-value form. - New native concepts:
Date,Time, andYesNo— Built-in concepts for calendar dates (preserving source precision — no fabricated times of day), times of day, and boolean judgments, plus atimestructure-field type completing the temporal triple. Every native now materializes from its pinned normative definition, so crate fingerprints byte-agree across implementations.
Added
pipelex resolveand thepipelex codegenfamily:pipelex resolveassembles 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-structuresprojects the crate's concept set into typed artifacts. Thets-zodtarget emits a puretypes.ts(imports onlyzod, with wire-native snake_case keys so a schema validates a wire payload directly) plus abinder.tsexposing a typedparse/serializepair per concept;python-pydanticemits self-containedBaseModels;python-structuresemits runtimeStructuredContentclasses.pipelex codegen inputs --pipe <ref>renders a runnable inputs template for a pipe (defaulting to the closure'smain_pipe).- Generated files are never edited — each carries an
AUTOGENERATEDheader pointing at the sibling extension-file mechanism. - Codegen trust chain (stamps ·
codegen.lock· offlinecodegen check): Every filepipelex codegen typeswrites 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 siblingcodegen.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 (0current ·1drift ·2no 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|jsonsuccess stream,--error-formaterror stream).codegen typesruns the same engine and write-if-changed emission as the bare CLI;codegen checkpresents the offline drift verdict structurally — drift is aCodegenDriftErrorenvelope enumeratingdrifts[]by category (exit1), a missing or unreadablecodegen.lockis a no-verdict error (exit2). 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_contentsresolves in-memory MTHDS contents into the normalized crate — same engine, sameValidateBundleErrorverdict vocabulary, and the same loaded-on-success library contract asvalidate_bundle— so a host (thepipelex-api/v1/resolve+/v1/codegenroutes) serves resolution without touching the filesystem.pipelex.codegen.emission.build_stamped_projectionis the new pure stamping core (stamped files +codegen.lockcontent, no disk writes) thatwrite_stamped_projectionnow rides, so artifacts served over HTTP are byte-identical to locally written ones and pass the offlinecodegen checkverbatim. The shared bundle-loading error cascadetranslate_to_validate_bundle_erroris 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/--ignorefix-rule filters,--allow-signatures,--max-iterations, and the same bundle-file or directory resolution aspipelex-agent validate bundle. Successful fix results reportpending_signaturesandis_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-iterationsoptions, and the same bundle-file or directory resolution aspipelex validate bundle. A new--diffflag 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--diffanswers "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 exactpipelex fix bundlecommand (echoing the invocation's-Ldirs) in place of the generic tip. suggested_fixfield in/validatepayloads: Each validation error that has a deterministic safe fix now carries an additive structuredsuggested_fixobject —fix_code(the kebab-case rule id),description(the author-facing 💡 text),safety(safe/unsafe), optionalsourcefile, andops(the semantic patch operations) — on everyvalidation_errors[]item, both in thepipelex-agent validate/fixJSON envelopes and in the API/validateresponse 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 genericnative.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
ImageorDocumentinputs are resolved relative to the inputs file's directory. - Declared structured lists (e.g.,
Person[]) accept a direct path to a.csvfile 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
--explicitflag: Added--explicittopipelex build inputsandpipelex-agent inputsto generate the legacy{"concept", "content"}envelope templates. - Native
YesNoconcept: Built-in concept (backed byYesNoContent) for boolean judgments. Requires a strict boolean, renders asyes/noin downstream prompts, and can be read viapipe_output.main_stuff_as_yes_no.yes_no. - Native
Dateconcept: Built-in concept (backed byDateContent) for calendar dates. Requires adatetime.dateand accepts an optionaldatetime.time. Preserves source precision (never forcing the LLM to fabricate a time of day) and retains UTC offsets verbatim. - Native
Timeconcept: Built-in concept (backed byTimeContent) 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 toTime(Breaking:Timejoins the reserved native concept codes, and the former bare-time rejection —InputsTimeOnlyNotSupportedError— is gone). - Structure-field
type = "time": The concept structure language gains atimefield type (a time of day, optionally with a UTC offset), completing the temporal triple besidedateanddatetime. Generates adatetime.timefield; 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 newnative-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 insubject_grants.tomlat 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-grantis the sole registry writer; thecheck-keyword-onlyAST 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) inmake check, CI, and the edit-time hook. Subjects typedbool/int/floatare banned outright — no grant can cover them. The registry schema is strict: exactlyparam+rationale, unknown keys fail the check. Seedocs/contribute/keyword-only-arguments.md. - Drift contracts (
pipelex-dev drift plan|check|ack): Deterministic review obligations between code and docs. A rootdrift.tomldeclares 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-checkgates themake checkaggregate and runs as an advisory CI job;make drift-planprints 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
PipeAbstractrun family (run_pipe,dry_run_pipe,validate_before_run, … —job_metadata/visited_pipesnow keyword-only),StuffContent.rendered_for_prompt/rendered_for_template_async(text_format) and theTextFormatRenderable/ImageRenderableprotocols with all their implementations, theLibraryManagerAbstractload family (library_id),ensure_pipelex_booted(config_overrides),pipeline_run_setup(execution_config),ConceptLibrary.is_compatible(tested_concept),GraphTracerProtocol.setup(graph_id), thePromptImageFactory/PromptDocumentFactorycontent factories (uri), the model-manager/backend-library setup+load (secrets_provider), the reporting protocol'sset_event_log/clear_event_log(context_key), and assorted tools helpers (pluralize/count_with_noun,LogLevel.from_int,set_dry_run_forced, …). pipelex build structuresis now an alias ofcodegen 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 stampedstructures.pymodule plus acodegen.lock(bare-when-unique class names, declared imprecision instead of the old silentTextContentguess for structureless concepts). The--forceflag is gone (emission is write-if-changed), a.mthdsfile 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 runneremits its structures through the codegen engine (Breaking): The scaffoldedstructures/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 astructure = "<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 sameValidationErrorItems 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(...)/PresenceMarkerreprs). 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/fixoutput renders prose, not a JSON dump:pipelex-agent validate bundleandpipelex-agent fix bundlemarkdown 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 exactpipelex-agent fix bundlecommand 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/validatebody) 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-verdictrendered_markdownnow carries the same per-error💡 Suggested fixprose 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 inputsandpipelex-agent inputsnow 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): Adatefield now generates adatetime.date(JSON schemaformat: date), preventing LLMs from hallucinating times for calendar dates. A newtype = "datetime"field handles full timestamps (format: date-time), and default-value validation now rejects adatetimedefault on adatefield. - Strict input validation (Breaking): Undeclared input names are now a hard error (previously silently ignored), and top-level
nullvalues are rejected — absence must be expressed by omitting the key. Known limitation: this also rejects the extra stuffs carried by apipelex-agent run --with-memoryenvelope, so piping one run's full working memory into a downstream pipe that declares only a subset of it now fails withUnknownInputNameError. 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
Dateconcept; a bare time-of-day literal maps to the newTimeconcept (it never silently becomes aDate— shaping aTimeinto aDateslot fails the compatibility check). ImageContentflattens its pixel dimensions (Breaking):size: ImageSize | Noneis replaced by paired optionalwidth/heightinteger 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.ImageSizeremains as the internal img-gen parameter type. Image wire payloads change shape accordingly.native.Datedeclares real structure (Breaking): MaterializedDateis no longer structureless — its pinned definition carries a requireddatefield plus an optionaltimefield (the newtimefield 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_librariestook an explicitlibrary_idbut 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 inscoped_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. PipeExtractbroke crate normalization (500s on every static-core route):PipeExtractBlueprint.validate_outputonly accepted the authoring spellingPage[], 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 aPipeExtractraised a raw pydanticValidationErrorout ofresolve_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 nativePage— since a refined output would be an unverifiable promise:PipeExtractmechanically produces native pages and cannot make them more specific.resolve/codegenrejected 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 likevalidate— offline, but with model specs loaded.- Native
Imageno longer materializes structureless: Every native now materializes from its pinned normative definition, so generated clients for image-producing pipes exposeurl,public_url,caption,width,height, etc. (The brief interim dict-with-imprecision mapping for the nested size model is superseded by theImageContentflatten 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 inputsis write-if-changed: Likecodegen types, an already-current inputs template is left untouched (no mtime churn) and the console reportsUnchangedinstead of claimingGenerated.- 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"), somodel_validatekeeps the payload verbatim instead of pydantic's defaultextra="ignore"silently dropping every field. Opaque is pass-through, never lossy — matching the ts-zodz.unknown()behavior. - Human CLI
~expansion:pipelex validate bundle(and the newpipelex fix bundle) now expand~in the bundle path and-L/--library-dirvalues, matching the agent CLI. - Validate telemetry label: The
CLI_COMMANDtelemetry tag forpipelex validatesubcommands 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 genericnative.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, andnative-concepts.mdfor the Smart Inputs paradigm, leading with the bare-value approach and demoting the envelope format to an escape hatch.
What's Changed
- Update README and deferred-step-f-notes with release status and follo… by @lchoquel in #1034
- feat(core): Native YesNo and Date concepts + Smart Inputs by @lchoquel in #1028
- Add graph provenance mode metadata by @lchoquel in #1038
- Autofix by @lchoquel in #1027
- feat(codegen): normalized library crate + deterministic projections (types, inputs, trust chain, resolve/codegen CLI + agent CLI) by @lchoquel in #1039
- Subject grants: every positional subject is an explicit, recorded, reviewed exception by @lchoquel in #1040
- drift ack hardening: auto-stage the ack file; hard-fail dirty triggers on verify contracts by @lchoquel in #1041
- docs: fix code-doc drift across under-the-hood + advanced pages (Drift Hunt S2-3) by @lchoquel in #1042
- docs: fix drift on building-methods pipes/ pages (Drift Hunt S2-1) by @lchoquel in #1043
- Drift Hunt S2-2: fix building-methods concepts + top-level docs drift by @lchoquel in #1044
- Drift Hunt S2-4: features + the tail — Stage-1 drift fixes + cross-cutting sweeps by @lchoquel in #1045
- Drift Hunt: campaign close-out — records, handoff, and tracker by @lchoquel in #1046
- Stabilization fixes and changelog polish by @lchoquel in #1047
- Release v0.39.0 by @lchoquel in #1048
Full Changelog: v0.38.0...v0.39.0