Metadata

Every train execution produces a metadata record. It captures everything about the run: when it started, what junctions it passed through, what it was carrying, and whether it completed or failed.

FieldTypeDescription
IdlongAuto-generated primary key
NamestringTrain name (canonical interface FullName)
ExternalIdstringGUID for external references
Executorstring?Assembly that ran the train
TrainStateTrainStatePending / InProgress / Completed / Failed / Cancelled
StartTimeDateTimeWhen the train started
EndTimeDateTime?When the train finished
Inputstring?Serialized input (jsonb)
Outputstring?Serialized output (jsonb)
FailureJunctionstring?Which junction failed
FailureExceptionstring?Exception type
FailureReasonstring?Error message
StackTracestring?Stack trace if failed
FailureClassFailureClassUnclassified / Transient / Conflict / Permanent, from the registered failure classifier. Unclassified when the run did not fail or nothing classified it
ParentIdlong?The parent run's metadata id. Nothing in Trax sets it at present, so it is null for every run, including a train dispatched from a junction; see Nested Trains
ManifestIdlong?Links to manifest for scheduled trains
ReplayDecisionsOflong?The run whose recorded decisions this run replays, following that run's own link back for questions it never reached. Set by a re-queue; null for a run that asks its deciders afresh. Not a foreign key: a run whose chain names a run that no longer exists, belongs to another train, or ran without recording its decisions fails before its first junction, Permanent, rather than asking afresh
DecisionsRecordedboolTrue when the run started on a host that records decisions (AddDecisionRecording), set on its first write, so a replay can tell a run that reached no questions from one whose decisions were never recorded
ScheduledTimeDateTime?Scheduled execution time
CancellationRequestedboolCross-server cancellation flag
JunctionStartedAtDateTime?Current junction start timestamp (requires AddJunctionProgress)
CurrentlyRunningJunctionstring?Name of the currently running junction (requires AddJunctionProgress)
HostNamestring?Machine hostname where the train ran
HostEnvironmentstring?Environment type (lambda, ecs, kubernetes, server)
HostInstanceIdstring?Instance identifier (pod name, Lambda stream, etc.)
HostLabelsstring?User-provided labels as JSON (region, service, team)

The TrainState tracks the lifecycle: Pending -> InProgress -> Completed, Failed, or Cancelled. If a train fails, the metadata record captures the exception, stack trace, and which junction it happened at.

Failure Fields

When a junction throws, Trax captures structured context without modifying the original exception. The junction name, exception type, original message, and stack trace from the throw site are attached to the exception via Exception.Data["TrainExceptionData"]. When the train finishes, Metadata.AddException() reads this structured data and populates:

FieldSourceExample
FailureJunctionJunction class name where the throw occurred"ValidateInputJunction"
FailureExceptionException type short name"InvalidOperationException"
FailureReasonOriginal exception message (unmodified)"Input 'email' was null"
StackTraceStack trace from the original throw sitePoints to the junction's Run method
FailureClassThe registered IFailureClassifier's answer, set before the failure is recorded. Cancelled runs are not classified; a cancellation nothing asked for, such as an HttpClient timeout, is a failure and records Transient when the classifier has no answerConflict

FailureClass is also read back from a failure that already carries one: from TrainExceptionData, or from the message of a TrainException, which is how a remote failure arrives. Another exception type's message is never read for a class, even when it is JSON. A carried value outside the FailureClass vocabulary is recorded as Unclassified, so the row stays writable on Postgres and readable on SQLite.

The original exception is rethrown to callers with its type, message, and stack trace intact. TrainExceptionData rides along in Exception.Data for any code that wants structured context (e.g., logging, monitoring). Its TrainName is the train's canonical name, the interface FullName its metadata row's Name holds, whether the failure happened inside a junction or outside any; for a plain Core Train run without Trax.Effect it is the class name.

Host Tracking

In distributed environments (Lambda, ECS, multiple servers), every metadata record captures where the train actually executed. Host information is auto-detected at startup and stamped on each execution. See Host Tracking for details on auto-detection, custom labels, and the builder API.

Nested Trains

A junction can dispatch another train mid-execution by injecting ITrainBus. The child gets a metadata record of its own, but it is not linked to the parent's: its ParentId is not set, and passing the parent's Metadata to RunAsync throws rather than linking them. The column, the API's childCount and executionChildren, and the cleanup that clears a deleted parent's children all exist, but no Trax code path writes a parent link today.

See Mediator: Nested Trains for how to dispatch a child train.

Execution Flow

For a diagram of the full ServiceTrain lifecycle, from client request through metadata initialization to SaveChanges, see Effect Architecture.