- Add per-tenant TTS configuration endpoints (GET/POST /v1/tts/config)
with RoleAdmin auth instead of master scope
- Implement TenantTTSResolver for channels to use tenant-specific
TTS providers and auto mode settings
- Add per-agent voice override in channel TTS auto-apply:
- Extend OutboundMessage with AgentID and AgentOtherConfig
- Inject AgentAudioSnapshot in dispatch.go from outbound message
- MaybeApply reads tts_voice_id/tts_model_id from agent context
- Fix events.go to use RunContext.TenantID directly (H3)
- Fix dispatch.go error notification to use sendCtx (H1)
- Update UI to use new /v1/tts/config endpoints
Pin that a pre-hardening fragmented entry written under the raw UUID
cache key (tenantID:<uuidStr>) is still evicted by the TTL branch in
Router.Get when a UUID-form caller arrives after TTL expiry. The test
synthesizes the fragmented entry directly via a test-only map write,
then asserts the subsequent Get evicts the raw-UUID entry, re-invokes
the resolver once, and writes the canonical tenantID:agentKey entry —
leaving no fragmented entries behind.
Router.Get's canonical double-check branch trusted any existing entry
under the canonical key without re-checking TTL. If an earlier
agent_key caller wrote the entry and the TTL expired, a later UUID-form
caller would resolve fresh, hit the canonical branch, find the stale
entry, and return it — indefinitely — because the raw-UUID key was
never the map key and the initial-miss eviction branch did nothing.
Re-check TTL inside the double-check branch and evict+rewrite when
stale. Regression test primes a canonical entry with cachedAt set
2×TTL in the past, then asserts a UUID-form Get re-invokes the
resolver and the returned agent reflects the fresh resolver output.
Rewrite inline comments added during the agent identity hardening so
they explain the code as it stands today, rather than tying to internal
plan terminology (phase numbers, FR/NFR/H/M/C codes, PR references,
trap zone labels). Commit history already carries the plan archaeology.
Comments now keep the non-obvious invariants (cache boundaries, bypass
gaps, silent-nil traps, dual-tenant semantics) and drop the scaffolding.
Comment-only — no runtime behavior change.
Three related fixes to agent router identity handling:
1. Canonicalize on resolve — Router.Get now stores entries under the
canonical tenantID:agent_key after a successful resolver call,
regardless of whether the caller passed a UUID or agent_key. Prevents
fragmentation where the same logical agent would occupy two cache
slots. Callers that pass the UUID form are now un-cached and resolve
on every call; all production callers pass agent_key today.
2. Exact-segment match — matchAgentCacheKey helper replaces HasSuffix in
Router.Remove and Router.InvalidateAgent. Prevents substring collisions
like "tenantX:sub-foo" being wiped when invalidating "foo". Rejects
empty agentKey to guard against wildcard wipes.
3. Tenant-scoped IsRunning (C6) — Router.IsRunning now accepts ctx and
uses agentCacheKey for lookup. Pre-fix, the bare r.agents[agentID]
lookup always returned false in tenant-scoped deployments, causing
agents.list to report every live agent as idle.
Updates the sole caller at gateway/methods/agents.go:134.
Phase 2 of agent identity hardening (TD-3).