Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/data-submit-rpcs.md

Data-fill submit RPCs — pushing leaderboard / test / release rows to a live server

Design + guide + idiot-guide for EPIC N-data: the write counterpart to the thin viz read path (viz-remote-rpcs.md). It closes the gap where a live server's 🏆 Leaderboard / 🧪 Test / 🚀 Release panes stayed empty because nothing could push agent_model_runs / test_results / release_events into the locked server warehouse. Mirrors Bench.Submit.

1. The problem

nornir-server holds the single-writer redb catalog lock on each served workspace's Iceberg warehouse. A thin client (nornir … --server, nornir viz) can't open that warehouse to write it — only the server process can. The viz read path already solved the read half (Viz.* / Warehouse.Scan), and Bench.Submit solved benches. But the three remaining data-bearing tables had no client→server write path, so on the live server those panes were empty:

Table Pane Produced by Write path before
agent_model_runs 🏆 Leaderboard nornir bakeoff run (dev's LLMs) local warehouse only
test_results 🧪 Test nornir test run (dev's hardware) local warehouse only
release_events 🚀 Release nornir release run (dev's hardware) local warehouse only

Each is produced on the developer's machine (it needs their LLMs / CPU / repo checkouts), so the data must be shipped to the server — exactly like benches.

2. The contract (mirror Bench.Submit)

The CLI mints the same row structs the embedded path appends, ships them as proto messages over a Telemetry.* RPC, and the server handler maps them 1:1 back into those structs and calls the same append_* the embedded path calls. The thin write is byte-equivalent to the embedded write.

The mode switch is uniform: a persist_* helper checks NORNIR_SERVER (server_target()). Set ⇒ submit over gRPC; unset ⇒ append the local warehouse. Same shape as persist_bakeoff / run_bench.

3. The three RPCs (all on the Telemetry service)

RPC Request Server handler Appends Client helper CLI
Telemetry.SubmitBakeoff SubmitBakeoffRequest{AgentModelRunPb[]} submit_bakeoff append_agent_model_runs remote::submit_bakeoff nornir bakeoff run/demo
Telemetry.SubmitTestResults SubmitTestResultsRequest{TestResultPb[]} submit_test_results append_test_results remote::submit_test_results nornir test run/all
Telemetry.SubmitReleaseEvents ⭐NEW SubmitReleaseEventsRequest{ReleaseEventPb[]} submit_release_events append_release_events remote::submit_release_events nornir release run

All three return SubmitCountResponse{accepted}. The nornir-workspace header selects which served workspace the rows land in (same as every other RPC).

Status before this epic (honest reckoning):

4. The release_events Option round-trip

ReleaseEventRow carries two Options that must survive the wire distinct from their empty/zero forms (the proto-3 "all scalars have a default" trap, the same one BenchRun.TestOutcome solves with has_duration):

The CLI/server map Option ↔ (value, has_*) on both ends. The integration test asserts all three shapes round-trip (None, non-empty list, present-but-empty).

5. How release run ships its events

nornir release run runs on the dev's hardware and its wh handle is opened read-only (lock-tolerant, to coexist with a live server). So the per-emit append_release_events(&wh, …) can't reach the server. Instead the ReleaseEventEmitter now records every minted row into a shared buffer (recorded_rows()); a drop-guard in run_release_run drains that buffer and submits the whole run over Telemetry.SubmitReleaseEvents on every exit path — success, ?, or a phase bail! — so even a failed/partial release fills the live 🚀 Release tab. Embedded mode is a no-op (the per-emit append already wrote the local warehouse). Best-effort + non-fatal: a submit failure is logged, never aborts the run.

6. Iceberg schema-evolution LAW

No table schemas changed — all three tables already exist with their full column set, and the proto messages are additive (a new service method + new message types, no field renumbering), so old↔new binaries stay wire-compatible. The append_* paths already go through ensure_table_schema, so any future column add auto-evolves.

7. Tests (inject real rows, assert real output)

tests/data_submit_rpcs.rs — for each of the three RPCs: spin ONE ephemeral nornir-server over an empty workspace warehouse, submit real rows over the submit RPC, then read them back over the thin RPCs the live panes use (Viz.BakeoffResults, Warehouse.Scan of test_results, Viz.ReleaseEvents) and assert the exact values landed (scores, error strings, statuses, failure messages, and the release_events Option round-trip), not merely a non-empty result. A unit test in warehouse/release_events.rs asserts the emitter's recording buffer captures every minted row across clones, in seq order.

8. Idiot-guide — fill the live server

The fat CLI does it automatically once NORNIR_SERVER is set. Point at the server (token + workspace), then run the producers exactly as you would locally — they detect NORNIR_SERVER and submit instead of writing a local warehouse:

export NORNIR_SERVER=https://oden:7878
export NORNIR_SERVER_TOKEN="$(sudo sed -n 's/^NORNIR_TOKEN=//p' /etc/nornir/nornir.env)"
export NORNIR_WORKSPACE=holger        # which served workspace to fill

# 🏆 Leaderboard — bake-off rows
nornir bakeoff demo                                   # quick mock matrix, or:
nornir bakeoff run --task "…" --agents claude,kilo --models opus,llama3

# 🧪 Test — multi-aspect matrix rows
nornir test run <repo>                                # one repo, or:
nornir test all                                       # every configured repo

# 🚀 Release — release-op DAG rows
nornir release run --workspace holger                 # ships events on completion

Each prints → submitted N row(s) to <server>. Then open nornir viz (or the panes directly) against the same server and the 🏆 / 🧪 / 🚀 tabs are populated. Embedded (no NORNIR_SERVER) behaves exactly as before — local warehouse.