This does not create ever-growing workflow objects ​
Explicit propagation does not mean every value accumulates everything ever observed.
This is not the intended model:
A {x}
↓
B {x,y}
↓
C {x,y,z}
↓
D {x,y,z,w}
↓
EverythingEverKnown { ... }TPF encourages small steps and small types precisely because pipelines make decomposition cheap.
State can continually change shape:
A {x}
↓
B {x,y}
↓
C {y,z}
↓
D {z,w}
↓
E {w,result}Data can disappear once no reachable downstream computation requires it.
A useful way to think about each pipeline value is:
the immutable closure of data required by the computation that remains.
That is very different from a mutable WorkflowContext carrying a bag of properties.
And because authored steps remain small functions, their real dependencies remain small too.
Duplication can be cheaper than lookup ​
Carrying data has a cost.
So does retrieving it.
Consider two alternatives.
carry immutable data
↓
serialize a few extra fields
↓
possibly compress over transport
↓
continue immediatelyversus:
carry ID
↓
acquire database connection
↓
network round trip
↓
query planning/execution
↓
deserialize result
↓
continueFor IDs, names, classifications, hashes, monetary values, small records and modest amounts of text, the bandwidth cost is frequently trivial.
The database round trip is not.
More importantly, carrying the value preserves exactly what this execution knew.
Re-querying may return what the world knows now.
Those are not necessarily the same thing.
So explicit propagation can simultaneously improve:
performance, temporal consistency, determinism and architectural clarity.
Event-driven systems already discovered this ​
Kafka and other event-driven architectures make this tradeoff constantly.
An event might evolve through:
OrderPlaced
↓
OrderValidated
↓
OrderPriced
↓
OrderReadyForFulfilmentEach event often repeats contextual information that could theoretically be reconstructed elsewhere.
Why?
Because downstream consumers become dramatically easier to operate when they do not need synchronous calls to several other systems before doing anything useful.
Event enrichment trades storage and bandwidth for:
- fewer synchronous dependencies;
- lower temporal coupling;
- replayability;
- observability;
- resilience;
- consistency about what a consumer actually saw.
TPF makes a similar tradeoff.
But it has an important advantage.
TPF values are not enterprise events ​
Kafka events frequently become contracts for consumers the producer does not control.
Over time this can produce the notorious overloaded event:
CustomerUpdatedV19 {
everythingAnyoneHasEverNeeded
}TPF operates in a different environment.
A pipeline is a closed, compiler-known typed computation.
A → B → C → DTPF knows its:
types
branches
cardinalities
composition
release identitySo values can be shaped for the computation they actually serve.
ValidatedInvoice
↓
InvoiceAnalysisRequest
↓
InvoiceReview
↓
ConfirmedInvoice
↓
ArchiveOutcomeThese are not generic messages thrown onto a bus in the hope that unknown consumers will find them useful.
They are purpose-built immutable states of a known computation.
That makes explicit enrichment much less dangerous.
Types are not merely DTOs ​
This leads to one of the more important consequences of the TPF model.
A type such as:
InvoiceReviewis not merely a DTO passed between two Java methods.
It can simultaneously be:
- the output of a computation;
- the input to another computation;
- immutable pipeline state;
- a persisted historical fact;
- an observable execution value;
- a replayable result;
- something another Query can retrieve later;
- part of the release-pinned application contract.
That gives TPF types considerably more architectural significance than conventional transport objects.
The familiar application landscape of:
DTO
Entity
API model
Event
Command
Persistence model
Read model
Internal modeldoes not necessarily disappear.
But neither does TPF begin by assuming all those representations must exist.
It begins with:
What value did the computation produce?
That value is the primary architectural fact.
Persistence turns computation into history ​
If pipeline values are persisted, execution naturally produces a temporal data model.
Consider an invoice workflow:
InvoiceAnalysisRequest
↓
InvoiceReview
↓
ConfirmedInvoice
↓
ArchiveOutcome
↓
NotificationOutcomePersist those values and the application acquires history almost automatically.
Suppose:
InvoiceReview
documentId = 42
recommendedProperty = Aand later:
ConfirmedInvoice
documentId = 42
recommendedProperty = A
confirmedProperty = BThe application does not need a mutable workflow registry containing:
recommendationChanged=trueThe history already says what happened.
Ask the data:
InvoiceReview(documentId = 42)
ConfirmedInvoice(documentId = 42)and compare them.
The audit trail is not something reconstructed from logs after the fact.
It is the computation's immutable output.
This is not event sourcing ​
There is an important boundary.
TPF does not require an application to reconstruct its entire current state by folding every pipeline value from the beginning of time.
Persisted values can serve as:
business facts
execution history
audit records
replay material
observability data
queryable application statewithout requiring strict event-sourcing architecture.
Applications remain free to create projections and query models.
The important distinction is that those projections do not need to become hidden execution authority.
The pipeline already has its facts.
Large data travels by reference ​
The same architecture extends naturally to files and other large payloads.
Carrying a 50 MB document through every serialized pipeline value would be absurd.
But throwing away its identity and repeatedly locating it in external storage would recreate hidden coupling.
TPF therefore carries:
PayloadReferenceinstead.
PDF
↓
PayloadReference
↓
typed pipeline state
↓
materialize when requiredThe computation retains immutable identity and provenance without duplicating the bytes.
This produces a remarkably small data vocabulary:
small known data
→ carry the value
large known data
→ carry PayloadReference
new external knowledge
→ Query
external side effect
→ CommandThat is close to a complete data architecture.
The cost is storage ​
Immutable history accumulates.
That is not a surprise, nor is it unique to TPF.
Kafka installations, event stores, analytical systems and audit-heavy architectures all eventually distinguish operational data from historical data.
TPF can do the same:
HOT
────────────────
active executions
recent typed values
operational queries
fast replay
current UI
↓ retention / archival
COLD
────────────────
historical typed values
audit
forensics
analytics
long-term replayCold storage is not an architectural embarrassment.
It is the natural lifecycle of immutable history.
And TPF's data architecture is unusually friendly to long-term replay. Immutable typed values can be interpreted alongside generated contract hashes, step and type identities, durable execution and release identity, timestamps, and PayloadReference provenance.
Those facts live on different current surfaces; persistence does not automatically wrap every business value in one historical envelope. A production archive therefore needs an archive schema and restore contract that retain the context it needs, map stored representations back to canonical types, and keep referenced payloads resolvable.
That is an implementation obligation, not an architectural impediment. TPF does not yet prescribe one universal archive format, but its data architecture makes restorable history a coherent extension rather than a forensic reconstruction from unstructured application logs.