Skip to content

fix(bot): pass the measured 800 ms CSRC inactivity window from the composition root - #1385

Open
jfw-ppi wants to merge 3 commits into
Vexa-ai:mainfrom
jfw-ppi:upstream-csrc-inactive-ms
Open

jfw-ppi wants to merge 3 commits into
Vexa-ai:mainfrom
jfw-ppi:upstream-csrc-inactive-ms

Conversation

@jfw-ppi

@jfw-ppi jfw-ppi commented Sep 1, 2026

Copy link
Copy Markdown

Delivers issue: #1383 — mechanism delivered; the issue's live leg (row A3) is now witnessed upstream by the author on a real Teams meeting (C0); the non-author sign-off is still open.

Contribution rights

  • Independent: I created this contribution, or otherwise have the right to submit it
    under Apache-2.0, and it is not owned or controlled by an employer, client, or other entity.
  • Employer/client authorization required: an employer, client, or other entity owns or
    may control this contribution. I am requesting Vexa's private corporate-authorization process.
  • Unsure: I need a private rights review before merge.

Every commit carries my DCO Signed-off-by.

What & why

The transport sensor's CSRC_INACTIVE_MS = 400 spans a packet gap; in real Teams meetings it also does speech-pause duty, where it is too short: entry-timestamp staleness during natural pauses is p50 ≈ 550 ms, so the median pause trips a synthesized deactivation and one speech phase fragments. Replayed through the Teams CSRC channelizer, 400 ms gives 1.67 lane activations per real turn; 800 ms gives 1.13 (the knee) for ~1.8 pp more contamination; 1600 ms starts merging turns. Those numbers are a downstream measurement (#1383); this PR ports the mechanism.

  • The composition root (capture-bridge.ts) passes 800 ms through the sensor's existing inactiveMs parameter. The sensor keeps its packet-gap default and now refuses a non-finite or sub-poll window from any caller — it is the point of introduction of the NaN failure mode (both liveness comparisons false, every source flipped on every poll).
  • VEXA_CSRC_INACTIVE_MS overrides it per deployment: rendered onto the runtime by compose, helm (runtime.csrcInactiveMs) and lite, declared in the runtime's config.v1, forwarded to spawned bots by the kernel's tuning allowlist, documented in the configuration reference. Unset or blank ⇒ the default, silently — the templates render an unset knob as an empty string, and every other bot tuning knob reads it that way; a non-blank unusable value (NaN, below one 100 ms poll, above 10 s) warns and falls back.
  • The two mixed-pipeline comments that stated the sensor's 400 ms as a fact now state the composed window.

Scope: fragmentation only. Lane contamination is out of scope (TEAMS_CSRC_CONTESTED_TRANSCRIPTS.md covers the adjacent contested-words problem).

