Derivations overview
A derivation is a YAML file under derivations/ that fills one or more
fields. Folio ships six kinds, all sharing the same materialize loop.
Anatomy of a derivation file
# derivations/<anything>.yamltargets: [country_code] # required: fields this derivation writesinputs: [country] # required for cache invalidationkind: python # one of: ai | import | python | sql | http | cross_sheetscript: country_to_code # kind-specific# ...kind-specific fields...The file’s basename can be anything. Folio walks derivations/ alphabetically
for a stable execution order.
Common rules
Every kind respects the same rules:
targetslists the field names the derivation writes. They must all be declaredx-derived: trueon the contract.inputslists the fields whose changes invalidate the cache. They must exist on the contract.- Multi-target derivations must declare
output_schema(a{name: type}map) so Folio knows which output goes to which target. Single-target derivations omit it. outputistext(default; the kind returns one scalar) orjson(the kind returns a JSON object that maps target → value).
The six built-in kinds
| Kind | One-liner | Offline? | Doc |
|---|---|---|---|
| ai | Calls an LLM via AIClient (Anthropic SDK by default). | Stub-only | ai |
| import | Reads from a local CSV / JSONL / JSON file in the sheet. | Yes | import |
| python | Runs scripts/<name>.py as a subprocess. | Yes | python |
| sql | Evaluates a DuckDB SELECT-only expression against records. | Yes | sql |
| http | Calls a templated HTTP endpoint via HTTPTransport. | Stub-only | http |
| cross sheet | Joins to a sibling sheet 1:1 by primary key. | Yes | cross_sheet |
How to choose a kind
Need an LLM? aiPulling from a CSV / JSONL sidecar? importDeterministic transform of fields? python (or sql)Joining to another sheet? cross_sheetExternal API? httpHeavy aggregation across rows? sqlWhen in doubt: prefer determinism. python / sql are cheaper to cache,
faster to test, and keep provenance.jsonl from filling with hashes that
depend on remote state.
Dependency resolution between derivations
Derivations can target fields that other derivations input. Folio
topologically sorts them at materialize time (Kahn’s algorithm) and runs them
in dependency order. A cycle aborts the run with DerivationError.
targets: [area_sqkm]inputs: [country]kind: pythonscript: country_to_area
# derivations/density.yamltargets: [density]inputs: [population, area_sqkm] # depends on area_sqkmkind: pythonscript: divideFolio runs area.yaml first, then density.yaml.
Multi-target derivations
A single derivation can fill several fields when they naturally come together
(e.g. one ai call returning structured output):
targets: [industry, employee_count]inputs: [company_name, country]kind: aimodel: claude-sonnet-4-6prompt_ref: prompts/enrich.mdoutput: jsonoutput_schema: industry: string employee_count: integerThe cache stores the whole envelope keyed by one input_hash, so the two
targets always evolve together.
Cache hashing recap
input_hash covers more than inputs. It includes:
- the canonical JSON of every value listed in
inputs, - the SHA-256 of the derivation file itself,
- kind-specific extras: the resolved
prompt_bodyforai, the source file hash forimport, the foreign sheet’srecords.jsonlhash forcross_sheet.
If anything in that bag changes, the cache misses, the kind re-runs, and a fresh provenance line is appended.
Defining a custom kind
The six built-ins live under src/folio/kinds/. To add a new kind you would:
- Define a Pydantic model that extends
Derivationand registers under a discriminator value. - Implement an
execute_<kind>(derivation, inputs, *, sheet_path, ...)function that returnsdict[str, Any](target → value). - Branch on
isinstance(derivation, NewKindDerivation)insideSheet.materialize. - Add
output_schemavalidation,input_hashextras, and tests.
Folio doesn’t ship a plugin system on disk yet. Custom kinds live in your fork or the calling project’s code.
Where to next
- The six kind pages: ai, import, python, sql, http, cross_sheet.
- Materialize lifecycle — the loop that glues them all together.