Skip to content

Experiment: WhatsApp single-turn - #401

Draft
jptaylor wants to merge 22 commits into
mainfrom
fun-whatsapp
Draft

Experiment: WhatsApp single-turn#401
jptaylor wants to merge 22 commits into
mainfrom
fun-whatsapp

Conversation

@jptaylor

Copy link
Copy Markdown
Collaborator

No description provided.

jptaylor added 22 commits May 17, 2026 21:19
…acking

- Lift event_context onto a top-level field on bus events so the
  TaskAgent LLM context never sees __event_context, recipient_ids, or
  recipient_reasons when an event falls through to its raw payload.
- _extract_event_id reads the lifted field first, then falls back to
  the payload-embedded shape.
- _record_event_order / _record_handled_event_order skip event.query
  so backfill envelopes don't pollute the live high-water mark.
- _sort_ready_events keeps no-id events in arrival order instead of
  demoting them behind id-bearing events.
- Add a _closed guard on EventRelay so a late inbound event after
  close() does not restart the worker.
- Tests cover the no-leak path, the new sort stability, the
  event.query skip, and the top-level / fallback _extract_event_id
  shapes. Integration harness drains _game_event_queue after sending
  so post-handle assertions see synchronous state.
Move byoa_agent_name, _is_byoa_agent_name, _is_byoa_runner_name,
_lookup_byoa_owner, and _byoa_wake_failure_message out of VoiceAgent
into a new ByoaCoordinator collaborator at
src/gradientbang/pipecat_server/byoa_coordinator.py.

These five helpers have no shared state with the rest of VoiceAgent
so they extract cleanly. Renamed to drop the redundant byoa_ prefix
and underscore (now coordinator.agent_name_for / is_agent_name /
is_runner_name / lookup_owner / wake_failure_message).

VoiceAgent constructs ByoaCoordinator in __init__ and call sites
go through self._byoa. Subsequent steps will move presence, broker
auth, registry invalidation, and the wake flow.
Move _byoa_presence dict, _byoa_presence_sweep_task, _byoa_known_ships,
the _ByoaPresence dataclass, the BYOA_PRESENCE_* constants, and the six
presence-related methods (_on_byoa_presence, _push_byoa_presence,
_ensure_byoa_presence_sweeper, _byoa_presence_sweep_loop,
_mark_stale_byoa_presence_offline, _release_lock_on_byoa_offline) out
of VoiceAgent into ByoaCoordinator.

Adds one narrow host facade method on VoiceAgent: release_ship_lock,
with an optional expected_task_id guard for race-free conditional pops.
Adds coordinator.note_known_ship (called from _handle_start_task) and
coordinator.close_sweeper (called from close_tasks).

The coordinator still calls back to host._invalidate_byoa_registry_entry;
that helper moves in step 4.
Move _byoa_active_agents dict and the BYOA-specific branch of
_broker_identity_for_message out of VoiceAgent into ByoaCoordinator.

Adds four coordinator methods:
- register_active(name, ...) — adds to the active-agent table
- deactivate(name) — pops, returns the prior ctx or None
- get_active(name) — read-only lookup
- resolve_identity(source, task_id) — the BYOA branch of broker auth,
  raises PermissionError on missing entry or task_id mismatch

VoiceAgent._broker_identity_for_message now dispatches into
coordinator.resolve_identity once is_agent_name confirms the source
is BYOA. The in-process child fallback stays unchanged.
Move _invalidate_byoa_registry_entry from VoiceAgent into
ByoaCoordinator.invalidate_registry_entry. VoiceAgent exposes a tiny
get_agent_registry facade so the coordinator can reach into pipecat's
private AgentRegistry state without crossing the host boundary
elsewhere.

Three call sites updated to self._byoa.invalidate_registry_entry; the
two presence-internal call sites that previously went through
self._host._invalidate_byoa_registry_entry now stay inside the
coordinator.
Move the BYOA wake-flow subsystem into ByoaCoordinator:
- _pending_wakes per-ship watchdog dict
- _byoa_bus_channel (now _bus_channel)
- _call_wake_agent / _dispatch_byoa_wake / _watch_wake_timeout
- _cancel_pending_byoa_wake (now try_cancel_pending_wake)

VoiceAgent gains three narrow host facade methods used only by the
coordinator: ship_for_locked_task, clear_pending_task, has_pending_task.
release_ship_lock (added in step 2) is now also used here.

The dispatch block inside _handle_start_task still does the shared-state
writes (_pending_tasks, _locked_ships) directly so the rollback path
stays lexically local and readable; the watchdog spawn and wake-call
dispatch go through coordinator.arm_wake_watchdog and
coordinator.dispatch_wake_async respectively.

