Skip to content

feat(diagnostics): listen for agent_hello + warn on bad client config#16

Merged
nickmeinhold merged 2 commits into
mainfrom
feat/agent-hello-listener
May 30, 2026
Merged

feat(diagnostics): listen for agent_hello + warn on bad client config#16
nickmeinhold merged 2 commits into
mainfrom
feat/agent-hello-listener

Conversation

@maxwell-merge-slam

Copy link
Copy Markdown
Contributor

Summary

Tech World clients now publish a one-shot agent_hello data message immediately after room.connect() succeeds (Flutter PR enspyrco/tech_world#478), carrying the ConnectOptions actually in use plus SDK / build / version metadata.

This change makes both Clawd and Dreamfinder log every hello they receive and emit a structured warning when a client is connected with adaptiveStream: true — the configuration known to silently break Tech World video forwarding (and pause audio via SFU demand-signaling) because the Flutter client renders through the Flame canvas instead of VideoTrackRenderer.

Complements PR #15's publisher-side heuristic: that infers misconfiguration from a downstream symptom (track-subscribed but no first frame); this reads the cause directly off the wire.

Coupled PR

Flutter publisher: enspyrco/tech_world#478 (feat/agent-hello-diagnostic). The two PRs are independently deployable:

  • This PR first → no behavior change (listener with no publisher).
  • Flutter PR first → old bots silently drop the data message (no consumer).

Wire contract

Topic: agent_hello (snake_case — deliberate, agent-inbound channel naming).

Payload v1:

{
  "schemaVersion": 1,
  "clientSdk": "flutter",
  "clientSdkVersion": "2.7.0",
  "buildSha": "string",
  "appVersion": "0.0.0+1",
  "adaptiveStream": false,
  "dynacast": false,
  "platform": "macos|ios|android|web|...",
  "userAgent": "string | null"
}

Log shape

Successful parse:

{ "event": "agent_hello_received", "participant": "<identity>", "payload": { ... } }

Bad-config warning (only when adaptiveStream === true):

{ "event": "client_misconfig_detected", "warning": "adaptiveStream=true breaks Tech World video/audio forwarding", "participant": "<identity>", "clientSdkVersion": "...", "buildSha": "...", "appVersion": "..." }

Notable design choices

  • New src/livekit-topics.ts to start mirroring Tech World's LiveKitTopic enum (only AGENT_HELLO today — extend opportunistically).
  • parseAgentHello is a pure validator: rejects unknown schemaVersion, missing or wrong-typed fields. No throw on bad payloads; warn + drop.
  • Logging via console.log / console.warn matches the rest of this codebase. Project memory mentions pino but the codebase doesn't actually have it — followed what's here.
  • No persistence, no retry, no buffering. The log line is the artifact.
  • Wired into BOTH src/index.ts (Clawd) and src/dreamfinder-entry.ts (DF) so every bot variant observes hellos.

Test plan

  • npm run typecheck clean.
  • npm run lint clean.
  • npm run build clean.
  • Manual: deploy alongside Flutter PR #478, watch tw-clawd / tw-dreamfinder logs for agent_hello_received lines on next player join.

🤖 Generated with Claude Code

@nickmeinhold

Copy link
Copy Markdown
Collaborator

Casing nit, but load-bearing: the wire string "agent_hello" should be "agent-hello" to match the kebab-case convention every other LiveKit topic uses on the Flutter side (map-info, door-unlock, chat-response, position-heartbeat, etc).

The Flutter LiveKitTopic enum is uniformly kebab; snake_case here makes agent_hello the only outlier and breaks the implicit "all topics are kebab" contract that external consumers can rely on.

One-line diff in src/livekit-topics.ts:

-  AGENT_HELLO: "agent_hello",
+  AGENT_HELLO: "agent-hello",

The TS constant name AGENT_HELLO (SCREAMING_SNAKE_CASE) stays the same — only the wire literal changes. Doc comments in src/agent-hello.ts referring to the topic by name should also update from agent_hello to agent-hello.

Coordinated comment going onto tech_world PR #478 so both ends flip together.

nickmeinhold and others added 2 commits May 30, 2026 16:02
Tech World clients now publish a one-shot `agent_hello` data message on
the LiveKit data channel immediately after connect (Flutter PR coming),
carrying the ConnectOptions actually in use plus SDK / build / version
metadata. This change makes both Clawd and Dreamfinder log every hello
they receive and emit a structured warning when a client is connected
with `adaptiveStream: true` — the configuration known to silently break
Tech World video forwarding (and pause audio via SFU demand-signaling)
because the Flutter client renders through the Flame canvas instead of
`VideoTrackRenderer`.

Complements the publisher-side heuristic detector in PR #15: that one
infers the misconfiguration from a downstream symptom (track-subscribed
but no first frame); this one reads the cause directly off the wire.

- New src/livekit-topics.ts mirroring Tech World's LiveKitTopic (today:
  just AGENT_HELLO; format ready for the rest as needed).
- New src/agent-hello.ts with a pure parseAgentHello() validator (rejects
  unknown schemaVersion, missing required fields, wrong types) and a
  handleAgentHello() that logs structured JSON via console.log/warn.
- Wired into both src/index.ts and src/dreamfinder-entry.ts data-received
  handlers ahead of any other topic switch.
- No persistence — the log line is the artifact. No retries, no buffering.

Safe to deploy independently of the Flutter PR: until clients ship the
publisher side, nothing arrives on `agent_hello` and the new code paths
are unreachable. After the Flutter PR ships, old bots silently drop the
data message (no consumer).

Tests: npm run typecheck / lint / build all green. No test harness in
this repo today; parseAgentHello documents the expected shape in JSDoc
and ships as a pure function so it's trivially unit-testable when one
gets added.

Wire contract: topic string `agent_hello` (snake_case, deliberate —
treated as the inbound agent-fleet channel name).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire-string casing audit on the Flutter side flagged 'agent_hello'
as the lone snake_case outlier — every other LiveKitTopic in the
Flutter enum is kebab-case ('map-info', 'door-unlock', 'chat-response',
…). Mirror the Flutter-side fix here so the wire contract stays
byte-for-byte identical.

Companion fix lands in `enspyrco/tech_world` PR #478.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nickmeinhold
nickmeinhold force-pushed the feat/agent-hello-listener branch from 9ffa2a8 to c4ed225 Compare May 30, 2026 06:02
@nickmeinhold
nickmeinhold merged commit d85cf0f into main May 30, 2026
1 check passed
@nickmeinhold
nickmeinhold deleted the feat/agent-hello-listener branch May 30, 2026 06:02
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