Journal format¶
The on-disk format of a .atj study file, record by record.
A journal is a plain append-only text file, one JSON object per line, and that is
the whole design: a study is the log of the mutations that produced it, so
resuming is a replay and there is nothing to migrate, lock or repair. It also
means the file is readable with the tools you already have — wc -l counts the
records, tail -f follows a run, grep '"kind":"set-values"' lists the finished
trials.
You do not need this page to use atune. It is here for the cases where the file itself is the interface: reading a study from another language, auditing what a worker wrote, or writing a tool over the log. Nothing on the page is a promise about the values — the format version at the top is the compatibility contract, and a reader must refuse a version higher than it knows.
The record types and their field names are generated from
crates/atune_core/src/storage/journal/record.rs, including the serde attributes
that decide the wire spelling, and the worked example at the end is the byte golden
a test compares the writer against — quoted, not retyped.
Generated file. Do not edit between the pragmas below — run
cargo dev generate-all --mode write instead. Everything outside them is
hand-written and is never touched.
The file¶
A journal is JSON Lines: one JSON object per line, appended and never rewritten. The first line is a header; every line after it is one operation. Both carry a record field naming which they are, so head -1 identifies a file and grep finds a record kind in it without a parser.
This build writes format version 5 (FORMAT_VERSION in crates/atune_core/src/storage/journal/record.rs). A journal whose header names a higher version is refused rather than guessed at; a lower one still opens.
The header line¶
"record": "header", then:
format— The on-disk format version —FORMAT_VERSIONwhen this build wrote it.atune— Theatune_coreversion that created the journal, for forensics.created_at_millis— When the journal was created, from the creator'sClock.created_by— Which worker created it.
An operation line¶
"record": "op", then:
seq— The record's position in the file: 1 for the first op.worker— The worker that appended it.at_millis— The writer's clock reading when it did.op— What happened.
The operations¶
There is exactly one variant per mutating Storage method, and the kebab-case kind is that method's name — so grep '"kind":"transition"' is a complete audit of a study's state machine. 22 of them:
"kind": "create-study"¶
A study was created.
Op::CreateStudy. Fields:
study— The identity assigned by the writer (derived from replay position).config— The stored configuration.
"kind": "create-trial"¶
A trial was created.
Op::CreateTrial. Fields:
study— The owning study.trial— The identity assigned by the writer.number— The per-study ordinal assigned by the writer.template— The template it was created from, if any.
"kind": "transition"¶
A trial changed state. Only ever written by the caller that won the transition, so replay applies it unconditionally.
Op::Transition. Fields:
trial— The trial.from— The state it was in.to— The state it is in now.
"kind": "params-batch"¶
Sampled parameters were recorded, all-or-nothing.
Op::ParamsBatch. Fields:
trial— The trial.params— The whole batch, in the order it was written.
"kind": "report"¶
An intermediate value was reported.
Op::Report. Fields:
trial— The trial.step— The step, in the study's resource unit.values— The reported values.
"kind": "set-values"¶
Final objective values were recorded.
Op::SetValues. Fields:
trial— The trial.values— The values, one per direction.
"kind": "set-error"¶
A failure message was recorded.
Op::SetError. Fields:
trial— The trial.message— The message, verbatim.
"kind": "heartbeat"¶
A heartbeat was recorded.
Op::Heartbeat. Fields:
trial— The trial.at_millis— The caller's timestamp, never the writer's clock.
"kind": "set-study-user-attr"¶
A study user attribute was inserted or replaced.
Op::SetStudyUserAttr. Fields:
study— The owning study.key— The user key; theatune:namespace is reserved.value— The portable JSON value.
"kind": "set-trial-user-attr"¶
A raw trial user attribute was inserted or replaced.
Op::SetTrialUserAttr. Fields:
trial— The owning trial.key— The user key; theatune:namespace is reserved.value— The portable JSON value.
"kind": "put-state"¶
A state blob was written.
Op::PutState. Fields:
scope— What it belongs to.blob— The blob.
"kind": "lifecycle-reserve"¶
A lifecycle-managed trial was reserved and created under the budget.
Op::LifecycleReserve. Fields:
study— The owning study.trial— The identity assigned by the writer.number— The per-study ordinal assigned by the writer.template— The template it was created from, if any.managed— Explicit schema marker distinguishing lifecycle records from rawcreate-trialrecords.
"kind": "lifecycle-abandon"¶
A reserved lifecycle trial was conditionally abandoned.
Op::LifecycleAbandon. Fields:
trial— The reserved trial.reason— The durable failure reason.
"kind": "lifecycle-claim"¶
A reservation or paused trial was claimed and started at a new epoch.
Op::LifecycleClaim. Fields:
trial— The trial being claimed.from— The state required by the claim target.worker— The new owner.heartbeat_millis— The first heartbeat supplied by the owner.epoch— The new ownership epoch.heartbeat_seq— The first heartbeat sequence in the new epoch.params— Parameters written atomically with the claim.state— Optional trial-owned state written atomically with the claim.
"kind": "lifecycle-renew"¶
A lifecycle owner renewed its heartbeat.
Op::LifecycleRenew. Fields:
trial— The owned trial.worker— The owner fence.epoch— The ownership epoch.heartbeat_millis— The caller's new heartbeat timestamp.heartbeat_seq— The resulting heartbeat sequence.
"kind": "lifecycle-fail-stale"¶
A stale trial was conditionally failed from a complete observation.
Op::LifecycleFailStale. Fields:
trial— The observed trial.observed_state— The complete observed state.owner— The complete observed owner marker.epoch— The complete observed epoch.heartbeat_millis— The complete observed heartbeat timestamp.heartbeat_seq— The complete observed heartbeat sequence.now_millis— The timestamp used for the stale comparison.grace_millis— The stale grace threshold.reason— The durable failure reason.intent— The initial outbox intent, absent on pre-v4 records.
"kind": "lifecycle-fenced"¶
One ownership-fenced lifecycle mutation.
Op::LifecycleFenced. Fields:
trial— The owned trial.worker— The owner fence.epoch— The ownership epoch.mutation— The mutation payload.
"kind": "lifecycle-terminal"¶
One ownership-fenced terminal transition.
Op::LifecycleTerminal. Fields:
trial— The owned trial.worker— The owner fence.epoch— The ownership epoch.outcome— The terminal payload, including values/error and state.intent— The initial outbox intent, absent on pre-v4 records.
"kind": "outbox-advance"¶
Atomically persists callback state/effects and advances one outbox.
Op::OutboxAdvance. Fields:
key— The durable terminal-finalization key.phase— The phase being committed.sampler_state— Replacement sampler seam state, if supplied.scheduler_state— Replacement scheduler seam state, if supplied.effects— Effects discovered at this callback boundary.
"kind": "outbox-effect-complete"¶
Durably acknowledges one externally executed outbox effect.
Op::OutboxEffectComplete. Fields:
key— The effect being acknowledged.
"kind": "outbox-replacement"¶
Atomically creates a replacement and marks its effect complete.
Op::OutboxReplacement. Fields:
effect— The idempotency key of the replacement effect.study— The study receiving the replacement.trial— The identity assigned by the writer.number— The per-study ordinal assigned by the writer.template— The replacement's fixed template, if any.attempt— The retry attempt metadata.retry_of— The failed source trial when this is a retry.
"kind": "lifecycle-finalized-state"¶
Replaces a state blob on a terminal lifecycle trial.
Op::LifecycleFinalizedState. Fields:
trial— The terminal trial receiving the state.scope— The exact trial-owned scope.blob— The replacement blob.
Why the floats are special¶
A trial may report NaN or ±inf — a diverging run is a real observation
(Storage::report stores values verbatim, non-finite ones included). JSON
has no such literals, and serde_json silently writes null for them,
which then fails to deserialize back into an f64. Every float this module
writes therefore goes through JsonF64, which encodes the three
non-finite values as the strings "NaN", "Infinity" and "-Infinity"
and everything else as a plain JSON number.
A whole study, byte for byte¶
crates/atune_core/tests/goldens/journal_full_study_v5.jsonl — the journal of a 5-trial study, committed and compared byte for byte by crates/atune_core/tests/journal_goldens.rs, which also replays it back into the study that produced it. Regenerate it deliberately with ATUNE_BLESS_JOURNAL_GOLDEN=1 cargo test -p atune_core --test journal_goldens; the only field normalised is the writer's atune version, which would otherwise force a regeneration on every release.
{"record":"header","format":5,"atune":"0.0.0-GOLDEN","created_at_millis":1000000,"created_by":0}
{"record":"op","seq":1,"worker":0,"at_millis":1000000,"op":{"kind":"create-study","study":0,"config":{"name":"golden","directions":["Minimize","Maximize"],"seed":167,"space":null,"resource_unit":"Steps","metric_names":["loss","reward"]}}}
{"record":"op","seq":2,"worker":0,"at_millis":1000000,"op":{"kind":"set-study-user-attr","study":0,"key":"team","value":"control"}}
{"record":"op","seq":3,"worker":0,"at_millis":1000000,"op":{"kind":"create-trial","study":0,"trial":0,"number":0}}
{"record":"op","seq":4,"worker":0,"at_millis":1000000,"op":{"kind":"transition","trial":0,"from":"Waiting","to":"Running"}}
{"record":"op","seq":5,"worker":0,"at_millis":1000000,"op":{"kind":"params-batch","trial":0,"params":[{"name":"lr","dist":{"Float":{"low":0.00001,"high":0.1,"log":true,"step":null}},"value":{"F64":0.01}},{"name":"opt","dist":{"Cat":{"choices":{"version":1,"choices":[{"label":"adam","value":{"type":"string","value":"adam"}},{"label":"sgd","value":{"type":"string","value":"sgd"}}]}}},"value":{"Cat":0}},{"name":"layers","dist":{"Int":{"low":1,"high":8,"log":false,"step":1}},"value":{"I64":4}},{"name":"use_bn","dist":"Bool","value":{"Bool":true}}]}}
{"record":"op","seq":6,"worker":0,"at_millis":1000000,"op":{"kind":"set-trial-user-attr","trial":0,"key":"dataset","value":{"fold":2,"split":"train"}}}
{"record":"op","seq":7,"worker":0,"at_millis":1000050,"op":{"kind":"heartbeat","trial":0,"at_millis":1000050}}
{"record":"op","seq":8,"worker":0,"at_millis":1000050,"op":{"kind":"report","trial":0,"step":1,"values":[0.9,0.1]}}
{"record":"op","seq":9,"worker":0,"at_millis":1000050,"op":{"kind":"report","trial":0,"step":2,"values":[0.5,0.4]}}
{"record":"op","seq":10,"worker":0,"at_millis":1000050,"op":{"kind":"set-values","trial":0,"values":[0.42,0.58]}}
{"record":"op","seq":11,"worker":0,"at_millis":1000050,"op":{"kind":"transition","trial":0,"from":"Running","to":"Complete"}}
{"record":"op","seq":12,"worker":0,"at_millis":1001050,"op":{"kind":"create-trial","study":0,"trial":1,"number":1}}
{"record":"op","seq":13,"worker":0,"at_millis":1001050,"op":{"kind":"transition","trial":1,"from":"Waiting","to":"Running"}}
{"record":"op","seq":14,"worker":0,"at_millis":1001050,"op":{"kind":"params-batch","trial":1,"params":[{"name":"lr","dist":{"Float":{"low":0.00001,"high":0.1,"log":true,"step":null}},"value":{"F64":0.05}}]}}
{"record":"op","seq":15,"worker":0,"at_millis":1001050,"op":{"kind":"report","trial":1,"step":1,"values":[1.2,0.05]}}
{"record":"op","seq":16,"worker":0,"at_millis":1001050,"op":{"kind":"transition","trial":1,"from":"Running","to":"Pruned"}}
{"record":"op","seq":17,"worker":0,"at_millis":1002050,"op":{"kind":"create-trial","study":0,"trial":2,"number":2}}
{"record":"op","seq":18,"worker":0,"at_millis":1002050,"op":{"kind":"transition","trial":2,"from":"Waiting","to":"Running"}}
{"record":"op","seq":19,"worker":0,"at_millis":1002050,"op":{"kind":"set-error","trial":2,"message":"objective panicked: NaN loss at step 3"}}
{"record":"op","seq":20,"worker":0,"at_millis":1002050,"op":{"kind":"transition","trial":2,"from":"Running","to":"Failed"}}
{"record":"op","seq":21,"worker":0,"at_millis":1003050,"op":{"kind":"create-trial","study":0,"trial":3,"number":3,"template":{"fixed":{"lr":{"F64":0.001},"opt":{"Cat":1}},"parent":null}}}
{"record":"op","seq":22,"worker":0,"at_millis":1003050,"op":{"kind":"put-state","scope":{"Sampler":0},"blob":{"kind":"tpe","version":1,"data":{"gamma":0.25,"observations":3}}}}
{"record":"op","seq":23,"worker":0,"at_millis":1004050,"op":{"kind":"create-trial","study":0,"trial":4,"number":4}}
{"record":"op","seq":24,"worker":0,"at_millis":1004050,"op":{"kind":"transition","trial":4,"from":"Waiting","to":"Running"}}
{"record":"op","seq":25,"worker":0,"at_millis":1004050,"op":{"kind":"report","trial":4,"step":0,"values":["NaN","Infinity"]}}
{"record":"op","seq":26,"worker":0,"at_millis":1004050,"op":{"kind":"set-values","trial":4,"values":["NaN","-Infinity"]}}
{"record":"op","seq":27,"worker":0,"at_millis":1004050,"op":{"kind":"transition","trial":4,"from":"Running","to":"Complete"}}