Status: approved design
Date: 2026-07-12
Evaluate two configurable capability-invocation architectures for the service voice-reply path, primarily targeting a low-latency conversational toy:
ChatAgent owns low-latency, natural, user-facing speech.The first production events are:
The primary success criterion is to measure whether keeping provider tool schemas off ChatAgent's first-token critical path actually improves reply latency and cost without reducing event accuracy. The lab must support both direct ChatAgent tool calling and the dual-agent text-event design under one consistent capability contract.
Keep one shared flat event registry and make the event-selection path configurable. The two experimental modes differ only in how events are selected and how initial arguments are proposed; they share argument normalization, execution, concurrency, result policies, and user-visible behavior.
Configuration uses one explicit tool_invocation_mode value:
dual_agent: ChatAgent receives a compact event catalog but no provider tools. It streams its visible reply and appends internal event names in the text-event block. EventAgent resolves missing arguments.chat_agent_tools: ChatAgent receives the enabled provider tool schemas and emits native tool calls, including initial arguments. The shared event pipeline validates and normalizes those arguments before execution.In dual_agent mode, the parser hides the event block from the user and dispatches the named events to EventAgent. In chat_agent_tools mode, the streaming client collects native tool-call deltas while continuing to expose any visible assistant content allowed by the provider.
Both modes convert model output into the same internal event batch. The shared pipeline performs deterministic parsing and normalization first, then a small LLM fallback only when required fields remain ambiguous or missing. It validates the resolved arguments and invokes the same handlers through the registry.
ChatAgent may emit multiple event names in one response. Independent events are resolved and executed concurrently. The runtime applies an explicit conflict policy before execution so terminal or mutually exclusive side effects do not race unpredictably.
Each event definition declares a result policy so the runtime does not hardcode behavior by event name:
| Result policy | Runtime behavior |
|---|---|
terminate |
Execute after ChatAgent's farewell, end the session on success, and do not run ChatAgent again. |
silent_success |
Keep ChatAgent's first acknowledgement as the normal response; run a corrective follow-up only if execution fails. |
template_follow_up |
Produce a deterministic confirmation from the normalized execution result without another LLM call. |
llm_follow_up |
Feed a compact structured result back to ChatAgent and produce a second user-facing answer. |
Initial mapping:
| Event | Result policy |
|---|---|
terminate_session |
terminate |
adjust_device_volume |
silent_success |
add_schedule |
template_follow_up |
web_search |
llm_follow_up |
The comparison must not become two separate implementations. Both modes use:
EventBatch and event IDs;web_search flow;Only these variables change:
| Concern | dual_agent |
chat_agent_tools |
|---|---|---|
| ChatAgent capability context | Compact event names/descriptions | Full enabled tool schemas |
| Event selection | Hidden text event names | Native provider tool calls |
| Initial arguments | Empty or minimal; resolved by shared pipeline | Proposed by ChatAgent tool-call arguments |
| EventAgent LLM fallback | Used only when deterministic resolution is incomplete | Used only when ChatAgent arguments plus deterministic normalization remain incomplete |
| Provider dependency | Requires reliable content streaming | Requires reliable streaming native tool calls and multiple tool calls |
Direct ChatAgent tools do not bypass the shared execution pipeline. ChatAgent proposes calls; the application still validates, authorizes, deduplicates, schedules, executes, and applies result policies.
Provider-required assistant tool-call and tool-result messages are allowed to differ between modes. They are internal protocol overhead, not user-visible conversation, and their prompt/completion tokens must be counted as part of the direct-mode result rather than normalized away.
Neither mode is assumed to win before measurement:
| Dimension | dual_agent |
chat_agent_tools |
|---|---|---|
| Ordinary-turn LLM calls | One ChatAgent call | One ChatAgent call |
| ChatAgent prompt growth | Concise descriptions per event | Full JSON schema per tool |
| Clear side-effect event | Usually one ChatAgent call plus deterministic execution | Usually one ChatAgent call plus deterministic execution |
| Ambiguous arguments | May add one low-cost EventAgent fallback | ChatAgent arguments may avoid fallback; invalid/incomplete arguments may still add fallback |
| Web search | First ChatAgent answer + search + second ChatAgent answer | Same user-visible flow and minimum calls |
| First-answer behavior | Text protocol explicitly places visible reply before event names | Depends on provider support for visible content alongside streamed tool calls |
| Multiple events | Flat text event list | Multiple native tool calls |
| Capability growth cost | ChatAgent context grows slowly | ChatAgent receives every enabled schema on every turn |
| Provider dependence | Requires reliable content streaming and marker adherence | Requires reliable native tool-call streaming, argument JSON, and multiple calls |
The likely trade-off is that direct tools may reduce argument-fallback calls on event-heavy conversations, while dual-agent mode should reduce prompt tokens and preserve first-answer behavior as the enabled capability set grows.
The initial event model stays flat. There is one registry of self-contained event definitions; the first version does not introduce event category inheritance, nested capability trees, plugins, or a general workflow engine.
Adding a capability should normally require only:
The parsers, ChatAgent protocols, Runtime, and shared event orchestration must not require an event-name-specific branch. They operate on the metadata declared by each definition. The event-specific flow sections below describe initial behavior and acceptance criteria, not hardcoded Runtime architecture.
Multiple events remain a flat ordered list in one batch. Generic batch orchestration handles deduplication, conflict metadata, bounded concurrency, result aggregation, and terminal barriers.
The flat model should evolve only when real additions reveal repeated structures that cannot be expressed cleanly with definition metadata. Examples include several events sharing one authorization flow, a multi-step transaction/rollback requirement, or recurring dependencies between event outputs. Until then, those abstractions are deliberately out of scope.
terminate_session through the configured text-event or native tool-call protocol.This event should terminate the dialogue through the dialogue/session boundary. It should not be implemented as a direct MQTT action.
adjust_device_volume through the configured protocol.mode: absolute, relative, mute, or unmute;value: an absolute target when present;delta: a signed relative change when present.Validation must clamp or reject values outside the supported range and must distinguish "set to 30" from "turn it up by 30".
add_schedule through the configured protocol.Schedule execution must use an idempotency key so retransmission or duplicate event detection does not create duplicate entries. Relative phrases such as "tomorrow morning" are resolved against the session timezone and a captured request timestamp.
web_search through the configured protocol after the visible reply where the provider supports content plus tool calls.The first answer must not fabricate current facts. When the answer depends heavily on search, it should provide a natural bridge or a clearly qualified preliminary view rather than guessing.
ChatAgent may output multiple event names in the hidden event block or multiple native tool calls in one response. Both protocols produce the same flat ordered event batch. The shared pipeline resolves and executes independent events concurrently with a bounded concurrency limit.
Examples that may run concurrently:
Conflict policy:
web_search may run alongside side-effecting events;terminate_session is dispatched after other accepted events have reached a terminal result, with a short overall deadline, so ending the dialogue does not cancel an already acknowledged schedule or device action;The design extends the registry-owned event definition with execution metadata rather than adding event-name conditionals throughout the runtime.
Conceptually, an event definition contains:
The runtime remains responsible for orchestration and ordering:
EventAgent remains responsible for argument resolution, validation, and handler invocation. Production side effects remain behind service-owned ports/adapters.
The local fast Gate for obvious high-confidence intents remains a later optimization. It should be evaluated only after the two configured model-driven modes establish a baseline and production-like measurements show that event-catalog/tool-schema cost or event-selection errors justify the added routing complexity.
ChatAgent should receive:
dual_agent mode, a compact event catalog containing only enabled event names and concise descriptions;chat_agent_tools mode, the enabled schemas produced by the same event registry.In dual_agent mode, ChatAgent should not receive:
EventAgent should receive:
For web_search, only the compact result summary enters the second ChatAgent call. Raw provider payloads stay in audit/debug data.
For deterministic resolution, no EventAgent LLM request is made. When fallback is needed, EventAgent should use a lower-cost model and a small token limit because its output is structured arguments rather than conversational text.
agent-lab is the behavior and protocol proving ground. Production integration belongs to mituan-service and must use its existing boundaries:
The current service voice reply path does not yet wire web search into its model request, so real search integration is a separate service-repository change.
Because implementation spans repositories, implementation planning must be split:
agent-lab plan for runtime policies, mock/contract tools, UI/audit visibility, and tests;mituan-service plan for production ports, adapters, device/session commands, configuration, and integration tests.Each repository plan must load that repository's AGENTS.md and maintain its own todos.md execution order and status.
dual_agent sends tools=[], injects the compact event catalog, and extracts hidden text events.chat_agent_tools sends exactly the enabled registry schemas and converts native tool calls into the same internal event batch.terminate_session yields farewell -> accepted sibling events finish or time out -> successful termination -> no second ChatAgent call.adjust_device_volume yields acknowledgement -> action result, with no second answer on success and a corrective answer on failure.add_schedule yields acknowledgement -> normalized execution -> deterministic confirmation, without a second LLM call.web_search yields first answer -> search execution -> distinct second answer grounded in the returned result.dual_agent and chat_agent_tools modes with the same model parameters and enabled events.Expected hypotheses, not predetermined conclusions:
dual_agent should use fewer ChatAgent prompt tokens and scale better as tool schemas grow;chat_agent_tools may avoid EventAgent fallback calls when ChatAgent produces complete arguments;