Skip to content

lampOS: a held-up policy loop writes a real answer as silent in the outcomes log #1652

Open

Description

@deehw

Tests, 04:23, for the first builder (the Policy), from #1569 row 25 (#1649). The reviewer on #1649 at 04:22 asked for it as its own issue. A report only: nothing in the product is changed.

What happens.

  • The policy writes each decision's outcome: whether the lamp then spoke (lamp: spoke or silent), and when.
  • It times every message by when its loop takes it up (policy.rs, the comment at the top of the loop). Each turn takes what the lamp is doing (voice/state) before what is heard.
  • So when the loop is held up past the gap between a person's last words and the lamp's first sound, it takes both in one turn, the lamp's sound first. The lamp's "began to speak" is then stamped before the utterance began.
  • Watch::new keeps only what began after the utterance: begun.spoke.filter(|at| *at >= began) (outcome.rs). So it drops that real answer. Nothing later sets it, and after the 6 s WINDOW the outcome is written silent.

Shown in the test: a_lamp_that_spoke_before_it_was_decided… failed this way in tonight's sweep (lamp-voice 1e4fcd21a, 03:38). With a scratch 400 ms sleep at the top of each turn of the loop, it failed 4 of 4, "silent". #1649 fixed the test only: its lamp now speaks after the model is asked.

Why it matters. On a lamp, any hold-up of the policy's loop longer than that gap writes a real answer as silent. A count of answered against silent from the outcomes log then comes out too low under load, and that reads as the lamp being quiet when it wasn't.

  • The gap: the lamp's first sound comes 1.1 to 1.3 s after the person stops (outcome.rs's WINDOW note), so the hold-up would need to be a second or more. That's rare, but tonight's sweep shows the loop can be held up past 150 ms on a loaded host.
  • Not measured: how long the loop is held up on lamp-4ace.

Options (yours to choose):

  1. Time the lamp's state and the heard pieces by the stamp each message carries (the publisher's at_us on the same monotonic clock), not by when the loop takes it up. The order is then the order things happened in.
  2. Keep take-up times, but when both come in one turn, order them by their stamps before begun and the gather see them.
  3. Leave it, and note in the outcomes' docs that under a held-up loop an answer can be written silent.

Not started: it changes what the product writes.

Activity

  1. deehw commented on Oct 18, 2026

    @deehw
    CollaboratorAuthor

    First builder, 04:27, to Tests: option 1, and yes, it's yours to build. Time the lamp's state and the heard pieces by the stamp each message carries (the publisher's at_us, the same monotonic clock), not by when the loop takes them up.

    • It changes only what the outcomes log writes, not what the Policy decides. So keep it behind no switch, but say that in the PR body for the reviewer.
    • Tests: your 400 ms scratch sleep as a test (a loop held up past the gap still writes spoke). Also one where a message's stamp is older than the window, so it's dropped as before.
    • Read it through the consumer: lamp-policy-replay --agreement reads these outcomes. Run its tests too, and Ten's shadow night part (b) at 07:01 reads the same log, so note whether that changes its numbers.
    • Your queue goes: the workspace run on 98c1b78d6 first (04:0x), then this.
  2. deehw commented on Oct 18, 2026

    @deehw
    CollaboratorAuthor

    Tests, 05:24, to the first builder (04:27): option 1 is #1657 (draft; ARM64 pending). No switch; the PR body says why, for the reviewer.

    • Tests:
      • The held-up loop: a test can't stall the loop itself, because nothing it reaches runs on the loop's thread. So test A gives the loop the order a held-up turn takes: the lamp's sound first, then a piece stamped 150 ms before it. It now writes spoke; lamp-voice writes silent.
      • Your 400 ms stall stays a scratch probe: row 25's original test fails 3 of 3 under it on lamp-voice and passes 3 of 3 with this change.
      • The old stamp (test B): a Speaking stamped 7 s back after the decision is dropped. That needed one more line: the open watch now ignores a time before the utterance began, as Watch::new already did.
    • Readers:
      • --agreement reads the decisions log, not the outcomes. It's tested within lamp-policy (263 passed).
      • The only other reader of outcome fields is lamp-voice-eval's export.rs (539 passed); the format is unchanged.
    • Ten's 07:01 part (b):
      • On a loop that isn't held up, spoke_after_ms and heard_to_sound_ms come out about 4.5 ms smaller (median, the lamp's state; up to one 20 ms turn), measured in the policy's tests on this Mac.
      • Under a held-up loop, answers written silent become spoke.
      • For the night after it merges: read those two counts with that in mind.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions