Confirmation, Profiles & Agent Architecture
This page records the theory and research behind two related engineering decisions in Booker4j:
- How a booking is confirmed before it is committed — and why this is configurable per customer rather than hard-wired.
- How configurable behavior is structured — as a small set of named interaction profiles rather than an ever-growing pile of boolean feature flags.
It exists so that future changes to these decisions are made with the reasoning, not against it. Every claim is cited inline; the full annotated bibliography lives in References.
- Per-customer profile:
interaction.profilein the customer form YAML →InteractionConfig/InteractionProfile. - Deterministic completion: the join-barrier hook in
ParallelToolCallingManager+FormCompletionInterceptor, with idempotent finalization inFormSubmissionServiceand a durablesubmittedflag on the session. - Design record & step-by-step plan:
docs/superpowers/plans/2026-05-30-confirmation-removal-interaction-profiles.mdand.claude/completion-interaction-profiles.md.
Part 1 — Explicit vs. implicit confirmation
When a conversational agent is about to commit a transaction (book, pay, send), it can confirm in one of two ways:
- Explicit confirmation — restate what will happen and wait for a separate "yes" before acting ("Here's your booking — shall I confirm?"). The user is put in control before the commit.
- Implicit confirmation — assume the input was understood and fold the acknowledgement into the result itself ("Done — booked your cleaning for June 5th"). No extra turn.
The research is consistent on how to choose between them: by stakes. Explicit confirmation is recommended for high-consequence, hard-to-reverse, "high-purposiveness" actions — flights, payments, medical, anything a mistaken commit would hurt — because it reassures the user and keeps them in control (Hanakano, Voice First — 5 confirmations). Implicit confirmation is preferred for low-stakes, "low-purposiveness" tasks, where users prioritize efficiency and find an extra confirmation turn slow and "overly structured" (Springer — confirmation strategies). Crucially, implicit confirmation still happens in the live turn, with the user present — it is not a silent, after-the-fact commit.
There is also a structural hazard specific to chat (vs. GUIs): the submit/reset metaphor problem. A GUI button gives the backend an unambiguous "commit now" signal; a free-text chat has no button, so the system can lose track of when the user actually intends to commit — especially if they change topic mid-flow (Bridging UI Design and Chatbot Interactions). The lesson: don't make "commit" a thing the model has to remember to do — anchor it to deterministic state.
How Booker4j applies this
We expose the choice as a per-customer interaction profile:
| Profile | Confirmation style | When the booking commits |
|---|---|---|
verified (default) | Explicit | After the form is complete, a deterministic summary is shown and the user must confirm; an idempotent submitBooking tool then commits. |
express | Implicit | The moment the form is complete, the backend submits in the same turn and the assistant's reply is the success message. |
Two design choices follow directly from the research:
- Commit is a deterministic event, not an LLM tool-press or a background timeout. Completion is detected in code at the parallel-tool join barrier (all mandatory answered + all optional addressed) — sidestepping the submit/reset-metaphor risk. A background "auto-submit after inactivity" job was explicitly rejected: it would commit without consent and outside the live turn, which is not what implicit confirmation means.
- The summary is deterministic, not LLM-generated — built from the form state, so values are never hallucinated.
Part 2 — Playbooks, hybrid agents, and "LLM interprets, logic decides"
The second decision is architectural: how should configurable behavior be expressed? Two industry-leading platforms converged on the same answer — a playbook / flow as the unit of behavior.
- Google Dialogflow CX — "Playbooks." A playbook is a declarative unit of agent behavior: a goal, natural-language instructions, few-shot examples, and parameters. You define many and route between them, and you can run a hybrid agent — deterministic flows where exactness matters, generative playbooks where flexibility helps (Playbooks, Generative vs. deterministic, hybrid agents).
- Rasa CALM — "Flows." YAML business-process definitions, with the guiding principle: "the LLM interprets what the user wants, the logic decides what happens next, and that separation keeps agents fast and predictable" (Rasa CALM). CALM also ships reusable conversation patterns for the off-happy-path cases (corrections, digressions, repair) (conversation patterns), and a process-calling model where the LLM triggers and collaborates with a stateful process (Rasa 2025 overview).
The takeaway: keep three layers cleanly separated — declarative (the what, in config), deterministic (the control, in code), generative (the conversation, in the LLM).
How Booker4j applies this
This is exactly the shape Booker4j already has and now leans into:
- Declarative — the form definition and the
interaction.profilelive in customer YAML. - Deterministic — the form engine validates and gates; the join-barrier hook finalizes the booking. The commit never depends on an LLM coin-flip.
- Generative — the LLM conducts the conversation (collecting answers, phrasing, handling corrections), steered per-turn by profile-appropriate context injected into the tool results rather than by scattered Java
ifbranches.
Part 3 — Why profiles, not boolean flags (constraint decay)
A natural temptation is to add each new behavior as an independent boolean toggle (confirmation.enabled, autoSubmit.enabled, recap.enabled, …). That path is a trap, and the research names the trap.
- Constraint decay — as constraints/rules/configs accumulate on an LLM agent, its performance measurably declines (Constraint decay). The fragility you feel as configs pile up is a real, observed phenomenon, not a hunch.
- Declarative beats imperative — flag-driven imperative control flow produces fragile implementations that break under load; declarative specs ("describe the desired behavior, not how to enforce it") are associated with large reductions in development time and faster deployment velocity (Declarative agentic layer, declarative agent workflows, formal high-level specs, orchestration beyond brittle scripts).
The distinction that resolves the tension:
- N independent booleans = 2ⁿ behavior combinations, almost none tested, read by scattered conditionals → combinatorial fragility. This is constraint decay.
- A profile = one named, coherent, tested bundle of behavior. Far fewer valid states, each verified — composition, not accretion.
How Booker4j applies this
interaction.profile is deliberately a named profile (verified / express), not the first of ten future booleans. New behavior becomes either a new profile or an attribute inside the profile schema — validated together at config-load (fail-fast) and covered by a test per profile — so the valid-config space stays small and verifiable. This is the structural defense against constraint decay, and it mirrors what Dialogflow CX and Rasa landed on.
Summary
| Question | Decision | Primary basis |
|---|---|---|
| Confirm before commit? | Configurable: verified (explicit) / express (implicit) | Choose-by-stakes (refs) |
| When does it commit? | Deterministically, in-turn, at the join barrier | Submit/reset-metaphor hazard (ref) |
| How is config structured? | Named interaction profiles, not boolean flags | Constraint decay; declarative > imperative (refs) |
| Layering | Declarative (YAML) · deterministic (engine) · generative (LLM) | Playbooks; Rasa CALM (refs) |
The full bibliography — with source-type labels and the non-research practitioner links — is in References.