Observation bundle

  • C0 — the author's live witness (upstream, this head, real Teams) · a compose stack built from this tree (vexa-v012, PR bot image 5d493bfa972f, PR runtime image built from the same tree and pinned by its own tag; local CPU Whisper small via deploy/transcription; nothing external) in a real Teams meeting, one witness bot spawned through POST /bots, admitted by hand. Three rounds, each read off the witness's own <session>.csrc.jsonl sidecar and its [Csrc] csrc poll started (100ms, inactive after N ms) log line, with VEXA_CSRC_INACTIVE_MS set on the compose .env → runtime container → spawned bot env (verified by docker inspect at each hop):

    1. Synthetic speech, two speaker bots (Piper TTS), same ten-turn exchange at both windows: 1.00 activation per utterance at 800 ms and at 400 ms, one attributed transcript segment per utterance both times — no regression at 800 ms; synthetic speech never pauses long enough to discriminate the windows.

    2. One human reading ten scripted sentences with natural pauses, once per window: 400 ms → 13 activations, 7 re-activations within 0.6 s of the previous deactivation, 8 transcript segments with two garbled; 800 ms → 5 activations, 9 attributable segments. The reading cadence differed between the two readings, so this row shows the direction only.

    3. The same two recordings (the witness's own recordings of round 2, 74 s and 72 s), replayed through a speaker bot into both windows — cadence held constant:

      Recording (same audio in both columns) 400 ms: activations · re-activations < 0.8 s 800 ms: activations · re-activations < 0.8 s
      reading 1, 10 sentences 6 · 4 3 · 1
      reading 2, 10 sentences 9 · 8 4 · 3

    · concluded: on identical human audio the 800 ms window halves the transport activations for the same ten sentences and removes most sub-window re-activations, on both recordings; the composed window and the override reach the real bot on compose. Absolute counts are below the live reading because the replay re-encodes through a bot's virtual microphone; the ratio holds. Not claimed: the downstream per-turn numbers (C5), which need many speakers and hours; this is one voice, ten sentences, two recordings. Full timelines and tapes kept by the author; the recordings are the author's own voice and can be shared with a validator.

  • C1 — sensor · pnpm --filter @vexa/mixed-capture-core test · green: 500 ms of silence held open inside a caller's 800 ms window, deactivation synthesized past it; inactiveMs of NaN, 50 and Infinity each fall back to 400 ms with one activation and no flip-flop, said through log.

  • C2 — composition root · npx tsx src/csrc-wiring.test.ts on the built bundle, plain and with VEXA_CSRC_INACTIVE_MS=abc ambient · green both times: the real bridge hands the real sensor factory inactiveMs=800; garbage/zero/negative/sub-poll/over-ceiling fall back with one warning each; unset, blank and a good override are silent. Negative control: deleting the one inactiveMs: csrcInactiveMs, line from the page-side call turns 2 checks red — the two named RED-at-base.

  • C3 — kernel + deploy surfaces · test_profiles.py + test_config_contract.py (22 passed); deploy/helm/tests/test_template.sh (gate:helm PASS); helm template --set runtime.csrcInactiveMs=1200 and docker compose config with the env set · the runtime carries VEXA_CSRC_INACTIVE_MS: "1200" on both.

  • C4 — workspace · full @vexa/bot suite exit 0; gate:node (18 packages build + test) green; node scripts/gates.mjs all green except config-contract, which named the missing declaration — declared, that gate re-run green; the full sweep was not re-run after that one-line fix.

  • C5 — measurements · downstream, prior to this port: staleness percentiles and the 400/800/1600 ms sweep over 5 × 30 min tapes. Not reproducible here — the tapes carry meeting audio, and the percentiles need raw poll samples a csrc.jsonl tape (post-window transitions) does not record.

Acceptance floor (the issue's numbering)

Row Evidence
A1 Sensor default pinned (csrc-poll.test.ts); composition passes 800 (capture-bridge.ts CSRC_DEFAULT_INACTIVE_MS, asserted on the object that crosses to the sensor); override and unusable-window refusal pinned in csrc-poll.test.ts
A2 resolveCsrcInactiveMs + warning pinned in csrc-wiring.test.ts (floor/ceiling boundary-exact; blank silent); reachability pinned by test_profiles.py (kernel) and the helm/compose renders (C3)
A3 Witnessed upstream by the author (C0, round 3): same human audio, 400 → 800 ms halves activations on both recordings (6 → 3, 9 → 4). The downstream per-turn ratio (C5: 1.67 → 1.13) is not re-derived here. Non-author witness still open — see the validation request

Docs diff (D6c)

docs/changelog.d/1385-csrc-inactive-window.md (the mechanism proved; the reduction labelled a downstream measurement), the VEXA_CSRC_INACTIVE_MS row in docs/docs/configuration.mdx, runtime.csrcInactiveMs in values.yaml.

Security checks

No new dependencies, no lockfile change. The templates pass one env string through, the kernel copies it verbatim, the bot JSON.stringifys it in its one warning and hands the page a number. Nothing touches auth, secrets or the network.

Validation request

A non-author with Teams capture: reproduce C0 round 3 in about ten minutes — a stack from this head, one witness bot, and either read ten sentences with natural pauses yourself or replay any recording of natural speech through a second participant, once with VEXA_CSRC_INACTIVE_MS=400 on the runtime and once unset. Compare the activation count in the witness's csrc.jsonl (or the csrc-crossed counter in its log) for the same audio; the author's two recordings are available on request. Deployments validated in this PR: compose, live (C0); helm and lite: template render only (C3), no live run — honestly unclaimed.

Era

Based on and verified against main@3f5c3c03 (the v0.12.26 train).

Authorship

Sole author: the human submitting this. No agent co-author trailers (D13). Tooling disclosure: port and verification assisted by Claude Code; the measurements are from live captures in a downstream deployment.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

👋 Thanks for opening your first PR to Vexa, @jfw-ppi!

Highly recommended (not required): hop into our Discord
and tell us in a sentence or two what this change does and why. It helps us review
your value bundle faster and connects you with the reporter and maintainers.

Your PR is judged on its evidence — the observation bundle + the diff — not on whether you
show up. But showing up makes the whole thing smoother. See
the delivery guide for how a PR reaches merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c18d14099

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread core/meetings/services/bot/src/capture-bridge.ts Outdated
Comment thread core/meetings/services/bot/src/capture-bridge.ts Outdated
@jfw-ppi
jfw-ppi force-pushed the upstream-csrc-inactive-ms branch from 2c18d14 to 5fd6ab7 Compare September 1, 2026 19:50
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🃏 Merge card — #1385

check what it needs
Value missing state: value-signed (the value sign-off)
Diff no non-author approval on the current head sha (a new push dismisses a stale approval)

Not mergeable yet — every row above must be accepted before merge (choke point 1). Fill in what's ❌ above, then this clears automatically.

How a PR reaches merge: the merge bar.

@jfw-ppi
jfw-ppi force-pushed the upstream-csrc-inactive-ms branch 2 times, most recently from bf7c092 to c947d5d Compare September 1, 2026 22:09
…position root

The sensor's default is 400ms - sized to span a packet gap, not a
speech pause. Measured entry-timestamp staleness during real pauses is
p50 550ms, so the MEDIAN natural pause trips a synthesized deactivation
and one speech phase fragments. Against the current channelizer
(5 x 30 min seeded tapes): 400ms gives 1.67 lane activations per real
turn, 800ms gives 1.13 - the knee - for 1.8pp more contamination;
1600ms starts merging turns.

The value ships as a PARAMETER at the composition root: the sensor
owns the `inactiveMs` seam and its packet-gap default, nothing more.
Overridable via VEXA_CSRC_INACTIVE_MS - forwarded to spawned bots by
the runtime kernel's tuning allowlist (profiles.py), inherited from
the host env on the lite/process backend. An override that was SET but
is unusable (empty, NaN, below one 100ms poll, above 10s) warns and
falls back to the measured default: a NaN window makes both of the
sensor's comparisons false and re-deactivates every source on every
poll - the exact fragmentation this value exists to end, worse and
silent.

Scope: fragmentation only - no value of inactiveMs/lookbackMs/
flickerHoldMs brings lane contamination into a usable range (see
TEAMS_CSRC_CONTESTED_TRANSCRIPTS.md).

Measured in a downstream deployment against real Teams sessions and
ported here against the same seams.

Closes Vexa-ai#1383

Signed-off-by: Jacob Weinhold <29459386+jfw-ppi@users.noreply.github.com>
@jfw-ppi
jfw-ppi force-pushed the upstream-csrc-inactive-ms branch from c947d5d to b61e54e Compare September 1, 2026 22:09
@jfw-ppi jfw-ppi changed the title fix(capture): pass the measured 800 ms CSRC inactivity window from the composition root fix(bot): pass the measured 800 ms CSRC inactivity window from the composition root Sep 1, 2026

@jfw-ppi jfw-ppi left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review against main@3f5c3c03 (head b61e54ee at the time). Findings addressed in 926b8d0f; verification is in the PR description.

…an unusable window

The composed 800 ms window shipped, but the override could not reach a
bot on compose or helm: both render only the six BOT_* tuning keys onto
the runtime, so the kernel's allowlist never saw it. Now compose, helm
(runtime.csrcInactiveMs) and lite render it, the runtime's config.v1
declaration carries it (gate:config-contract), and the configuration
reference documents it.

Blank is unset. The deploy templates render an unset knob as an empty
string and every other bot tuning knob reads that as "no override"; the
resolver now does the same, so a default deployment boots without a
false warning. A non-blank unusable value still warns and falls back.

The sensor is the point of introduction of the NaN failure mode (both
liveness comparisons false, every source flipped on every poll), so it
now refuses a non-finite or sub-poll window from any caller, falls back
to its default and says so through its log. Pinned for NaN, a sub-poll
value and Infinity.

The two mixed-pipeline comments that stated the sensor's 400 ms as a
fact now state the composed window. The capture-bridge rationale names
the measurement as downstream and the pipeline it was replayed through;
the changelog fragment claims the mechanism proved and labels the
activations-per-turn reduction as a downstream measurement.

Refs Vexa-ai#1383

Signed-off-by: Jacob Weinhold <29459386+jfw-ppi@users.noreply.github.com>
Signed-off-by: Jacob Weinhold <29459386+jfw-ppi@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant