.mthds File Format
The .mthds file is a TOML document that defines typed data (concepts) and typed transformations (pipes) within a single domain. This page is the normative reference for every field, validation rule, and structural constraint of the format.
File Encoding and Syntax
A .mthds file MUST be a valid TOML document encoded in UTF-8. The file extension MUST be .mthds. Parsers MUST reject files that are not valid TOML before any MTHDS-specific validation occurs.
Top-Level Structure
A .mthds file is called a bundle. It consists of:
- Header fields — top-level key-value pairs that identify the bundle.
- Concept definitions — a
[concept]table and/or[concept.<ConceptCode>]sub-tables. - Pipe definitions —
[pipe.<pipe_code>]sub-tables.
All three sections are optional in the TOML sense (an empty .mthds file is valid TOML), but a useful bundle will contain at least one concept or one pipe.
Header Fields
Header fields appear at the top level of the TOML document, before any [concept] or [pipe] tables.
| Field | Type | Required | Description |
|---|---|---|---|
domain |
string | Yes | The domain this bundle belongs to. Determines the namespace for all concepts and pipes defined in this file. |
description |
string | No | A human-readable description of what this bundle provides. |
system_prompt |
string | No | A default system prompt applied to all PipeLLM pipes in this bundle that do not define their own system_prompt. |
main_pipe |
string | No | The pipe code of the bundle's primary entry point. If set, this pipe is auto-exported when the bundle is part of a package. |
Validation rules:
domainMUST be a valid domain code (see Domain Naming Rules).main_pipe, if present, MUST be a valid pipe code (snake_case) and MUST reference a pipe defined in this bundle.
Example:
domain = "legal.contracts"
description = "Contract analysis methods for legal documents"
main_pipe = "extract_clause"
Domain Naming Rules
Domain codes define the namespace for all concepts and pipes in a bundle.
Syntax:
- A domain code is one or more
snake_casesegments separated by.(dot). - Each segment MUST match the pattern
[a-z][a-z0-9_]*. - Domains MAY be hierarchical:
legal,legal.contracts,legal.contracts.shareholder.
Reserved domains:
The following domain names are reserved and MUST NOT be used as the first segment of any user-defined domain:
native— built-in concept typesmthds— reserved for the MTHDS standardpipelex— reserved for the reference implementation
A compliant implementation MUST reject bundles that declare a domain starting with a reserved segment (e.g., native.custom is invalid).
Recommendations:
- Depth SHOULD be 1–3 levels.
- Each segment SHOULD be 1–4 words.
Concept Definitions
Concepts are typed data declarations. They define the vocabulary of a domain — the kinds of data that pipes accept and produce.
Simple Concept Declarations
The simplest form of concept declaration uses a flat [concept] table where each key is a concept code and the value is a description string:
[concept]
ContractClause = "A clause extracted from a legal contract"
UserProfile = "A user's profile information"
This form declares concepts with no structure and no refinement. They exist as named types.
Structured Concept Declarations
A concept with fields uses a [concept.<ConceptCode>] sub-table:
[concept.LineItem]
description = "A single line item in an invoice"
[concept.LineItem.structure]
product_name = { type = "text", description = "Name of the product", required = true }
quantity = { type = "integer", description = "Quantity ordered", required = true }
unit_price = { type = "number", description = "Price per unit", required = true }
Both forms MAY coexist in the same bundle. A bundle MAY mix simple declarations in [concept] with structured declarations as [concept.<Code>] sub-tables.
Concept Blueprint Fields
When using the structured form [concept.<ConceptCode>], the following fields are available:
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | Yes | Human-readable description of the concept. |
structure |
table or string | No | Field definitions for the concept. If a string, it is a shorthand description (equivalent to a simple declaration). If a table, each key is a field name mapped to a field blueprint. |
refines |
string | No | A concept reference indicating that this concept is a specialization of another concept. |
hints |
table | No | Optional intent hints for the concept — non-normative presentation intent that applies wherever the concept is presented. |
Validation rules:
refinesandstructureMUST NOT both be present on the same concept. A concept either refines another concept or defines its own structure, not both.refines, if present, MUST be a valid concept reference: either a bare concept code (PascalCase) or a domain-qualified reference (domain.ConceptCode). Cross-package references (alias->domain.ConceptCode) are also valid.- Concept codes MUST be
PascalCase, matching the pattern[A-Z][a-zA-Z0-9]*. - Concept codes MUST NOT collide with native concept codes (see Native Concepts).
Concept Refinement
Refinement establishes a specialization relationship between concepts. A concept that refines another inherits its semantic meaning and can be used anywhere the parent concept is expected.
[concept.NonCompeteClause]
description = "A non-compete clause in an employment contract"
refines = "ContractClause"
The refines field accepts:
- A bare concept code:
"ContractClause"— resolved within the current bundle's domain. - A domain-qualified reference:
"legal.ContractClause"— resolved within the current package. - A cross-package reference:
"acme_legal->legal.contracts.NonDisclosureAgreement"— resolved from a dependency.
Concept Structure Fields
When structure is a table, each key is a field name and each value is a field blueprint. Field names MUST NOT start with an underscore (_), as these are reserved for internal use. Field names MUST NOT collide with reserved field names (Pydantic model attributes and internal metadata fields).
A field's value is either a field blueprint table or a bare string, which is shorthand for a required text field: summary = "A one-line summary" declares exactly what summary = { type = "text", required = true, description = "A one-line summary" } declares. The shorthand carries a description and nothing else, so a field needing any other key is written as a table.
Field Blueprint
Each field in a concept structure is defined by a field blueprint:
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | Yes | Human-readable description of the field. |
type |
string | Conditional | The field type. Required unless choices is provided. |
required |
boolean | No | Whether the field is required. Default: false. |
default_value |
any | No | Default value for the field. Must match the declared type. |
choices |
array of strings | No | Fixed set of allowed string values. When choices is set, type MUST be omitted (the type is implicitly an enum of the given choices). |
key_type |
string | Conditional | Key type for dict fields. Required when type = "dict". |
value_type |
string | Conditional | Value type for dict fields. Required when type = "dict". |
item_type |
string | No | Item type for list fields. When set to "concept", item_concept_ref is required. |
concept_ref |
string | Conditional | Concept reference for concept-typed fields. Required when type = "concept". |
item_concept_ref |
string | Conditional | Concept reference for list items when item_type = "concept". |
hints |
table | No | Optional intent hints for the field — non-normative presentation intent. |
The keys of a field blueprint are a closed set, exactly like an input slot table's. A key this table does not define MUST be rejected — a hopeful key (minimum, examples, unit, …) that validated green would be silently dropped, and an author would have no way to learn the field never carried what they wrote.
Field Types
The type field accepts the following values:
| Type | Description | default_value type |
|---|---|---|
text |
A string value. | string |
integer |
A whole number. | integer |
number |
A numeric value (integer or floating-point). | integer or float |
boolean |
A true/false value. | boolean |
date |
A calendar date value. | datetime |
datetime |
A date with a time of day (a point in time). | datetime |
time |
A time of day, optionally with a UTC offset. | time |
list |
An ordered collection. Use item_type to specify element type. |
array |
dict |
A key-value mapping. Requires key_type and value_type. |
table |
concept |
A reference to another concept. Requires concept_ref. Cannot have default_value. |
(not allowed) |
When type is omitted and choices is provided, the field is an enumeration field. The value MUST be one of the strings in the choices array.
Validation rules for field types:
type = "dict":key_typeandvalue_typeMUST both be non-empty.value_type = "Any"is a reserved marker declaring the dict's value type unspecified: the values are arbitrary, and a consumer surfaces the field as declared imprecision (e.g.dict[str, Any]with a caveat), never as a guessed value shape. It appears primarily in materialized native concept definitions; authors SHOULD declare a concrete value type instead.type = "concept":concept_refMUST be set.default_valueMUST NOT be set.type = "list"withitem_type = "concept":item_concept_refMUST be set.item_concept_refMUST NOT be set unlessitem_type = "concept".concept_refMUST NOT be set unlesstype = "concept".- If
choicesis provided andtypeis omitted,default_value(if present) MUST be one of the values inchoices. - If both
typeanddefault_valueare set, the runtime type ofdefault_valueMUST match the declaredtype. - A field MUST NOT declare both
required = trueanddefault_value. A default means "applied when the caller omits the field", which makes absence legal;requiredmeans "must be present". The pair is two contradictory instructions on one field, and it fails validation rather than resolving to whichever the implementation happens to check first.
Example — concept with all field types:
[concept.CandidateProfile]
description = "A candidate's profile for job matching"
[concept.CandidateProfile.structure]
full_name = { type = "text", description = "Full name", required = true }
years_experience = { type = "integer", description = "Years of professional experience" }
gpa = { type = "number", description = "Grade point average" }
is_active = { type = "boolean", description = "Whether actively looking", default_value = true }
graduation_date = { type = "date", description = "Date of graduation" }
last_seen_at = { type = "datetime", description = "Last activity timestamp" }
preferred_slot = { type = "time", description = "Preferred interview time of day" }
skills = { type = "list", item_type = "text", description = "List of skills" }
metadata = { type = "dict", key_type = "text", value_type = "text", description = "Additional metadata" }
seniority_level = { description = "Seniority level", choices = ["junior", "mid", "senior", "lead"] }
address = { type = "concept", concept_ref = "Address", description = "Home address" }
references = { type = "list", item_type = "concept", item_concept_ref = "ContactInfo", description = "Professional references" }
Native Concepts
Native concepts are built-in types that are always available in every bundle without declaration. They belong to the reserved native domain.
| Code | Qualified Reference | Description |
|---|---|---|
Dynamic |
native.Dynamic |
A dynamically-typed value. |
Text |
native.Text |
A text string. |
Image |
native.Image |
An image (binary). |
Document |
native.Document |
A document (e.g., PDF, web page). |
Html |
native.Html |
HTML content. |
TextAndImages |
native.TextAndImages |
Combined text and image content. |
Number |
native.Number |
A numeric value. |
YesNo |
native.YesNo |
The answer to a yes/no question. |
Choice |
native.Choice |
One option picked out of a declared set. |
Rating |
native.Rating |
A position on an ordered scale of described levels. |
Date |
native.Date |
A calendar date, optionally with a time of day. |
Time |
native.Time |
A time of day, optionally with a UTC offset. |
Page |
native.Page |
A single page extracted from a document. |
JSON |
native.JSON |
A JSON value. |
SearchResult |
native.SearchResult |
A web search result with answer and sources. |
Anything |
native.Anything |
Accepts any type. |
Composite |
native.Composite |
A named composition of contents. |
Native concepts MAY be referenced by bare code (Text, Image) or by qualified reference (native.Text, native.Image). Bare native concept codes always take priority during resolution.
A bundle MUST NOT declare a concept with the same code as a native concept. A compliant implementation MUST reject such declarations.
Each native concept's exact blueprint form — its fields, their types, and their descriptions — is pinned per standard version in Native Concept Definitions. Implementations MUST use the pinned definitions verbatim (no reflection over internal runtime types) wherever a native's structural definition is needed, such as library crate materialization.
Pipe Definitions
Pipes are typed transformations. Each pipe has a typed signature: it declares what concepts it accepts as input and what concept it produces as output.
Common Pipe Fields
Concrete pipe types share these base fields:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes for concrete pipes | The pipe type. Determines which category and additional fields are available. Omitted only for contract-only PipeSignature declarations. |
description |
string | Yes | Human-readable description of what this pipe does. |
inputs |
table | No | Input declarations. Keys are input names (plain snake_case), values are input slot declarations (see Input slot declarations). |
output |
string | Yes | The output concept reference with optional multiplicity. |
Pipe codes:
- Pipe codes are the keys in
[pipe.<pipe_code>]tables. - Pipe codes MUST be
snake_case, matching the pattern[a-z][a-z0-9_]*.
Input names:
- An input name MUST be a plain
snake_caseidentifier, matching the pattern[a-z][a-z0-9_]*, on every pipe, operator or controller alike. It names one whole value, of the concept its slot declares. - An input name MUST NOT contain a dot. A key such as
"invoice.total" = "Number"does not declare a field ofinvoice: a compliant implementation MUST reject it, as it rejects any other name that is not a plainsnake_caseidentifier (InvoiceTotal,2nd_total), and SHOULD report the refusal asmethod.identifier_invalid. - A pipe that needs one field of a value receives it in one of two ways. Either it declares the root with its whole concept and reads the field through the root in its template (
invoice = "Invoice", read as$invoice.total, see Inputs Read Through Templates), or the calling sequence hands it the field under a plain name with a binding step ({ from = "invoice.total", result = "total_amount" }, see Binding Steps) and the pipe declarestotal_amount = "Number". The message refusing a dotted name SHOULD name both remedies. - The names under which a step stores a value in working memory, such as a pipe step's
result, take the same form, so that an input can read whatever a step stores (see Stored Names).
Concept references in inputs and output:
Concept references in inputs and output support an optional multiplicity suffix and, for pipe declarations only, a presence marker:
| Syntax | Meaning |
|---|---|
ConceptName |
A single instance. |
ConceptName[] |
A variable-length list (runtime determines count). |
ConceptName[N] |
A fixed-length list of exactly N items (N ≥ 2). |
ConceptName[1] |
A single instance — the same slot as ConceptName, with the count written out. Not a one-item list. |
ConceptName? |
Optional single value. The slot may resolve as a recorded absence. |
ConceptName! |
Forced single input. If the slot is absent at run time, the run fails loudly. Inputs only. |
The bracketed count MUST be at least 1: ConceptName[0] is invalid, because a fixed count of zero declares a slot that can hold nothing. A count of exactly one is single throughout the standard — ConceptName[1] is a way of writing ConceptName, never a one-element list — so nothing downstream wraps such a value in an array, and a fixed count reported on any wire is always greater than one. Every artifact that carries multiplicity states the same rule: the library crate materializes it, and pipe I/O contracts, the input-form descriptor and the output-form descriptor report it.
Concept references MAY be bare codes (Text), domain-qualified (legal.ContractClause), or cross-package qualified (alias->domain.ConceptCode).
Presence markers have these constraints:
- Markers apply only to pipe
inputsandoutput, not concept definitions,refines, or structure fields. - Markers MUST NOT be combined with multiplicity.
Concept[]?,Concept[N]?,Concept[]!, andConcept[N]!are invalid because plural slots use an empty list when no items are produced. !MUST NOT appear onoutput. A force marker is an input-side assertion.
Input slot declarations:
Each value in inputs declares one input slot, in one of two forms. The string form is a concept reference with optional multiplicity and presence marker, as specified above. The expanded form is a table:
[pipe.summarize]
type = "PipeLLM"
description = "Summarize a contract, following optional steering instructions"
output = "Summary"
[pipe.summarize.inputs]
contract = "legal.Contract"
instructions = { concept = "Text?", hints = { intent = "prose" } }
Rules for the expanded form:
concept(string) is required and carries exactly the same grammar as the string form — a concept reference with optional multiplicity and optional presence marker.x = "S"andx = { concept = "S" }are equivalent.hints(table) is optional and attaches intent hints to the slot.- No other keys are defined in this version of the standard. An unknown key in an input slot table MUST be rejected. (The form is deliberately shaped so that future versions can add per-slot authoring fields — such as a slot description — without a second syntax.)
- The expanded form applies to
inputsonly.outputis always a string.
Example:
[pipe.analyze_contract]
type = "PipeLLM"
description = "Analyze a legal contract and extract key clauses"
output = "ContractClause[5]"
[pipe.analyze_contract.inputs]
contract_text = "Text"
Pipe Types
MTHDS defines pipe types in two categories:
Operators — pipes that perform a single transformation:
| Type | Value | Description |
|---|---|---|
| PipeLLM | "PipeLLM" |
Generates output using a large language model. |
| PipeStructure | "PipeStructure" |
Turns text into a structured concept using a large language model. |
| PipeFunc | "PipeFunc" |
Calls a Python function of the method's tree. |
| PipeImgGen | "PipeImgGen" |
Generates images using an image generation model. |
| PipeExtract | "PipeExtract" |
Extracts structured content from documents. |
| PipeSearch | "PipeSearch" |
Searches the web and returns structured results. |
| PipeJudge | "PipeJudge" |
Asks a judging model one or several closed questions about the evidence its prompt presents, and returns a verdict for each. |
| PipeCompose | "PipeCompose" |
Composes output from templates or constructs. |
Controllers — pipes that orchestrate other pipes:
| Type | Value | Description |
|---|---|---|
| PipeSequence | "PipeSequence" |
Executes a series of pipes in order. |
| PipeParallel | "PipeParallel" |
Executes pipes concurrently. |
| PipeCondition | "PipeCondition" |
Routes execution based on a condition. |
| PipeBatch | "PipeBatch" |
Maps a pipe over each item in a list. |
Contract-Only Pipe Signatures
A [pipe.<pipe_code>] section with no type is a PipeSignature when it contains only contract fields:
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | Yes | Human-readable description of the intended pipe. |
inputs |
table | No | Input declarations, in either input slot form — the string form, or the expanded form with hints. |
output |
string | Yes | Output concept reference. Multiplicity and ? are supported. |
signature_for |
string | No | Optional hint naming the concrete pipe type expected later, such as "PipeLLM". |
PipeSignature is not a pipe type value. Authors MUST NOT write type = "PipeSignature". A typeless pipe section that contains implementation fields such as prompt, steps, branches, or model is invalid because concrete implementations must declare their type.
Example:
[pipe.summarize_doc]
description = "Summarize a source document"
inputs = { document = "Document" }
output = "Text"
signature_for = "PipeLLM"
Inputs Read Through Templates
PipeLLM, PipeImgGen, PipeSearch, PipeJudge and PipeCompose read their inputs through templates, and the validation rules of each operator name the fields that do the reading. For these five operators the input rule runs in both directions: every variable those fields reference MUST correspond to a declared input, apart from the names the operator's own rules exclude, and every declared input MUST be read by at least one of them. A declared input that no such field reads is rejected.
A template variable is a dotted path whose first segment, its root, names an input, and whose following segments, if any, name fields reached through that input's concept. A variable corresponds to the declared input its root names, and reads that input. The match is made by the root alone and is the same on every operator, a PipeJudge's questions and a PipeCompose construct's from paths included, with no per-operator exception:
$deal.customer_namereads the inputdeal.@page.page_viewand{{ page.page_view.url }}read the inputpage, declared with its whole concept (page = "Page"). The field is reached through the root in the template and is never declared as an input of its own, since an input name is a plain name.- An optional (
?) input is not exempt. A conditional reference such as@?noteor{% if note %}…{% endif %}reads it.
A variable's value and its concept are what its path reaches through the root's declared concept. Each segment after the root names a field of the concept reached so far, found in that concept's structure, or, for a native concept, in its pinned definition. An operator that handles a value according to its concept MUST handle a variable according to the concept its path reaches, never the root's concept: PipeLLM's attachment of images and documents to the model call, PipeImgGen's injection of reference images, and PipeJudge's presentation of images and documents with its evidence apply to the reached concept, whether the path reaches a single value or a list. With page = "Page", @page.page_view reaches the page_view field of the pinned Page definition, an Image, so PipeLLM attaches it as an image, PipeImgGen injects it as a reference image, and a PipeJudge's prompt presents it as an image.
Operator: PipeLLM
Generates output by invoking a large language model with a prompt.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeLLM" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | — |
prompt |
string | No | The LLM prompt template. Supports Jinja2 syntax and the @variable / $variable shorthand. |
system_prompt |
string | No | System prompt for the LLM. If omitted, the bundle-level system_prompt is used (if any). |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline LLM settings table. |
model_to_structure |
string or table | No | Model used for structuring the LLM output into the declared concept. Accepts the same forms as model. |
structuring_method |
string | No | Directive controlling how the output is structured. Values: "direct" (single LLM call producing JSON) or "preliminary_text" (the runtime first produces text, then structures it as a second step). The standard does not prescribe HOW the runtime implements "preliminary_text". |
templating_style |
string or table | No | How the pipe's inputs are tagged and formatted when interpolated into the prompt. Either a tag style string (no_tag, ticks, xml, square_brackets) or a templating style table carrying tag_style and text_format. If omitted, the runtime default applies. |
Prompt template syntax:
{{ variable_name }}— standard Jinja2 variable substitution.@variable_name— shorthand, preprocessed to Jinja2 syntax.$variable_name— shorthand, preprocessed to Jinja2 syntax.- Dotted paths are supported:
{{ doc_request.document_type }},@doc_request.priority.
Validation rules:
- Every variable referenced in
promptandsystem_promptMUST correspond to a declared input (by root name). Internal variables starting with_and the special namespreliminary_textandplace_holderare excluded from this check. - Every declared input MUST be referenced by at least one variable in
promptorsystem_prompt. Unused inputs are rejected (see Inputs Read Through Templates).
Example:
[pipe.analyze_cv]
type = "PipeLLM"
description = "Analyze a CV to extract key professional information"
output = "CVAnalysis"
model = "$writing-factual"
system_prompt = """
You are an expert HR analyst specializing in CV evaluation.
"""
prompt = """
Analyze the following CV and extract the candidate's key professional information.
@cv_pages
"""
[pipe.analyze_cv.inputs]
cv_pages = "Page"
Inline LLM Settings
When the model field is a table instead of a string, it defines inline model settings using the LLMSetting structure:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | The model handle (e.g., "claude-4.5-sonnet"). |
temperature |
number | Yes | Sampling temperature. Range: 0–1. |
max_tokens |
integer, "auto", or null |
No | Maximum tokens for the response. "auto" lets the model choose. |
image_detail |
string | No | Image detail level for vision inputs. Values: high, low, auto. |
reasoning_effort |
string | No | Level of reasoning effort. Values: none, minimal, low, medium, high, xhigh, max. xhigh sits between high and max and maps to provider-specific xhigh values where supported. |
reasoning_budget |
integer | No | Token budget for reasoning. Must be > 0. |
description |
string | No | Human-readable description of this model configuration. |
Validation rules:
reasoning_effortandreasoning_budgetMUST NOT both be set on the same inline LLM settings table.
Example — inline LLM settings:
[pipe.analyze_cv]
type = "PipeLLM"
description = "Analyze a CV"
output = "CVAnalysis"
prompt = "Analyze: @cv_pages"
model = { model = "claude-4.5-sonnet", temperature = 0.1, max_tokens = 4096 }
[pipe.analyze_cv.inputs]
cv_pages = "Page"
Operator: PipeStructure
Turns text into a structured concept matching the declared output schema. The standard does not prescribe how a runtime achieves this; a typical implementation uses an LLM call.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeStructure" |
Yes | — |
description |
string | Yes | — |
inputs |
table | Yes | MUST contain exactly one entry. |
output |
string | Yes | The target structured concept reference, with optional multiplicity. MUST NOT be Text or a concept that refines Text. |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline LLM settings table. |
Validation rules:
inputsMUST contain exactly one entry. The single input concept MUST beTextor a concept that refinesText.outputMUST NOT beTextand MUST NOT be a concept that refinesText.outputMAY use multiplicity (Foo,Foo[],Foo[N]).PipeStructureMUST NOT accept image or document inputs. Use an upstream extraction step to produce text first.
Example:
[pipe.structure_review]
type = "PipeStructure"
description = "Turn a free-form review into a RestaurantReview"
inputs = { review_text = "Text" }
output = "RestaurantReview"
Example — with an explicit structuring model:
[pipe.structure_review_premium]
type = "PipeStructure"
description = "Use a stronger model for tricky structurings"
inputs = { review_text = "Text" }
output = "RestaurantReview"
model = "@default-premium"
Operator: PipeFunc
Calls a Python function of the method's tree.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeFunc" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | — |
function_name |
string | Yes | The import path of the Python function to call, read from the root of the method tree that holds the bundle. |
Example:
text_tools/
├── METHODS.toml
├── text.mthds
└── funcs/
└── text_utils.py (defines capitalize)
[pipe.capitalize_text]
type = "PipeFunc"
description = "Capitalize the input text"
inputs = { text = "Text" }
output = "Text"
function_name = "funcs.text_utils.capitalize"
Locating the function. function_name is a Python import path whose import root is the root of the method tree that holds the bundle, which for a bundle of a dependency is that dependency's own root. The name is normalized to NFC before anything else, as a file reference is, so a name an editor wrote in decomposed form finds the module whose tree path is in NFC: function_name = "café.brew", its é written as e and a combining accent, names the function brew of the module café.py. The name then splits at its last dot: the part before it is the module path and the part after it is the name of the function, an attribute of that module. The module path funcs.text_utils names the file funcs/text_utils.py of the tree, or the package funcs/text_utils/__init__.py, each dot of the module path being read as a /, and the modules that module imports are resolved by Python from the same root, so a helper module in a subdirectory has a place to live and relative imports work.
Validation rules:
function_nameMUST hold at least one dot. A name with no dot, such ascapitalize, names no module and is rejected.- Every dot-separated segment of
function_name, once the name is in NFC, MUST be a Python identifier that is not a Python keyword and that NFKC normalization leaves unchanged. Python reads identifiers in NFKC, so a segment NFKC would change would make Python import another file than the one the tree holds under that name:function_name = "files.load", whose first letters are the ligaturefi, would be imported asfileswhile the tree's lookup namesfiles.py. Such a segment, holding a ligature or a fullwidth letter, is rejected as an invalidfunction_name, never mapped to its NFKC form. - The module path MUST name a module of the tree: the tree holds the file
<module path>.pyor the file<module path>/__init__.py. A tree holding both is rejected as ambiguous, naming both files. A validator given the tree checks this without running any Python, and names the pipe whose module is missing.
A method declares the third-party Python packages its functions import in its requirements.txt. The standard specifies how a step locates its function and nothing about what the function must look like: how a runtime marks a function as callable from a method, the convention by which it is called, and how the runtime imports a tree's modules without letting them collide with another tree's or with its own are the runtime's to define and check.
Operator: PipeImgGen
Generates images using an image generation model. The pipe carries a required prompt string template (and an optional negative_prompt template); it does not take a dedicated prompt concept as input. Declared inputs are injected into the prompt at runtime, each variable according to the concept its path reaches (see Inputs Read Through Templates): a variable that reaches text is interpolated into the prompt text, while a variable that reaches an Image (a single image or a list), such as $source on a source = "Image" input or $page.page_view on a page = "Page" input, is injected as a reference image — each image becomes an [Image N] token in the rendered text and is passed to the generator alongside it, enabling image-to-image, reference-image, and image-editing generation.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeImgGen" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | — |
prompt |
string | Yes | The image generation prompt template. Supports Jinja2 and $variable shorthand; declared inputs are injected into it. |
negative_prompt |
string | No | An optional prompt template describing what to avoid in the generated image. |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline image generation settings table. |
aspect_ratio |
string | No | Desired aspect ratio. Values: square, landscape_4_3, landscape_3_2, landscape_16_9, landscape_21_9, portrait_3_4, portrait_2_3, portrait_9_16, portrait_9_21. |
is_raw |
boolean | No | Whether to use raw mode (less post-processing). |
seed |
integer or "auto" |
No | Random seed for reproducibility. "auto" lets the model choose. |
background |
string | No | Background setting. Values: transparent, opaque, auto. |
output_format |
string | No | Image output format. Values: png, jpeg, webp. |
Validation rules:
- Every variable referenced in
promptornegative_promptMUST correspond to a declared input. - Every declared input MUST be referenced by at least one variable in
promptornegative_prompt. Unused inputs are rejected (see Inputs Read Through Templates). outputMUST resolve to anImage-compatible concept.- Any variable referenced as a reference image in the
promptornegative_promptMUST reach, through its path, anImage-compatible concept (single or list).
Example:
[pipe.generate_portrait]
type = "PipeImgGen"
description = "Generate a portrait image from a description"
inputs = { description = "Text" }
output = "Image"
prompt = "A professional portrait: $description"
model = "$gen-image-testing"
Example with a reference image (image-to-image):
[pipe.restyle_photo]
type = "PipeImgGen"
description = "Restyle a source photo following a textual instruction"
inputs = { source = "Image", instruction = "Text" }
output = "Image"
prompt = "Restyle this image: $source. $instruction"
Inline Image Generation Settings
When the model field is a table instead of a string, it defines inline model settings using the ImgGenSetting structure:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | The model handle. |
quality |
string | No | Image quality. Values: low, medium, high. |
nb_steps |
integer | No | Number of generation steps. Must be > 0. |
guidance_scale |
number | No | Guidance scale for generation. Must be > 0. |
is_moderated |
boolean | No | Whether to apply content moderation. Default: false. |
safety_tolerance |
integer | No | Safety tolerance level. Range: 1–6. |
description |
string | No | Human-readable description of this model configuration. |
Validation rules:
qualityandnb_stepsMUST NOT both be set on the same inline image generation settings table.
Example — inline image generation settings:
[pipe.generate_portrait]
type = "PipeImgGen"
description = "Generate a portrait image"
inputs = { description = "Text" }
output = "Image"
prompt = "A professional portrait: $description"
aspect_ratio = "portrait_3_4"
model = { model = "flux-pro", quality = "high" }
Operator: PipeExtract
Extracts structured content from documents (e.g., PDF, web pages).
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeExtract" |
Yes | — |
description |
string | Yes | — |
inputs |
table | Yes | MUST contain exactly one input. |
output |
string | Yes | MUST be "Page[]". |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline extract settings table. |
max_page_images |
integer | No | Maximum number of page images to process. |
page_image_captions |
boolean | No | Whether to generate captions for page images. |
page_views |
boolean | No | Whether to generate page views. |
page_views_dpi |
integer | No | DPI for page view rendering. |
render_js |
boolean | No | Web-page extraction only: render JavaScript before fetching the page content. Honored by backends that support headless rendering. Default: false. |
include_raw_html |
boolean | No | Web-page extraction only: include the fetched HTML in each extracted Page's raw_html field. Default: false. |
Validation rules:
inputsMUST contain exactly one entry. The input concept SHOULD beDocumentor a concept that refinesDocumentorImage.outputMUST be"Page[]"(a variable-length list ofPage).- When the document URL is a web page, PipeExtract fetches and extracts the page content.
render_jsandinclude_raw_htmlapply only to this case; backends that target local documents (PDFs, images) MAY ignore them.
Example:
[pipe.extract_cv]
type = "PipeExtract"
description = "Extract text content from a CV PDF document"
inputs = { cv_pdf = "Document" }
output = "Page[]"
model = "@default-text-from-pdf"
Inline Extract Settings
When the model field is a table instead of a string, it defines inline model settings using the ExtractSetting structure:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | The model handle. |
max_nb_images |
integer | No | Maximum number of images to extract. Must be >= 0. |
image_min_size |
integer | No | Minimum image size in pixels. Must be >= 0. |
description |
string | No | Human-readable description of this model configuration. |
Example — inline extract settings:
[pipe.extract_cv]
type = "PipeExtract"
description = "Extract text content from a CV PDF document"
inputs = { cv_pdf = "Document" }
output = "Page[]"
model = { model = "gpt-4.1", max_nb_images = 10, image_min_size = 100 }
Operator: PipeSearch
Searches the web using a search provider and returns structured results.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeSearch" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | MUST be SearchResult or a concept that refines SearchResult. |
prompt |
string | Yes | The search query template. Supports Jinja2 syntax and the @variable / $variable shorthand. |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline search settings table. |
from_date |
string | No | Start date filter in ISO 8601 format (YYYY-MM-DD). Only return results from this date onwards. |
to_date |
string | No | End date filter in ISO 8601 format (YYYY-MM-DD). Only return results up to this date. |
include_domains |
array of strings | No | Restrict search to these domains only (e.g., ["reuters.com", "bbc.com"]). |
exclude_domains |
array of strings | No | Exclude results from these domains. |
Validation rules:
- Every variable referenced in
promptMUST correspond to a declared input. - Every declared input MUST be referenced by at least one variable in
prompt. Unused inputs are rejected (see Inputs Read Through Templates). outputMUST beSearchResultor a concept that refinesSearchResult.
Example:
[pipe.search_topic]
type = "PipeSearch"
description = "Search the web for information about a topic"
inputs = { topic = "Text" }
output = "SearchResult"
model = "$standard"
prompt = "What is $topic?"
Example — with date and domain filters:
[pipe.search_recent_from_sources]
type = "PipeSearch"
description = "Search specific sources for recent news"
inputs = { topic = "Text" }
output = "SearchResult"
model = "$standard"
prompt = "What are the latest developments about $topic?"
from_date = "2026-01-01"
include_domains = ["reuters.com", "apnews.com", "bbc.com"]
Inline Search Settings
When the model field is a table instead of a string, it defines inline model settings using the SearchSetting structure:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | The model handle. |
include_images |
boolean | No | Whether to include images in results. Default: false. |
include_inline_citations |
boolean | No | Whether to include inline citations. Default: true. |
max_results |
integer or null | No | Maximum number of results. Must be ≥ 1. |
description |
string | No | Human-readable description of this model configuration. |
Example — inline search settings:
[pipe.deep_search]
type = "PipeSearch"
description = "Deep research on a topic"
inputs = { topic = "Text" }
output = "SearchResult"
prompt = "What are the main details about $topic?"
model = { model = "linkup-deep", include_images = false }
Operator: PipeJudge
Asks a judging model one closed question, or several, about the evidence its prompt presents, and returns each verdict with whatever uncertainty the model reports: a yes or a no, one option out of a declared set, or one level on a declared scale.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeJudge" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | With question, the verdict native of the question's kind: YesNo, Choice or Rating, or a concept that refines it. With questions, a structured concept holding one verdict per question (see Several Questions). |
prompt |
string | Yes | The evidence template: what the judging model judges. Supports Jinja2 syntax and the @variable / $variable shorthand, and presents the images and documents it reads (see the evidence). |
question |
string | Yes (if no questions) |
The question template. Supports Jinja2 syntax and the @variable / $variable shorthand. |
questions |
table | Yes (if no question) |
Several questions over the same evidence: each key names a question and the output field holding its verdict, and each value is a question table. |
model |
string or table | No | Model identifier, model reference (see Model References), or an inline judgment settings table. |
options |
table | No | The options of a choice question: each key is an option, each value describes it. |
levels |
array | No | The levels of a rating question, from lowest to highest: each a string describing the level, or a level table carrying a label, a description or both. |
criteria |
table | No | What a yes and a no mean, for a yes/no question: a yes description and a no description, both required. |
threshold |
number | No | For a yes/no question, the probability of yes at or above which the verdict is yes. |
With questions, the fields options, levels, criteria and threshold are set on each question rather than on the pipe.
The question's kind is decided by which of options and levels the question declares, never by a separate field:
| The question declares | Kind | Its verdict is |
|---|---|---|
neither options nor levels |
yes/no | YesNo or a concept that refines it |
options |
choice | Choice or a concept that refines it |
levels |
rating | Rating or a concept that refines it |
The evidence and the question. A PipeJudge keeps what is judged apart from what is asked: prompt renders the evidence, and each question asks about it. The judging model sees the pipe's inputs only as prompt and the questions render them, so an input reaches it only through a template that reads it, and every declared input is read by prompt or by a question (see Inputs Read Through Templates). prompt is rendered as a PipeLLM's prompt is: a variable that reaches text is interpolated, and a variable that reaches an Image or a Document, a single value or a list, presents each file it reaches to the judging model with the rendered text, where a numbered token such as [Image 1] marks the place the prompt reads it. A question is plain text and presents no file; a variable it reads is interpolated, which suits a short parameter such as "Is the message about $topic?". An optional input is read through a guarded reference such as @?note, which renders nothing when the input is absent. Whether a judging model reads images or documents is a property of the model, not of the language, so an implementation SHOULD reject, before any run, a PipeJudge whose prompt reads a file its resolved model cannot read.
Rating levels. A level is either a string, which describes it, or a level table, closed to label and description: the label names the level in a few words, and the description states the situation it stands for. Either every level of a scale carries a label or none does, and the labels of one scale are distinct, because the label is what the verdict reports. A choice needs no labels, since an option's key already names it.
Validation rules:
promptMUST be set, and MUST NOT be empty.- Exactly one of
questionandquestionsMUST be set. A pipe that sets neither is rejected, and the message SHOULD say thatpromptholds the evidence and that the question is written inquestion. question, when set, MUST NOT be empty.- Every variable referenced in
promptor in a question MUST correspond to a declared input, and every declared input MUST be read bypromptor by at least one question. Unread inputs are rejected (see Inputs Read Through Templates). - A variable referenced in a question MUST NOT reach an
Imageor aDocument, whether a single value or a list: onlypromptpresents files. optionsandlevelsMUST NOT both be set.options, when set, MUST hold at least two options. Each key MUST be a non-empty string, and each value a string; an empty string is an option with no description.levels, when set, MUST hold at least two levels. A level's position in the array is its index, counted from0. A level written as a string MUST NOT be empty. A level table MUST carrylabel,descriptionor both, each a non-empty string, and any other key MUST be rejected. Either every level carries alabelor none does, and no two levels of one scale carry the same label.criteriaandthresholdMUST NOT be set besideoptionsorlevels.criteria, when set, MUST be a table carrying bothyesandno, each a non-empty string, and any other key MUST be rejected. A table declaring one side is rejected, and the message SHOULD say that criteria describe both answers and suggest writing the missing side as the complement of the other.threshold, when set, MUST be a number strictly between0and1.- With
question,outputMUST agree with the question's kind, per the table above, and MUST NOT carry a multiplicity suffix: the pipe produces one verdict. To judge each item of a list, map the PipeJudge over it with a PipeBatch.
The verdict. Each verdict is a verdict native whose required member is always set, and whose uncertainty members are set only when the judging model reports them:
- A yes/no question produces a
YesNo. When the model reports a probability of yes,probabilitycarries it. - A choice question produces a
Choicewhosechoiceis one of the keys ofoptions, and whoseprobabilities, when present, are keyed by those same keys. - A rating question produces a
Ratingwhoselevelis an index intolevels, and whoseprobabilities, when present, are keyed by those indices written as text. When the scale declares labels,labelcarries the label of the selected level, copied from the declaration rather than reported by the model; when it declares none,labelis absent.
An implementation MUST NOT synthesize an uncertainty member the model did not report — no probability of 1 read off a bare yes, no confidence invented for a model that has none.
A refusal. A judging model may refuse to answer a question, and a refusal carries no verdict. An implementation MUST NOT turn a refusal into a verdict: no no read off a refused yes/no question, no first option, no lowest level. With question, a refused question fails the run, and the error SHOULD name the pipe and the model and suggest rewording the question or changing the evidence. With questions, the output field of the refused question decides (see Several Questions).
The threshold. When a yes/no question's model reports a probability, yes_no is true exactly when that probability is at or above the question's threshold, and the default threshold is 0.5. When the model reports no probability, the threshold has nothing to apply to and the model's own verdict stands; when the question declares a threshold, an implementation SHOULD warn that it was not applied.
Because a Choice's choice is a plain string, a PipeCondition routes on it directly, with one outcome per option key.
Example — a yes/no question:
[pipe.judge_is_urgent]
type = "PipeJudge"
description = "Decide whether a customer message is urgent"
inputs = { message = "Text" }
output = "YesNo"
prompt = """
A message from a customer:
@message
"""
question = "Is the message urgent?"
threshold = 0.8
[pipe.judge_is_urgent.criteria]
yes = "The sender needs an answer today, or something stops working without one"
no = "The message can wait for the next working day"
Example — a choice question:
[pipe.route_ticket]
type = "PipeJudge"
description = "Pick the team that should handle a ticket"
inputs = { ticket = "Ticket" }
output = "Choice"
prompt = "@ticket"
question = "Which team should handle the ticket?"
[pipe.route_ticket.options]
returns = "Exchanges, refunds, wrong or damaged items"
shipping = "Delivery status, delays, lost packages"
billing = "Charges, invoices, payment problems"
other = "None of the above"
Example — a rating question with labelled levels:
[pipe.rate_severity]
type = "PipeJudge"
description = "Rate how severe a reported issue is"
inputs = { report = "BugReport" }
output = "Rating"
prompt = "@report"
question = "How severe is the reported issue?"
levels = [
{ label = "Cosmetic", description = "Appearance only; no lost functionality" },
{ label = "Workaround available", description = "A task fails, but another way works" },
{ label = "Fully blocked", description = "A task fails with no workaround" },
]
A verdict of the second level reads level = 1 and label = "Workaround available".
Example — an image in the evidence:
[pipe.check_damage]
type = "PipeJudge"
description = "Check a product photo for visible damage"
inputs = { photo = "Image", listing = "ProductListing" }
output = "YesNo"
prompt = """
Inspect the product in this photo: $photo
It is listed as:
@listing
"""
question = "Does the product have visible damage, such as a crack, tear, or dent? Ignore shadows and damage to the packaging."
Several Questions
A PipeJudge asks several questions about the same evidence by setting questions instead of question. Each key names a question and the field of the output that holds its verdict, and each value is a question table:
[concept.ProductInspection]
description = "What an inspection of a product photo found"
[concept.ProductInspection.structure]
damaged = { type = "concept", concept_ref = "YesNo", description = "Whether the product is visibly damaged", required = true }
category = { type = "concept", concept_ref = "Choice", description = "The product's category", required = true }
condition = { type = "concept", concept_ref = "Rating", description = "The product's overall condition" }
[pipe.inspect_product]
type = "PipeJudge"
description = "Inspect a product photo"
inputs = { photo = "Image" }
output = "ProductInspection"
prompt = "A product photo: $photo"
[pipe.inspect_product.questions.damaged]
question = "Does the product have visible damage, such as a crack, tear, or dent?"
threshold = 0.7
[pipe.inspect_product.questions.category]
question = "Which category does the product belong to?"
options = { footwear = "Shoes, boots and sandals", bags = "Handbags, backpacks and luggage", other = "Anything else" }
[pipe.inspect_product.questions.condition]
question = "What condition is the product in?"
levels = ["Damaged beyond use", "Worn", "Like new"]
A question table carries the fields that the single form sets on the pipe:
| Field | Type | Required | Description |
|---|---|---|---|
question |
string | Yes | The question template, as the single form's question. |
options |
table | No | The options of a choice question, as the single form's. |
levels |
array | No | The levels of a rating question, as the single form's. |
criteria |
table | No | What a yes and a no mean, for a yes/no question, as the single form's. |
threshold |
number | No | For a yes/no question, as the single form's. |
Every question is answered independently over the same evidence, and none sees another's answer. A question that depends on another's answer belongs to a later pipe in a PipeSequence, which reads the earlier verdict as an input.
Validation rules (several questions):
questionsMUST hold at least one question, and each key MUST be a field name the field name rules admit.- A question table MUST carry a non-empty
question, and MAY carryoptions,levels,criteriaandthreshold, each under the single form's rules, the question's kind being decided by its ownoptionsandlevels. Any other key MUST be rejected. options,levels,criteriaandthresholdMUST NOT be set on the pipe itself besidequestions.outputMUST be a structured concept and MUST NOT carry a multiplicity suffix. Its structure's fields MUST be exactly the question names: a field that no question answers is rejected, since nothing could fill it, and so is a question with no field to hold its verdict. Each field MUST be declaredtype = "concept", with aconcept_refnaming the verdict native of its question's kind or a concept that refines it.
The verdicts and their refusals. Each field of the output holds the verdict of the question it names, under the single form's rules for that kind. A refused question whose field is not required leaves that field absent, and an implementation SHOULD record the refusal where the run's trace shows it. A refused question whose field is required fails the run, and the error SHOULD name the question.
Inline Judgment Settings
When the model field is a table instead of a string, it defines inline model settings using the JudgmentSetting structure:
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | The model handle. |
description |
string | No | Human-readable description of this model configuration. |
Example — inline judgment settings:
[pipe.judge_is_spam]
type = "PipeJudge"
description = "Decide whether an email is spam"
inputs = { email = "Text" }
output = "YesNo"
prompt = "@email"
question = "Is the email unsolicited bulk advertising?"
model = { model = "verdict-small", description = "A fast model for screening" }
Operator: PipeCompose
Composes output by assembling data from working memory using either a template or a construct. Exactly one of template or construct MUST be provided.
Template Mode
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeCompose" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | MUST be a single concept (no multiplicity). |
template |
string or table | Yes (if no construct) |
A Jinja2 template string, or a template blueprint table with template, category, templating_style, and extra_context fields. |
When template is a string, it is a Jinja2 template rendered with the input variables. When template is a table, it MUST contain a template field (string) and a category field, and MAY contain templating_style and extra_context.
Template shorthand syntax:
MTHDS defines three shorthand patterns that a compliant preprocessor MUST expand before Jinja2 rendering:
| Pattern | Expansion | Description |
|---|---|---|
$name |
{{ name|format() }} |
Inline substitution with formatting. |
@name |
{{ name|tag("name") }} |
Block insertion with tagging. |
@?name |
{% if name %}{{ name|tag("name") }}{% endif %} |
Conditional block insertion (renders only if truthy). |
Rules:
- A shorthand pattern MUST NOT match when the character immediately following
$,@, or@?is a digit (0–9). This prevents dollar amounts (e.g.,$100) and version-like strings (e.g.,@2.0) from being treated as variables. - Dotted paths are supported:
$user.name,@doc.summary,@?extra.notes. Each segment of the dotted path MUST be a valid identifier. - When a matched name ends with a
.(dot), the preprocessor MUST strip the trailing dot from the variable name and place it after the expanded expression (treating it as sentence punctuation). - Raw Jinja2 syntax (
{{ }},{% %}) MUST always be accepted alongside the shorthands.
These shorthands apply to the template field of PipeCompose, the prompt and system_prompt fields of PipeLLM, the prompt and negative_prompt fields of PipeImgGen, the prompt field of PipeSearch, the prompt and question fields of PipeJudge and the question of each of its questions. See Pipes — Operators: Template Mode for the full reference on categories and filters.
Template blueprint fields (table form):
| Field | Type | Required | Description |
|---|---|---|---|
template |
string | Yes | The Jinja2 template string. |
category |
string | Yes | Template category. Values: basic, expression, html, markdown, mermaid, llm_prompt, img_gen_prompt. |
templating_style |
table | No | Rendering style configuration. See Templating Style below. |
extra_context |
table | No | Additional context variables for template rendering. |
Templating Style
The templating_style field controls how template output is formatted, particularly useful for templates that produce prompts for different LLM providers.
| Field | Type | Required | Description |
|---|---|---|---|
tag_style |
string | Yes | How variables are tagged in output. Values: no_tag, ticks, xml, square_brackets. |
text_format |
string | No | Output text format. Values: plain, markdown, html, json. Default: plain. |
Validation rules (template mode):
- Every variable referenced in the template MUST correspond to a declared input.
- Every declared input MUST be referenced by at least one variable in the template. Unused inputs are rejected (see Inputs Read Through Templates).
outputMUST NOT use multiplicity brackets ([]or[N]).
Construct Mode
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeCompose" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | MUST be a single concept (no multiplicity). |
construct |
table | Yes (if no template) |
A field-by-field composition blueprint. |
The construct table defines how each field of the output concept is composed. Each key is a field name, and the value defines the composition method:
| Value form | Method | Description |
|---|---|---|
Literal (string, integer, float, boolean, array) |
Fixed | The field value is the literal. |
{ from = "path" } |
Variable reference | The field value comes from a variable in working memory. path is a dotted path (e.g., "match_analysis.score"). |
{ from = "path", list_to_dict_keyed_by = "attr" } |
Variable reference with transform | Converts a list to a dict keyed by the named attribute. |
{ template = "..." } |
Template | The field value is rendered from a Jinja2 template string. |
Nested table (no from or template key) |
Nested construct | The field is recursively composed from a nested construct. |
Validation rules (construct mode):
- The root variable of every
frompath and every template variable MUST correspond to a declared input. - Every declared input MUST be referenced by at least one
frompath or by at least one variable in a field template, nested constructs included. Unused inputs are rejected (see Inputs Read Through Templates). fromandtemplateare mutually exclusive within a single field definition.
Example — construct mode:
[pipe.compose_interview_sheet]
type = "PipeCompose"
description = "Compose the final interview sheet"
inputs = { match_analysis = "MatchAnalysis", interview_questions = "InterviewQuestion[]" }
output = "InterviewSheet"
[pipe.compose_interview_sheet.construct]
overall_match_score = { from = "match_analysis.overall_match_score" }
matching_skills = { from = "match_analysis.matching_skills" }
missing_skills = { from = "match_analysis.missing_skills" }
questions = { from = "interview_questions" }
Controller: PipeSequence
Executes a series of steps in order. A step either runs a pipe or binds a value already in working memory to a new name, and what each step stores in working memory can be consumed by the steps after it.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeSequence" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | — |
steps |
array of tables | Yes | Ordered list of steps, each a pipe step or a binding step. MUST contain at least one step. |
Each step is either a pipe step, which runs a pipe, or a binding step, which binds the value at a path in working memory to a new name (see Binding Steps). A step carrying pipe is a pipe step, and a step carrying from is a binding step. Each shape is a closed table: a pipe step carries no from, and a binding step carries no field of a pipe step other than result.
Pipe step:
| Field | Type | Required | Description |
|---|---|---|---|
pipe |
string | Yes | Pipe reference (bare, domain-qualified, or package-qualified). |
result |
string | No | Name under which the step's output is stored in working memory. It MUST take the form of a plain input name, as every stored name does. |
nb_output |
integer | No | Expected number of output items. Mutually exclusive with multiple_output. |
multiple_output |
boolean | No | Whether to expect multiple output items. Mutually exclusive with nb_output. |
batch_over |
string | No | Working memory variable to iterate over (inline batch). Requires batch_as. A dotted path such as catalog.pages is a binding followed by a batch: the path is bound by the rules of a binding step, and the step iterates over the bound list (see Dotted batch_over). A plain batch_over MUST NOT start with the reserved prefix _bound_. |
batch_as |
string | No | Name under which each item is stored for the step's pipe during inline batch iteration. Requires batch_over. It MUST take the form of a plain input name, as every stored name does. |
Binding step:
| Field | Type | Required | Description |
|---|---|---|---|
from |
string | Yes | The path to bind: a name in working memory, followed by zero or more field names, separated by dots (see Path Grammar). |
result |
string | Yes | Name under which the bound value is stored in working memory. It MUST take the form of a plain input name, as every stored name does, so it is never dotted. A binding step whose result breaks the form is rejected as method.binding_step_invalid. |
Validation rules:
stepsMUST contain at least one entry.nb_outputandmultiple_outputMUST NOT both be set on the same step.batch_overandbatch_asMUST either both be present or both be absent.batch_overandbatch_asMUST NOT be the same value.- A dotted
batch_overMUST follow the path grammar, or the step is rejected asmethod.binding_step_invalid, and its walk MUST derive a list (see Dottedbatch_over). - A step MUST carry exactly one of
pipeandfrom. A step carrying both is rejected asmethod.binding_step_invalid. A step carrying neither, such as{ result = "x" }, is neither a pipe step nor a binding step, and the schema rejects it asmethod.schema_violation, since no sharper code names it. - A binding step MUST carry
result, and MUST NOT carrynb_output,multiple_output,batch_overorbatch_as, or the step is rejected asmethod.binding_step_invalid. - A binding step's
resultMUST take the form of a plain input name, or the step is rejected asmethod.binding_step_invalid. - A binding step's
fromMUST follow the path grammar, or the step is rejected asmethod.binding_step_invalid, and its path MUST be walkable through the declared structures, or the step is rejected asmethod.field_path_unresolved(see The Concept of the Result). - A pipe step's
resultandbatch_asMUST take the form of a plain input name, or the step is rejected asmethod.identifier_invalid(see Stored Names). - A pipe step's plain
batch_overMUST NOT start with the reserved prefix_bound_, or the step is rejected asmethod.identifier_invalid(see Stored Names).
Example:
[pipe.process_document]
type = "PipeSequence"
description = "Full document processing pipeline"
inputs = { document = "Document" }
output = "AnalysisResult"
steps = [
{ pipe = "extract_pages", result = "pages" },
{ pipe = "analyze_content", result = "analysis" },
{ pipe = "generate_summary", result = "summary" },
]
Binding Steps
A binding step binds the value at a path in working memory to a new name, so that the steps after it can read one field of a larger value, or the same value under another name, without a pipe written to do it:
[concept.Invoice]
description = "An invoice received from a supplier"
[concept.Invoice.structure]
supplier_name = { type = "text", description = "The supplier's name", required = true }
total = { type = "number", description = "The total amount due", required = true }
[pipe.review_invoice]
type = "PipeSequence"
description = "Decide whether an invoice needs a manager's approval"
inputs = { invoice = "Invoice" }
output = "YesNo"
steps = [
{ from = "invoice.total", result = "total_amount" },
{ pipe = "judge_large_amount", result = "needs_approval" },
]
[pipe.judge_large_amount]
type = "PipeJudge"
description = "Decide whether an amount needs a manager's approval"
inputs = { total_amount = "Number" }
output = "YesNo"
prompt = "An invoice total: $total_amount"
question = "Is the total large enough to need a manager's approval?"
The first step binds the invoice's total field under the name total_amount, as a Number, and the judge declares exactly that input. The judge's signature names a whole concept, a Number, so it can judge any amount, and the sequence, which knows the invoice's concept, picks the field at the call site. The judge's prompt presents the amount, and the judging model sees nothing else of the invoice.
Binding steps live in a PipeSequence's steps only. A binding orders a value before the steps that read it, and only a sequence has an order: a PipeParallel branch is always a pipe step, and a value its branches need is bound by a sequence step before the parallel. PipeCondition and PipeBatch have no steps.
Path Grammar
from is a path. Its first segment, the root, names a value in working memory: an input of the sequence or the result of an earlier step. Each following segment, zero or more, names a field of the value the path has reached. Segments are separated by single dots, and each segment MUST be an identifier: a letter followed by letters, digits and underscores, matching [A-Za-z][A-Za-z0-9_]*. A segment therefore never starts with an underscore, which marks private names. Subscripts (lines[0]), expressions and whitespace are not part of the grammar: a path names fields, and anything computed is a pipe's job. A from that breaks the grammar is rejected as method.binding_step_invalid.
A path with no dot is a bare name, which binds a renamed copy of the whole value (see A Bare Name Binds a Renamed Copy).
The Concept of the Result
The result's concept is derived statically, before any run, by walking the path through declared structures. The walk starts from the root's concept and multiplicity as the sequence knows them at the binding step, which are those of the latest value stored under the root's name before that step, in step order:
- When one or more earlier steps stored a value under the root's name, the root takes the concept and multiplicity of the most recent of them, whether that is a pipe step's output (a list when the step batches or asks for several outputs), the result of a branch of a PipeParallel step with
add_each_output, or the result of an earlier binding step. - Only when no earlier step stored a value under that name does the root take the concept and multiplicity that the sequence's own
inputsdeclare for it.
A step that stores its result under the name of a sequence input therefore replaces that input for every later binding step, which walks from the concept of the replacement, not from the one the input declares. The walk then reads one segment at a time, finding the field the segment names in the structure of the concept it stands on:
| The segment names a field declared as | The walk continues into, or the result is |
|---|---|
type = "concept", concept_ref = X |
X, whose structure the next segment walks |
type = "list", item_type = "concept", item_concept_ref = X |
X, crossing a list (see Lists Map and Flatten) |
type = "text", or choices |
native.Text, a leaf |
type = "number" or type = "integer" |
native.Number, a leaf |
type = "boolean" |
native.YesNo, a leaf |
type = "date" or type = "datetime" |
native.Date, a leaf, since a Date carries a calendar date with an optional time of day |
type = "time" |
native.Time, a leaf |
type = "list" with any other item_type |
the native concept that item_type gives by the rows above, a leaf, crossing a list |
type = "dict" |
native.JSON, a leaf |
The walk reads only the concept references the structures declare. A field the table above calls a leaf holds a plain value, which has no fields of its own, so the walk always ends on it, whatever native concept it derives. A native concept reached through a concept reference is walked through its pinned definition, so page.page_view is a native.Image and page.page_view.caption is a native.Text. The exceptions are the natives whose pinned definition is a single field holding the value itself, which are leaves wherever the walk reaches them. A concept that refines another is walked through the structure it inherits. The walk never infers a concept from the shape of a value: several concepts can share one structure, and only the declaration says which of them a field holds.
A native that holds its value in a single field is a leaf wherever it is reached. A native concept whose pinned definition is a single field holding the value itself, rather than a part of it, is a leaf for the walk, and so is any concept that refines one: a path never enters it. In the pinned set, these natives are Text, whose field is text, Number, whose field is number, Time, whose field is time, and JSON, whose field is json_obj. How the walk reached the concept makes no difference: a field declared type = "number" and a field declared type = "concept" with concept_ref = "native.Number" both derive native.Number, and both end the walk. A path may end on such a concept, but no segment may follow it: from = "note.text", over a note holding a Text, is rejected as method.field_path_unresolved, and so is from = "invoice.total.number" over the invoice above, whether Invoice declares total as a number field or as a reference to native.Number. Every other native that has a structure, such as YesNo, Date, Image or Page, carries several fields and is walked through its pinned definition when a concept reference leads to it.
A plain field stays a leaf even when its native has several fields. A boolean field derives native.YesNo and a date field derives native.Date, both natives with several fields, yet the walk ends on the field itself. This makes the walk asymmetric, and the asymmetry is intended. Over an invoice declaring a boolean field paid, the one-step path from = "invoice.paid.probability" is rejected as method.field_path_unresolved before any run. Binding invoice.paid first, with { from = "invoice.paid", result = "paid" }, stores a YesNo carrying only the field's value, and a later step's from = "paid.probability" then validates, because its walk starts from a YesNo and follows its pinned definition. A plain boolean carries no probability, so that later step records an absence at run time.
When the walk crosses no list, the result is a single value of the concept the walk ends on. When it crosses at least one list, the result is a variable-length list of that concept, X[]. A bare name has no segment to walk, and its result takes the root's concept and multiplicity unchanged. A step that reads the result is checked against the derived concept and multiplicity exactly as it would be against a pipe's output, and a binding step that ends the sequence is checked against the sequence's output the same way.
The walk refuses the path, and the step is rejected as method.field_path_unresolved, when:
- a segment names no field of the structure it walks;
- a segment follows a leaf: a field the table above calls a leaf, or, however the walk reached it, a native whose pinned definition is a single field holding the value itself, such as
Text,Number,TimeorJSON, or any concept that refines one of them; - a segment follows a
dictfield, or alistfield with noitem_type; - a segment walks a concept that has no structure, which is any concept declared with neither a
structurenorrefines, any of the structureless nativesDynamic,AnythingandComposite, and any concept that refines one of them; - the path ends on a
listfield with noitem_type, whose items have no concept to derive.
The diagnostic names the segment that failed and lists the fields that were available at that point, so that a typo can be repaired from the message alone.
Lists Map and Flatten
When the walk crosses a list, whether the root is a list or a field along the path is one, the rest of the path is applied to every item. Every list crossed is flattened into one, so the result is always a single list, never a list of lists. Wherever a segment holds nothing, on one item or on a single value before the first list is reached, that part of the path contributes no items: such items are dropped, the way a PipeBatch drops absent branch results. An empty list is a valid result, and a list result is never absent.
pages.page_view, overpagesholdingPage[], givesImage[], one image per page that has a page view.order.lines.amount, wherelinesis a list ofOrderLineand each line declares a number fieldamount, givesNumber[].shipments.parcels, overshipmentsholdingShipment[]whoseparcelsfield is a list ofParcel, gives one flatParcel[].
Absence
A binding step introduces no new kind of absence: like a pipe's output under the optionality model, a single result is either a value or a recorded absence, and a list result is never absent.
- The root is absent. The root is read like a plain input, so the binding step lifts the way a pipe with an absent plain input does: a single result is recorded as a skipped absence, with provenance pointing to the root's absence. A list result is an empty list instead, since a plural slot is never absent.
- The path reaches nothing. When the result is a single value and a segment holds nothing, at the leaf or at any segment before it (
invoice.scan.urlon an invoice with noscan), the result is a recorded absence whose provenance names the segment that held nothing. This is not an error. When the result is a list, the list rule applies instead. - Statically, a single result may be absent when its root may be absent, judged from the same latest value stored under the root's name that gives the root its concept (see The Concept of the Result), or when its path walks a field that may hold nothing, meaning one that is not
requiredand has nodefault_value. Structure fields default torequired = false, so most single-value bindings may be absent unless the concept marks the field required, which is correct, since the data may lack the field. A list result is never considered maybe-absent.
What follows is the existing machinery: a step reading the result through a plain input lifts when it is absent, a step reading it through an optional (?) input runs and guards the read, and a sequence whose output can be absent MUST declare its output ?.
The Value Is a Copy
The result is a new value holding a deep copy of the value at the path, taken when the step runs. It is never an alias of the root: a later step that stores a new value under the root's name, or anything that changes the root's content, leaves the bound value as it was, and a change to the bound value leaves the root as it was. The whole value at the path is copied, every field of a concept included, never field by field, so a bound Image keeps its caption and every other field it holds.
A leaf holding a plain value is stored as its derived native concept, with the value in that concept's pinned field: a text field's string becomes a Text whose text is the string, a number or an integer becomes a Number, a boolean a YesNo, a date a Date, a datetime a Date carrying both its date and its time of day, a time a Time, and a dict a JSON. The new value has its own identity, and the binding step is its producer wherever an implementation records which step produced a value.
A Bare Name Binds a Renamed Copy
A path with no field segment binds a deep copy of the whole value under a new name, with the same concept and multiplicity:
steps = [
{ from = "departure_board", result = "board" },
{ pipe = "announce_delays", result = "announcement" },
]
Working memory matches a pipe's inputs by name, so this is how a sequence hands a value to a pipe whose input has another name: here announce_delays declares board, and the sequence holds the value as departure_board.
Dotted batch_over
A pipe step whose batch_over is a dotted path is a binding followed by a batch. { pipe = "describe_view", batch_over = "pages.page_view", batch_as = "page_view" } behaves exactly as a binding step of pages.page_view under a private name starting with the reserved prefix _bound_, which no other step can read, followed by the same pipe step with batch_over naming that private name. The path follows every rule of a binding step. Its root takes the concept and multiplicity of the latest value stored under its name before the step, in step order, and those the sequence's inputs declare only when no earlier step stored that name (see The Concept of the Result). Its concept is derived by the same walk, it maps and flattens across lists, so a dotted path over a list of catalogs iterates over the pages of all of them, and it lifts and records absences the same way.
A dotted batch_over MUST bind a list: its walk MUST cross at least one list, whether the root is a list, a field along the path is one, or the path ends on a list field. A dotted batch_over whose walk derives a single value, such as batch_over = "invoice.supplier_name" over the invoice above, is rejected before any run, and an implementation reports it the way it reports a batch_over naming a value that is not a list. A dotted batch_over that breaks the path grammar, such as a..b or pages[0].x, is rejected as method.binding_step_invalid, as a binding step's from would be.
A dotted batch_over binds, and only a sequence's steps can bind, so a PipeParallel branch MUST NOT carry one: a branch whose batch_over is a dotted path is rejected as method.binding_step_invalid, which the schema catches, exactly as a binding step placed in a branch is. A plain batch_over on a branch is unaffected. A branch that needs to iterate over a list held in a field gets it from the calling sequence, which binds the field in a step before the PipeParallel step, so that the branch's plain batch_over names the bound list.
Stored Names
A stored name is a name under which a step stores a value in working memory: a pipe step's result, the result of a PipeParallel branch, the batch_as of a pipe step or a branch, under which each item of the batch is stored for the pipe that the step or branch runs, a PipeBatch's input_item_name, under which each item is stored for its branch pipe, and a binding step's result.
Every stored name MUST take the form of a plain input name, matching [a-z][a-z0-9_]*. A step stores a value for a later step to read, and an input reads a stored value only under a plain name, so a stored name takes the form of an input name and every value a step stores can be read by an input. A name in any other form, such as Pages or a.b, would store a value that no input can read. A step such as { pipe = "extract_pages", result = "Pages" } is rejected, which the schema catches, and a compliant implementation SHOULD report the refusal as method.identifier_invalid, the code it reports for an input name that breaks the same form. A binding step's result is a stored name held to this rule like the others, and a binding step that breaks it is rejected as method.binding_step_invalid instead, since that code reports every fault of a binding step's own shape.
Names starting with _bound_ are reserved for the private names under which an implementation binds a dotted batch_over. A plain input name starts with a letter, so no stored name can take the prefix: a step such as { pipe = "extract_pages", result = "_bound_pages" } breaks the form above and is refused for it. A plain batch_over reads a name in working memory rather than storing one, and it MUST NOT start with _bound_ either, so that no step reads a value an implementation bound under a private name: a step such as { pipe = "describe_page", batch_over = "_bound_pages", batch_as = "page" } is rejected, which the schema catches, and a compliant implementation SHOULD report the refusal as method.identifier_invalid. The reservation keeps every name an author writes apart from the names an implementation binds, wherever in a run's working memory the two might meet.
Validation Surface
A compliant implementation SHOULD report a binding step's own faults, and a stored name or a plain batch_over that breaks the rules of Stored Names, under these codes of the core error registry, each linked to its page:
| Code | Fault | Caught by |
|---|---|---|
method.binding_step_invalid |
A step carrying both pipe and from; a binding step lacking result or carrying nb_output, multiple_output, batch_over or batch_as; a binding step whose result is not a plain input name; a from that breaks the path grammar; a dotted batch_over that breaks the path grammar; a binding step in a PipeParallel's branches; a dotted batch_over in a PipeParallel's branches. |
The schema. |
method.field_path_unresolved |
A path the declared structures cannot walk, under the refusals listed in The Concept of the Result. | Validation of the bundle, which reads the concepts' structures, before any run. |
method.identifier_invalid |
A pipe step's or a PipeParallel branch's result or batch_as, or a PipeBatch's input_item_name, that does not take the form of a plain input name, such as Pages, a.b or _bound_pages; a pipe step's or a branch's plain batch_over starting with the reserved prefix _bound_ (see Stored Names). |
The schema. |
A binding step can also cause faults that are not its own, and an implementation reports each of them the way it reports the same fault caused by a pipe step: a root that is neither an input of the sequence nor stored by an earlier step, whose diagnostic asks for the concept whose structure holds the path; a step that reads the result through an input declaring a concept or multiplicity incompatible with the ones the binding derives; a binding step ending the sequence whose derived concept or multiplicity does not match the sequence's declared output; and a dotted batch_over whose walk derives a single value, which is reported as a batch_over naming a value that is not a list. A maybe-absent result escaping a sequence whose output is not ? is method.optional_unhandled, as Optionality defines it.
Controller: PipeParallel
Executes multiple sub-pipes concurrently. Each branch operates independently, then the branch results are combined into the pipe's declared output.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeParallel" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | Combined output concept. MUST be Composite or a structured concept whose fields match branch result names. MUST NOT use multiplicity. |
branches |
array of tables | Yes | List of pipe steps to execute concurrently. |
add_each_output |
boolean | No | If true, each branch's output is individually added to working memory under its result name. Default: false. |
Validation rules:
branchesMUST contain at least one entry.outputMUST beCompositeor a structured concept.outputMUST NOT use multiplicity brackets ([]or[N]).- For structured output, required fields MUST be produced by matching branch
resultnames and branch output concepts MUST be compatible with the corresponding fields. add_each_outputcontrols only whether branch results are also exposed individually in working memory. It does not control the main output.- Each branch is a pipe step, in the format of a PipeSequence pipe step. A branch MUST NOT be a binding step: a branch carrying
fromis rejected asmethod.binding_step_invalid, and a value the branches need is bound by a sequence step before the parallel. - A branch MUST NOT carry a dotted
batch_over, which is a binding followed by a batch (see Dottedbatch_over): such a branch is rejected asmethod.binding_step_invalid, while a plainbatch_overon a branch is unaffected. - A branch's
resultandbatch_asMUST take the form of a plain input name, and its plainbatch_overMUST NOT start with the reserved prefix_bound_, or the branch is rejected asmethod.identifier_invalid, as a sequence's pipe step is (see Stored Names).
Example:
[pipe.extract_documents]
type = "PipeParallel"
description = "Extract text from both CV and job offer concurrently"
inputs = { cv_pdf = "Document", job_offer_pdf = "Document" }
output = "Composite"
add_each_output = true
branches = [
{ pipe = "extract_cv", result = "cv_pages" },
{ pipe = "extract_job_offer", result = "job_offer_pages" },
]
Controller: PipeCondition
Routes execution to different pipes based on an evaluated condition.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeCondition" |
Yes | — |
description |
string | Yes | — |
inputs |
table | No | — |
output |
string | Yes | — |
expression_template |
string | Conditional | A Jinja2 template that evaluates to a string matching an outcome key. Exactly one of expression_template or expression MUST be provided. |
expression |
string | Conditional | A static expression string. Exactly one of expression_template or expression MUST be provided. |
outcomes |
table | Yes | Maps outcome strings to pipe references. MUST have at least one entry. |
default_outcome |
string | Yes | The pipe reference (or special outcome) to use when no outcome key matches. |
add_alias_from_expression_to |
string | No | If set, stores the evaluated expression value in working memory under this name. |
Special outcomes:
Certain string values in outcomes values and default_outcome have special meaning and are not treated as pipe references:
| Value | Meaning |
|---|---|
"fail" |
Abort execution with an error. |
"continue" |
Skip this branch and continue without executing a sub-pipe. |
Example:
[pipe.route_by_document_type]
type = "PipeCondition"
description = "Route processing based on document type"
inputs = { doc_request = "DocumentRequest" }
output = "Text"
expression_template = "{{ doc_request.document_type }}"
default_outcome = "continue"
[pipe.route_by_document_type.outcomes]
technical = "process_technical"
business = "process_business"
legal = "process_legal"
Controller: PipeBatch
Maps a single pipe over each item in a list input, producing a list output.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"PipeBatch" |
Yes | — |
description |
string | Yes | — |
inputs |
table | Yes | MUST include an entry whose name matches input_list_name. |
output |
string | Yes | — |
branch_pipe_code |
string | Yes | The pipe reference to invoke for each item. |
input_list_name |
string | Yes | The name of the input that contains the list to iterate over: a plain input name. |
input_item_name |
string | Yes | The name under which each individual item is passed to the branch pipe. It MUST take the form of a plain input name, as every stored name does. |
Validation rules:
input_list_nameMUST be a plain input name. A compliant implementation MUST reject a dotted name such ascatalog.pages, as it rejects a dotted input name, and SHOULD report the refusal asmethod.identifier_invalid. To map a pipe over a list held in a field, the PipeBatch declares the list itself as its input (pages = "Page[]", withinput_list_name = "pages"), and the calling sequence hands the field to it under that name with a binding step ({ from = "catalog.pages", result = "pages" }, see Binding Steps).input_list_nameMUST exist as a key ininputs.input_item_nameMUST NOT be empty.input_item_nameMUST NOT equalinput_list_name.input_item_nameMUST NOT equal any key ininputs.input_item_nameMUST take the form of a plain input name, or the pipe is rejected asmethod.identifier_invalid(see Stored Names).
Example:
[pipe.batch_generate_jokes]
type = "PipeBatch"
description = "Generate a joke for each topic"
inputs = { topics = "Topic[]" }
output = "Joke[]"
branch_pipe_code = "generate_joke"
input_list_name = "topics"
input_item_name = "topic"
Pipe Reference Syntax
Every location in a .mthds file that references another pipe supports three forms:
| Form | Syntax | Example | Resolution |
|---|---|---|---|
| Bare | pipe_code |
"extract_clause" |
Resolved within the current bundle and its domain. |
| Domain-qualified | domain.pipe_code |
"legal.contracts.extract_clause" |
Resolved within the named domain of the current package. |
| Package-qualified | alias->domain.pipe_code |
"docproc->extraction.extract_text" |
Resolved in the named domain of the dependency identified by the alias. |
Pipe references appear in:
steps[].pipe(PipeSequence)branches[].pipe(PipeParallel)outcomesvalues (PipeCondition)default_outcome(PipeCondition)branch_pipe_code(PipeBatch)
Pipe definitions (the [pipe.<pipe_code>] table keys) are always bare snake_case names. Namespacing applies only to pipe references.
Concept Reference Syntax
Every location that references a concept supports three forms, symmetric with pipe references:
| Form | Syntax | Example | Resolution |
|---|---|---|---|
| Bare | ConceptCode |
"ContractClause" |
Resolved in order: native concepts → current bundle → same domain. |
| Domain-qualified | domain.ConceptCode |
"legal.contracts.NonCompeteClause" |
Resolved within the named domain of the current package. |
| Package-qualified | alias->domain.ConceptCode |
"acme->legal.ContractClause" |
Resolved in the named domain of the dependency identified by the alias. |
The disambiguation between concepts and pipes in a domain-qualified reference relies on casing:
snake_casefinal segment → pipe codePascalCasefinal segment → concept code
Concept references appear in:
inputsvalues — the string form, or theconceptkey of the expanded formoutputrefinesconcept_refanditem_concept_refin structure field blueprints
Complete Bundle Example
domain = "joke_generation"
description = "Generating one-liner jokes from topics"
main_pipe = "generate_jokes_from_topics"
[concept.Topic]
description = "A subject or theme that can be used as the basis for a joke."
refines = "Text"
[concept.Joke]
description = "A humorous one-liner intended to make people laugh."
refines = "Text"
[pipe.generate_jokes_from_topics]
type = "PipeSequence"
description = "Generate 3 joke topics and create a joke for each"
output = "Joke[]"
steps = [
{ pipe = "generate_topics", result = "topics" },
{ pipe = "batch_generate_jokes", result = "jokes" },
]
[pipe.generate_topics]
type = "PipeLLM"
description = "Generate 3 distinct topics suitable for jokes"
output = "Topic[3]"
prompt = "Generate 3 distinct and varied topics for crafting one-liner jokes."
[pipe.batch_generate_jokes]
type = "PipeBatch"
description = "Generate a joke for each topic"
inputs = { topics = "Topic[]" }
output = "Joke[]"
branch_pipe_code = "generate_joke"
input_list_name = "topics"
input_item_name = "topic"
[pipe.generate_joke]
type = "PipeLLM"
description = "Write a clever one-liner joke about the given topic"
inputs = { topic = "Topic" }
output = "Joke"
prompt = "Write a clever one-liner joke about $topic. Be concise and witty."