on_agent_ready's BYOA-watchdog cancel becomes
coordinator.cancel_pending_wake. close_tasks calls
coordinator.cancel_all_pending_wakes.

The coordinator's __init__ now takes a config kwarg (ByoaAgentConfig)
so it can read agent_wake_timeout_seconds for the watchdog timer.
VoiceAgent still keeps _byoa_config as its hello-handshake timeout
source.
Rename _enqueue_deferred_update to enqueue_deferred_update and
_update_polling_scope to update_polling_scope on VoiceAgent. The
prior names were marked private but they're now part of the
ByoaCoordinator host contract.

The 16 internal callers in voice_agent.py and the test mocks are
updated to match. No behavior change.

Closes the byoa extraction series. VoiceAgent goes from 3,781 lines
to ~3,000; ByoaCoordinator is the single owner of BYOA wake,
presence, broker auth, and registry invalidation.
Post-extraction cleanup, no behavior change:

- Expand file docstring from 4 to ~18 lines describing what VoiceAgent
  owns now that BYOA moved out: pipeline + speech cycle, deferred-update
  coalescer, tool handlers + bus broker, task lifecycle, TaskStateProvider,
  the narrow ByoaCoordinator host facade.

- Fix self-contradicting _locked_ships comment (was both "server-side
  mutex is authoritative" and "the bot is the only authority" — only
  the latter is true post-DB-lock-removal).

- Rename two mislabeled section headers: 'Lifecycle' → 'Speech-cycle
  gating', 'Agent lifecycle' → 'Subagent spawn handshake'. Both reflect
  what the methods under them actually do.

- Add 'Host facade for ByoaCoordinator' section header and cluster all
  five facade methods (ship_for_locked_task, clear_pending_task,
  has_pending_task, release_ship_lock, get_agent_registry) together —
  the latter two were ~600 lines away from their siblings.

- Move is_our_task up to the existing 'TaskStateProvider protocol'
  section where it belongs (EventRelay calls it, not the coordinator).
Carry garrison mode (defensive/offensive/toll) and active combat per
visited sector in the local_map_region response so the client can color
icons correctly on first render without waiting for follow-up events.

- LocalMapSectorGarrison gains a mode field, populated for all visible
  garrisons (own and enemy).
- New LocalMapSectorCombat type and combat field on LocalMapSector,
  populated from sector_contents.combat where ended is false.
- Mirror both shape changes in the client types.
Consume the snapshot's new garrison.mode field and active combat per
sector. Color garrison icons by mode (defensive=cyan, offensive=red,
toll=gold) for own and enemy garrisons alike. Surface corp-mate ships
in a distinct color from self-owned by threading owner_character_id
through the ship payload and classifying in BigMapPanel / MiniMapPanel.

Tighten sectorsEquivalentForRender to compare garrison.mode and combat
— fixes the "icon doesn't update without hover" memo-skip bug.

Server: owner_character_id added to buildShipPayload return so the
client can do the self-vs-corp-mate classification.
Replace the fan-in of bespoke per-event setters (updateMapSectors,
addCombatSector, removeCombatSector) with a single discriminated MapDelta
union and one applyMapDelta merge function in mapSlice. Every map-relevant
server event in GameContext (sector.update, map.update, garrison.destroyed,
combat.round_waiting, combat.ended) now funnels through this one entry
point.

Combat overlay state moves from the slice-level combat_sectors Record onto
each sector node as `combat?: { combat_id }`, with BigMapPanel deriving
the combatSectorsSet from sector data. This removes the dual-pass
ignoreLocal + source-priority branching that grew up around the old
updateMapSectors, so future overlays only need one merge case instead of
new merge rules.
- Extract mapShipsForViewer helper; drop 3 copies of the same ship classification (Big/Mini/Mobile).
- Drop redundant garrison node style (was identical to garrisonOffensive); offensive becomes the fallback for unknown modes.
- Trim verbose comments on MapDelta and corpMateColor.
- Pick up MobileMap into the new corp-mate coloring path (was still using the pre-PR shape).
@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gradient-bang Ready Ready Preview May 18, 2026 0:40am

Request Review

@github-actions

Copy link
Copy Markdown

Preview deployed: https://gradient-bang-jjbbxwznl-daily-co.vercel.app

jptaylor added a commit that referenced this pull request May 18, 2026
These files were accidentally squashed into #403; they belong only to
the in-flight WhatsApp POC in #401.

- Delete src/gradientbang/sms_agent/, prompts/agents/sms_agent.md,
  deployment/supabase/functions/sms_agent_tools/, docs/sms-whatsapp.md
- Drop sms-agent extras and script entry from pyproject.toml
- Remove get_sms_agent_llm_config() and build_sms_agent_prompt()
- Refresh uv.lock (drops twilio + aiohttp-retry)
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