···1515 string node_id = 2 [(buf.validate.field).string.min_len = 1];
1616 // engine names this node can run ("microvm", "nixery").
1717 repeated string engines = 3;
1818- // GOARCH of the node, so the mill won't place arch-incompatible jobs.
1818+ // GOARCH of the node, retained as an informational trait for logs.
1919 string arch = 4;
2020 // the highest relay offset the executor believes it has sent; a resume hint.
2121 uint64 last_offset = 5;
2222+ // opaque operator-defined labels used for runs_on matching.
2323+ repeated string labels = 6;
2224}
23252426// Resume is the mill's reply to Hello. The executor replays every buffered
···2830 uint64 ack_offset = 1;
2931}
30323131-// EngineSnapshot is the changing per-engine state on a node. free_seats is a
3232-// coarse "can you take more" hint; 0 also means draining (the mill treats a
3333-// draining node as a full one until it leaves). The resource fields are coarse
3434-// budget headroom for ranking only; the executor makes the real yes/no call in
3535-// ReserveResult.
3636-message EngineSnapshot {
3737- uint32 free_seats = 1;
3838- int64 free_memory_mib = 2;
3939- int64 free_vcpus = 3;
4040- int64 free_disk_mib = 4;
3333+// EngineAvailability is the changing per-engine state on a node. The executor
3434+// reports opaque load metrics (higher = more loaded); the mill ranks by the
3535+// worst load and uses the sum as a tie-breaker. The mill does not know what
3636+// the metric keys mean.
3737+message EngineAvailability {
3838+ bool available = 1;
3939+ // engine-defined load metrics. keys are opaque to the mill.
4040+ map<string, double> load = 2;
4141}
42424343// NodeSnapshot is pushed on connect, periodically, and right after any state
···4545message NodeSnapshot {
4646 string node_id = 1;
4747 uint64 seq = 2;
4848- map<string, EngineSnapshot> engines = 3;
4848+ map<string, EngineAvailability> engines = 3;
4949}
50505151// ReserveSeat asks an executor to hold a seat for a job. Zero secrets ride this
···6464 uint32 ttl_seconds = 7;
6565}
66666767+enum RejectClass {
6868+ REJECT_CLASS_UNSPECIFIED = 0;
6969+ REJECT_CLASS_TRANSIENT = 1;
7070+ REJECT_CLASS_INCOMPATIBLE = 2;
7171+}
7272+6773// ReserveResult is the executor's accept/reject for a ReserveSeat.
6874message ReserveResult {
6975 string lease_id = 1 [(buf.validate.field).string.min_len = 1];
7076 bool accepted = 2;
7177 string reject_reason = 3;
7272- // optional bid score the mill ranks accepted leases by (higher is better).
7373- double score = 4;
7878+ RejectClass reject_class = 4;
7479}
75807681// Secret is a single unlocked secret, sent only inside CommitLease.
+1
spindle/mill/session.go
···1919// request/response by lease id. We never hold a lock across a decode.
2020type millSession struct {
2121 nodeID string
2222+ labels []string
2223 enc messageEncoder
2324 l *slog.Logger
2425