flybrain/docs/design/session-framework/seed-derivation-v1.md
acamilo f043cd79c0 docs(session-framework): seed derivation v1 and the FLYSESS1 checkpoint envelope
Two specifications CONTRACT-01 owes the slices that cannot be built without them.

seed-derivation-v1: the versioned derivation of independent per-agent seeds from a recorded
master seed and stable agent ids, the exact hashed material, the lane rule that keeps a seed
away from zero (the pinned kernel RNG is an xorshift, whose state must not be zero), the
properties a composition may rely on, and the test vectors both languages reproduce.

checkpoint-envelope-v1: the exact byte layout of the new envelope per state-media-v1 section
4, with the header, canonical-JSON manifest, 112-byte payload table entries, eight-byte
aligned payloads and the digest footer; the manifest field set; what a reader enforces and in
what order; and the durable commit sequence of section 6, whose commit point is the store
manifest rename. FLYSIM01 is unchanged, refused at the magic, and stays separately readable.

Both are specifications with fixtures, not the store: generations, rotation, the capture
queue and the restore flow remain STATE-01.
2026-09-22 12:47:48 +00:00

101 lines
5.2 KiB
Markdown

# Seed derivation v1
Status: **draft 1**, 2026-09-22. Specified by CONTRACT-01 of the
[implementation guide](implementation.md), required by
[worker interfaces](workers-v1.md) section 2 before the real-agent slice. Reference
implementations: `services/flysim/crates/fly-session-types/src/seed.rs` and
`packages/session-types/src/seed.ts`; test vectors:
`services/flysim/crates/fly-session-types/fixtures/seed-vectors.json`.
## 1. What this is for
`Agent.Initialize` takes `seed`, a signed 32-bit integer, matching the current RNG input.
Workers-v1 section 2 requires that the coordinator derive independent per-agent seeds from
**its recorded master seed and stable agent IDs** under a versioned algorithm, and that the
algorithm be specified and tested before the real agent slice. This is that algorithm.
It is a reproducibility rule, not a secret: a run manifest records the master seed in the
clear, and anyone with the manifest can recompute every agent's seed. It is not a key
derivation function and must not be used as one.
`seed-derivation-v1` is part of composition identity. Changing any byte of it requires a new
identifier (`seed-derivation-v2`), because two runs that agree on every other identity but
disagree here are not the same experiment.
## 2. Inputs
| Input | Type | Source |
| --- | --- | --- |
| `masterSeed` | `U64` decimal string | Recorded once per run by the application/supervisor |
| `agentId` | `Id` | The configured agent identity, stable across restarts and epochs |
Both are the ipc-v1 section 2 scalars. An `agentId` that is not an `Id` is an error, not
something to normalize. The master seed is the whole 64-bit range: a 32-bit master seed would
be no wider than the seed it derives.
## 3. Derivation
```text
material = "flybrain/seed-derivation-v1" LF masterSeed LF agentId LF
digest = SHA-256(material)
lanes = digest read as eight big-endian uint32 values, in order
seed = the first nonzero lane, reinterpreted as a two's-complement int32
```
`LF` is one `0x0a` byte. `masterSeed` is its canonical decimal form: `"0"`, or no leading
zero. The prefix is a domain separator, so a digest from this algorithm can never collide with
one taken over some other pair of strings.
Zero lanes are skipped because the pinned kernel's RNG is an xorshift generator, whose state
must not be zero: a derivation that could hand out `0` would silently produce a stalled
generator. If every one of the eight lanes were zero, the material is rehashed with a counter
suffix (`material || "1" LF`, then `"2" LF`, then `"3" LF`) and the search repeats; no input
has ever needed it, and four rounds exhausted is an error rather than a fallback seed.
The seed is the *negative* number when the lane's high bit is set. That is deliberate: the
existing RNG input is a signed 32-bit integer, and half the range is negative.
## 4. Properties
- **Deterministic.** The seed is a function of the two recorded inputs and nothing else: not
of wall time, agent order, port assignment, worker process or thread count.
- **Independent per agent.** Distinct agent IDs give unrelated seeds; there is no arithmetic
relationship between `fly-a` and `fly-b` for a caller to exploit or accidentally rely on.
- **Stable across recovery.** Restore, episode reset and a new epoch do not re-derive a
different seed for the same agent ID under the same master seed. The seed is persisted as run
configuration and state, and the capture compatibility digest covers the resolved seed
(workers-v1 section 2), so a checkpoint cannot be installed into a differently seeded
instance.
- **Equal IDs give equal seeds.** That is the only way to get identical seeds, and workers-v1
allows identical seeds only when an experiment declares them. A composition therefore
refuses a repeated agent ID rather than quietly sharing a seed between two agents.
Non-properties, stated so nobody assumes them: this is not uniform over the int32 range beyond
what SHA-256 gives, it is not a stream (one seed per agent per run, not per step), and it says
nothing about how a model consumes its seed.
## 5. Test vectors
`fixtures/seed-vectors.json` carries the full table: five master seeds (`0`, `1`, `42`, `2^63`
and the `U64` maximum) across four agent IDs, each with the exact material string, its SHA-256
and the derived seed, plus one four-agent composition and the inputs that must be refused.
Both implementations reproduce every row, and each records the material as well as the seed so
a third implementation can find where it diverges.
The first two rows:
| masterSeed | agentId | material | seed |
| --- | --- | --- | ---: |
| `0` | `fly-a` | `flybrain/seed-derivation-v1\n0\nfly-a\n` | 1828176714 |
| `0` | `fly-b` | `flybrain/seed-derivation-v1\n0\nfly-b\n` | 1218785088 |
Refused: an agent ID that is not an `Id` (uppercase, empty, over 64 characters), a master seed
that is not a canonical `U64`, and a composition with a repeated agent ID.
## 6. Out of scope
Choosing the master seed, recording it in the run manifest, and the hand-selected explicit
seeds that workers-v1 allows for the first synthetic composition. This document defines only
the derivation. A profile that needs several independent streams inside one agent derives them
from the agent's own seed under its own documented rule; that is a profile concern, not a
session one.