Thing 2 contract state, Run/heartbeat/usage-readout sources, and the smallest first cut for "is anyone working, and on what". Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
13 KiB
Swarm dashboard: contract state, sources, first cut
Serves Eureka's ruling swarm-dashboard-first-thing2-surface (not readable from
the libby mind; cited by the id in the claiming follow-up
libby-body:follow_up:chat-s354-359). Read-only pass by the libby Self run
libby:run:mind-20261031T061423908Z, 2026-10-31. Nothing was built or deployed.
Claims below were probed on 2026-10-31 unless marked unverified.
The question the dashboard answers for a stranger: is anyone working, and on what.
1. Where Thing 2's surface contract stands
Probed against the forge GameCult/Thing (main e119e76, 2026-10-22) and its
hands/thing2-* branches. The source of truth for cut status is the thing2
campaign in the Eureka mind, which this pass cannot read; the prose map
(docs/thing2-map.md, last edited 2026-10-12) is the evidence used.
| Piece | State |
|---|---|
Contract, Rust (crates/thing-contract) |
On main. 13-arm closed Node union, SurfaceDocument, CompositionDocument, CommandDecl/Invocation/Result, AssetCatalog; byte goldens under tests/goldens/. |
| Contract, C# and TypeScript | Not on main. Branches hands/thing2-contract-cs (3dadfc6, 2026-10-14) and hands/thing2-contract-ts (aeede04, 2026-10-12). The map lists the cross-runtime store check and publication as waiting. |
Core (resolution, thing.std compositions, binding via the CultMesh port, Taffy layout, command dispatch) |
Not built. No crate exists. Decoding Verse surfaces waits on cultlib-gaps:follow_up:decode-stack-safety. |
| TUI backend | Not built. Planned first backend. |
| Web backend and shell | Not built. |
| Discovery/advertisement | Not a Thing kind (thing2:ruling:advertisement-owner); read through a core port; today's gamecult.eve.provider_advertisement.v1 is unchanged. |
README.md says the same: the layout solver, text, accessibility tree, render
commands and backends "are not built yet". Nothing can render a Thing 2
surface today. The only live renderers are Thing 1's (browser oracle, Ghostlight's vendored lowering,
the Nightwing browser reference), and Eve dashboards are quarantined (gamecult.md: no new Eve surfaces).
What a provider must publish to render
From docs/thing2-map.md "The consumer contract" and crates/thing-contract/src:
- A
SurfaceDocument(gamecult.thing.surface.v1, keything:surface:<providerId>.<label>,versionbumped only on structure change). Closed nodes:Box, Text, Image, Button, Toggle, Slider, TextInput, Select, Repeat, Overlay, Plot, Embed, Use. A swarm board isBox+Text+Repeat(one row per instance) +Useofthing.stdcompositions (card,metric,key-value). - The bound state records the document points at, under the provider's own schema and key. A
Bindis{provider?, schema, key, member path, format?}plusIteminside aRepeat. A collection binds to{provider, schema, key_prefix}or a list member of one record. No expressions, no derived values: the state's owner publishes derived values (map rationale, P5). So the dashboard's "working / idle" must be a published field, not computed in the surface. - Discovery: the provider is found through the existing advertisement, not a Thing record.
- Commands only if the surface acts. A read-only board declares none.
A provider needs no layout or renderer code. It needs the contract package in its language (Rust is the only one on main) and typed CultMesh state.
2. Sources (all typed state)
2a. Run documents, eight minds
Each mind is its own Huginn daemon (HEARTBEAT_PERSONAS in /etc/gamecult/heartbeat/heartbeat.env):
eureka 17872, libby 17873, heimdall 17874, kiko 17875, aqua 17876, weksa 17877, nibu 17878,
void 17879, all rudp://10.77.0.1:<port>. Probed: from this Self run,
EUREKA_INSTANCE=<name> HUGINN_ENDPOINT=<endpoint> huginn query '{"schemas":["epiphany.pipeline.run.v2"],"limit":200}'
answers on all eight, read-only, with caller client unknown (no per-client read identity yet).
Counts that day: eureka 68, libby 78, heimdall 94, kiko 70, aqua 81, weksa 74, nibu 71, void 87.
epiphany.pipeline.run.v2 (huginn schema run): instance, label, operated_by (Operator|Mind), turn (PersonaTurn|SelfRun), host, started_on (a date), budget_usd, claims [PipelineRef], campaigns.
What the dashboard can and cannot take from it:
- Open/closed is not on the Run. It is derived: a Run is closed when a
Resolutionwithsubject= the run is in force (huginn queryprintsInForceorResolved:<outcome>). Resolution key<instance>:resolution:run.<label>.n<seq>.huginn history <runId>reads it. - Start time is a date. The label
mind-YYYYMMDDTHHMMSSmmmZencodes the start to the millisecond, but that is a convention in a string. The admission header (huginn view) carriesadmitted <timestamp>, and the Resolution's header gives the end. Both are header facts, not document fields. A dashboard needing start and end times wants them as typed fields. - "Outcome" is nearly empty. Of 78 libby runs, 77 resolved
Recordedand one is in force. The meaning lives in free text:reason "answered 1"(55),"answered 2"(5),"the connector refused the credential lease: UnknownCaller"(3),"unit not active","failed after 0 answered...", plus rationalesThe Persona unit's start ended.(61) andThe Self run's carrier closed the run its claude left open.(11). Two Self-run resolutions carry the run's whole result message insidereason(long prose, operator-facing detail). A public board must never projectreasonorrationale. - "On what" is only on Self runs.
claimsholds the follow-up or other document the run took; aPersonaTurnhas no claims (its work is the chat room). The claimedfollow_up.itemis the operator's chat-relayed brief, free text, sometimes private. The typed handle (claim id, campaign slug, the claimed document'srepo) is safe; theitemis not. - An open Run can be a dead Run. Eleven libby Self runs were closed with "carrier closed the run its claude left open", so for some interval an in-force Run belonged to nobody. "Open" is "admitted and not yet resolved", not "a process is working".
- Dollar fields:
budget_usdis on every Run. Do not bind it.
Snapshot at 06:16 UTC: two Self runs in force, libby:run:mind-20261031T061423908Z (this one) and
void:run:mind-20261031T061543899Z (claims void-body:follow_up:chat-s335-340); no Persona run
in force in any of the eight.
2b. Heartbeat state and poll trace
Source: Huginn/crates/mind-body/src/heartbeat.rs. mind-heartbeat is one Idunn-managed unit, sole
writer of heartbeat.cc (a single-file CultCache store under the unit's state directory). Two kinds:
eureka.heartbeat.initiative.v1, key = instance slug:initiative_usd, as_of, urgency, urgency_at, last_burned_run, filled_events, classified_seq, launched_run, launch_spend_usd, launch_spend_known, completions_through, launch_window.eureka.heartbeat.poll_trace.v1, key = zero-padded unix ms, written only for polls that changed something:at, instances [InstanceTrace], launches [run label], burns [BurnTrace], classifier.InstanceTrace:instance, initiative_usd, rate_usd_per_h, urgency, urgency_age_s, busy, released, crossing_at, waited_s, idle_s, filled_by, fill_reason, note.
Usable without dollars: busy (an in-force Persona run, from the mind), released, crossing_at,
waited_s, idle_s, filled_by/fill_reason (tag or name), note, launches, urgency,
classifier. These say why nobody is working (waiting for initiative, held by the envelope).
Dollar fields to leave alone: initiative_usd, rate_usd_per_h, launch_spend_usd,
spend_usd, and in the Initiative record the same names. The metering teardown removes them, and
initiative_usd is the unit initiative itself is denominated in, so the heartbeat's own model will
change under whatever binds this.
Gaps:
- Not verified live.
heartbeat.ccis not readable from this Self run's account, and no/var/lib/gamecultpath for it was visible. The shape above is from source andeureka-body-map.md:534-540, not from a read of the file. - No wire. The heartbeat writes a file; it does not serve one. A reader opening a single-file
store while the heartbeat rewrites it has no stated safety (
FileStore::openpulls the whole file per call). A dashboard source must be published state, not a second process reading the file.mind-heartbeat timelineprints the latest poll for a human. - The trace is written only when a poll "changed something", so its newest line can be old while the system is healthy. Staleness must come from the heartbeat's own published liveness, which does not exist as a field.
2c. Connector usage readout
Source: ModelConnector/schemas/usage_readout.json, crates/model-connector/src/readout.rs.
gamecult.model.usage_readout.v1: {schema_id, at_unix_ms, plan_quota: [{window, status, utilization_permille (nullable), resets_at_unix_ms, observed_at_unix_ms}]}. Whole-plan windows, no
per-caller field, no dollars: the safe source for "how much capacity is left".
Gaps: it is a connector operation (usage_readout on gamecult.model.connector), reached at the
heartbeat's MIND_CONNECTOR_ENDPOINT (tcp://127.0.0.1:4110), a loopback port with a per-caller
credential. Three libby Self runs were already refused with UnknownCaller on a lease, so a new
caller needs registration before it can ask. It is a request/answer, not a document in a store. A
dashboard needs a publisher that polls it and republishes it as a CultMesh record. plan_quota
also rides on every lease grant. Unverified: the live values, since asking requires a caller
credential this pass was not given.
2d. Not a source: the presence provider
CultLib's 10-29 presence provider attests Idunn-managed service liveness, not attention or work
(libby-body:follow_up:presence-attention-not-carried). It can answer "is the heartbeat unit up".
It cannot answer "is anyone working".
3. Smallest first cut
Cut 1: one published read model, swarm.work.v1, from the Runs of the eight minds. No heartbeat,
no usage, no renderer.
One record per instance (eight, key <instance>), derived and published by a single projector
(owner: the swarm-dashboard provider), holding only closed-set or id fields:
state:Working(a Self run in force),Turn(a Persona run in force), orIdle.run: label of the newest in-force run, itsturn, its start (from the admission header), else the newest closed run's end.on: the claim id(s) and campaign slug(s), no free text.observed_atand the per-mindas_of, so a dead projector reads as stale.
This answers the stranger's question with typed state only. It deliberately omits reason,
rationale, item, budget_usd, and every *_usd field.
The Thing 2 surface for it is a SurfaceDocument that is a Repeat over those eight records, one
thing.std.card each, bound to state / on / the time fields. It can be authored and golden-tested
with the Rust contract on main. It cannot be seen until core (2a) and a backend exist. That is the
actual gating item, and it is not this cut's to work around with a Thing 1 or Eve surface.
Cut 2: add heartbeat liveness and the why-idle fields (busy, released, waited_s, idle_s,
note, launches), once the heartbeat publishes them as typed CultMesh state (it currently writes a
file only, and the dollar-denominated initiative model is under teardown).
Cut 3: add the plan_quota windows once a publisher polls the connector under a registered caller.
Forks for the operator (recommendations given)
- Who is the stranger? If public, the board shows ids and campaign slugs only (cut 1 as
written); "on what" in words needs a typed
public_labelthat the claiming agent sets, notfollow_up.item. Recommend ids only until that field exists. - Where does the projector live? Recommend a small reader that queries the eight Huginns (all
answer today) and publishes
swarm.work.v1, rather than adding publication tomind-heartbeat, which is Idunn-managed with a single-file store and a dollar model in flux. Confidence moderate: the cost is a new daemon, which needs the independent-lifecycle accountgamecult.mdrequires. - Is "open" enough? Recommend marking
Workingonly when a Run is in force and its carrier is attested (the Run was admitted by a launch that is still live); the data for that attestation is not in the Run. Until it is, say in the surface thatWorkingmeans "admitted, unresolved". - Typed times. Recommend asking Huginn for
started_atandended_atas Run/Resolution document fields, so no consumer parses labels or header facts. - Sequencing. A stranger sees nothing until core and a backend land. If the swarm board should come earlier than the TUI backend, say so; the TUI is the planned first backend and gives agents a reader, but a stranger needs the web backend (map cut 4).
Substrate gaps this pass felt
- The
swarm-dashboard-first-thing2-surfaceruling and thething2campaign are in Eureka's mind, not readable from libby's: cut status here comes from a 2026-10-12 prose map. heartbeat.ccunreadable from a Self run, and a file, not a served document.- No typed
ended_at/started_aton Run; outcome is free text inreason. - No per-client read identity on the eight Huginns (
client unknown).