An AI support product becomes interesting to me at the point where an answer stops being just text. A customer asks about an order, wants something changed, or needs a person to take over. Now the application has to decide what it knows, who is asking and which consequences it is allowed to create.
That is the part of Reesponder I want to explain here. The product brings business knowledge, customer conversations, connected API actions and human handoff into one application. The widget is the visible part. Behind it is a much less forgiving problem: maintaining a consistent account of what happened when the model, the browser and an external service can all disagree about the current state.
I find it more useful to follow those disagreements than to walk through a list of features. A retrieved record can be relevant and still be misquoted. An approved action can become invalid before it runs. A request can succeed even when the caller sees an error. The design has to give each of these situations a meaning that survives beyond the current HTTP request.
In this essay
Watch the original product overview
Finding the right page is only the beginning
The mistake I want to avoid is treating a convincing answer as evidence of a correct one. It must distinguish two similar products, respect the current business configuration, avoid acting on another customer’s data and recognize when a request should leave the automated workflow. A confident answer that blends the price of one product with the model year of another can be more damaging than a clear admission that the information is unavailable.
I don’t consider retrieval finished just because the search found a relevant page. The answer also needs to preserve relationships within that text. A model can retrieve the correct source and still assemble an incorrect combination of product name, year, availability and price. Reesponder’s grounding rules explicitly bind those facts to their source record.
The inspected implementation includes a targeted check for model-year conflicts using source titles and record identity. Previous assistant responses are not accepted as fresh evidence. Otherwise, one mistaken answer could become the apparent authority for the next answer and reinforce itself over a conversation.
For certain recommendation and conflict cases, the application makes a second provider pass. It supplies the candidate answer as untrusted quoted data, with no actions available to that pass. A further conflict or an attempted action fails that validation path. The second pass therefore cannot use a wording correction as an opportunity to invoke a business operation.
This is a bounded safeguard, not a universal hallucination detector. It adds provider latency and token cost, and its usefulness depends on the source records and checks it can actually evaluate. Usage from both passes is accounted for. The engineering trade-off is explicit: some requests spend more time checking factual consistency before a response is accepted.
A small example makes the problem concrete. The grounding tests include a 2018 Toyota Auris and a 2020 Peugeot 508. An answer that calls them a 2019 Auris and a 2021 Peugeot is not repaired by attaching the correct catalogue as a citation. The names and dates have already been recombined incorrectly. The check has to connect the claim about each vehicle to the identity of its own record.
A relevant source can still be misquoted.
The same tests deliberately allow a model to appear in more than one listed year. They also avoid treating an unrelated date in a sentence as a product-year claim. I like that distinction because a validator which rejects every number it does not recognize can make the assistant unusable without making it more truthful. The check needs a narrow question it can actually answer.
Recommendations introduce another kind of error. If a customer asks for a laptop with at least 16 GB of memory, presenting an 8 GB option as a suitable match is a failure even if every specification was copied accurately. The inspected recommendation test asks the review pass to consider all mandatory requirements. Record fidelity and satisfying the customer’s constraints are related, but they are not interchangeable.
The review pass receives the earlier draft as material to inspect. It does not inherit a new entitlement to act. Available actions are removed, and a correction that still violates the checked constraints cannot simply return the original draft as a successful answer. Otherwise the fallback would quietly undo the reason for having a review stage.
There is an operational price here. A second provider request adds latency and consumes tokens, which is why usage from both calls is accounted for. I would not describe that extra call as free insurance, or assume that applying it to every response must improve the product. The useful question is whether the particular check catches a costly, identifiable class of mistakes.
This also sets a limit on the claim. A model reviewing another model is still dependent on the available evidence and on the quality of the review. The source record remains the reference point. When the records cannot support a confident answer, producing a less confident answer is an appropriate product behavior. Polished wording cannot fill a missing fact.
The browser can describe a request. It cannot authorize it.
Once an answer can touch business data, the distinction between context and authority has to be reflected in the application itself. I can use the current page to understand what the customer is looking at. I cannot treat that page, or an account number typed into the widget, as proof that the customer is entitled to an order record.
The project is a TypeScript monorepo. Next.js provides the application frontend, Fastify implements the API, and a separate worker processes longer-running tasks. PostgreSQL is the system of record, with pgvector supporting knowledge retrieval. Redis handles ephemeral coordination, while uploaded objects use an S3-compatible storage abstraction.
The API owns authorization and business rules. Neither the customer-facing widget nor the application frontend talks directly to the database. Background processing is separated from interactive requests so that crawling, extraction and delivery work do not have to finish within a browser request.
The embedded widget is delivered independently and isolated in a closed Shadow DOM. That reduces accidental styling collisions with the host page. It is a compatibility boundary, not an authorization boundary: a customer’s website and the data arriving from a browser remain untrusted inputs to the API.
This separation creates more release coordination than a single process, but the responsibilities are clearer. Durable business state belongs in PostgreSQL. A transient cache or a lost coordination message must not become the only record that an action was approved or a handoff was requested.
A browser-supplied workspace identifier, page URL or contact detail is not evidence of authority. The visitor path uses a tab token checked against its stored hash and validates the request origin against the site configuration. Protected operations still apply workspace, site and conversation constraints.
Signed customer context is a separate trust level from ordinary browser context. It can establish information supplied by a trusted server, while a customer typing an account identifier into the widget cannot grant themselves access to that account. The distinction needs to remain visible as new integrations are added.
An origin check and a tab token answer different questions. The origin constrains which configured site is making the browser request. The token associates the request with a visitor session whose server-side record can be checked. Neither should be stretched into a claim that the visitor is the owner of whatever customer account they mention. Verified customer identity is an additional condition for the operations that need it.
The inspected order adapters make this distinction tangible. Shopify and WooCommerce lookups require verified identity, and the returned order is checked against the verified email. An order number alone is not enough. A support interface often makes identifiers easy to copy, so treating possession of one as permission would create exactly the wrong boundary.
There is a similar boundary around the network. A configured integration is not a general-purpose instruction to make arbitrary requests. The HTTP adapter accepts public HTTPS endpoints, checks resolved addresses and pins the validated address into the socket connection. Redirects are not followed. Responses must be JSON and fit a bounded buffer. These are application transport rules, not promises made to the language model.
I mention these details because “the model has access to an API” hides a lot of decisions. Which endpoint? With whose credentials? Which inputs can be supplied? Which response fields are exposed? Those decisions belong to a small integration contract that can be inspected independently of the conversation. The model can select within that contract; it cannot rewrite the contract by generating a persuasive sentence.
The same separation helps with maintenance. Frontend changes can alter how a confirmation is presented without moving the authorization rule into the browser. A widget embedded in a customer’s page may also remain open across a deployment. The server still needs to enforce its current rules when that older client makes its next request.
Confirmation has to refer to the thing that will actually run
Connected Actions let a conversation reach a configured external API. A lookup and a write have different consequences, so the system carries that distinction through the entire workflow. A model-generated proposal is stored as a pending application object, not immediately executed as a side effect of generated text.
The action catalogue exposes only enabled configurations whose tested revision matches their current revision. Plan eligibility and workspace and site scope are checked as well. Editing a configuration increments its revision, resets its tested state, disables the action and cancels pending proposals tied to the previous configuration.
A write requires explicit customer confirmation. Where an integration requires verified identity, that is another condition rather than a detail the model can waive. Immediately before execution, a database transition claims the pending proposal and rechecks configuration revision, tested state, plan, scope, conversation lifecycle and expiry.
Putting those checks at execution time closes the gap between an earlier proposal and the current state of the product. A customer may confirm after an administrator has changed the integration, a conversation has expired or a subscription has changed. Authorization has to be valid when the side effect occurs.
Consider a customer who receives a proposal while an integration is on revision 7. Before they confirm, an administrator changes its endpoint or its input mapping. The displayed action may have the same name, but the operation behind that name has changed. Carrying the old approval across to revision 8 would mean executing something the customer never reviewed.
This is why I care about the revision being part of the pending action, rather than an incidental field on the settings screen. Editing the configuration invalidates the earlier test and disables the action. Pending work tied to the old configuration is cancelled. To make the new definition available again, the current revision has to be tested and enabled.
Approval belongs to a specific operation.
The execution claim combines eligibility with the transition out of pending state. A successful earlier read is insufficient because another request can change the database before a later write. The inspected SQL checks the pending status, expiry, matching action revision, site and workspace relationship, entitlement and conversation lifecycle as part of claiming the run.
Imagine two copies of a confirmation arriving close together. Both can carry the same legitimate customer decision. What they must not acquire is two independent rights to call the provider. The transition from pending to running gives that ownership to one execution attempt. A second caller encounters the changed state instead of creating a second owner.
Customer confirmation also does not replace business authorization. A customer can agree to a request that has expired, or to an integration that is no longer eligible under the current configuration. The correct response is to stop and explain the changed state. Remembering a click is useful evidence of intent, but it is not a permanent exemption from the application’s rules.
Even the result vocabulary needs care. The built-in request adapter can record a return request; that is not the same as approving a refund. Its result says that the request was recorded. I would keep that distinction visible in the conversation because a model can otherwise turn a successful administrative step into a promise that the business has not made.
The cost of this approach is additional state and an occasionally interrupted customer journey. An administrator’s edit can force a new proposal rather than allowing the old button to keep working. I consider that friction justified at a side-effect boundary. A smooth interface is not useful if its confirmation means different things before and after a settings change.
The provider accepted the write. Then the connection disappeared.
One integration failure I pay particular attention to is an API that accepts a write and then loses the response. Treating that as a simple failure creates a temptation to retry, which can repeat a charge, cancellation or record change. Treating it as success invents certainty the application does not have.
The action implementation distinguishes this ambiguous write outcome as unknown. A failed read can be reported as failed; an uncertain write needs a different recovery decision. This is a useful example of designing the state model around what a distributed system can actually know.
Action proposals are also bounded by durable identifiers, including a uniqueness constraint on the turn that created them. A repeated request should find the same proposal rather than create another independent opportunity to perform the same change. Execution claims are atomic, so competing requests cannot both assume they own a pending action.
Responses from external providers pass through configured result mappings. Secrets stay on the server, and customer-facing failures use a safe error vocabulary rather than exposing arbitrary upstream error bodies. These choices make the integration contract narrower and more predictable.
It helps to draw the failure between the two systems. Reesponder sends a request. The provider commits the change. Before the response reaches Reesponder, the connection ends or the request times out. From the caller’s perspective, this can look very similar to a request that never reached the provider. From the business’s perspective, those two situations are completely different.
No response does not mean no side effect.
The implementation takes a conservative approach to errors from external writes: it can store the result as unknown rather than claiming a definite failure. That state does not mean the operation probably worked. It means the application does not have enough evidence to make a stronger statement. Recovery needs to preserve that uncertainty instead of replacing it with a reassuring message.
The adapter sends an idempotency key derived from the run identifier. This gives a cooperating provider a stable identity for the operation. But adding a header does not force another system to honor it, retain it for a particular period, or apply it to all side effects. I would verify the provider’s actual contract before treating a retry as safe.
There are therefore two separate protections here. The database claim prevents two local requests from both owning one pending action. Provider-side idempotency, where supported, can prevent repeat execution across network attempts. Solving the first does not automatically solve the second. Calling the whole workflow “exactly once” would conceal that distinction.
If I were extending an adapter, the recovery question would be part of its design from the start. Can the provider look up an operation by the same identifier? Does its response distinguish rejection from acceptance? Is there a durable status to reconcile? Those are questions for a specific integration, not existing guarantees that can be assumed for every configured HTTP endpoint.
Output mapping is another useful restraint. The application selects the configured scalar fields from a response instead of dumping the entire upstream body into the conversation. Customer-facing errors also use a limited vocabulary. That keeps an unexpected provider response from becoming an accidental disclosure or an invitation for the assistant to improvise an explanation from internal diagnostics.
For me, the test of this design is whether the stored state is still understandable after the original request and process are gone. A support engineer should be able to distinguish a pending proposal, a claimed run and an uncertain external result. Those distinctions are what make a later recovery decision possible.
A human handoff should survive the thing that made it necessary
A customer may ask for a person precisely because the automated conversation has stopped being useful. Making that escape route depend on one more successful model call would be a poor dependency. I want the request for help to exist before a generated summary becomes available.
A handoff is not just a button that asks a language model to write an email. The customer confirms contact information, the API validates it, and the application records an escalation associated with the conversation. The request is scoped to the correct tenant, site and visitor context.
The implementation locks the conversation row while creating the escalation and uses a uniqueness constraint to avoid duplicating the same handoff. It stores a bounded transcript and marks summarization as pending. That creates a durable request even if a summary provider is temporarily unavailable.
The worker can generate a summary, but transcript-based delivery remains a fallback. Lifecycle and retention conditions are checked again before work proceeds. Stable message identifiers, leases and retries support recovery when a worker stops or a delivery attempt needs to be repeated.
This changes the role of AI within the workflow. A summary can improve the experience for the receiving team, but generation is not allowed to become a single point of failure for the customer’s request to reach a person. Reliability comes from the surrounding application state and fallback behavior.
The transaction begins by locking the conversation row. The escalation is associated with that conversation, and a uniqueness constraint prevents the same conversation from acquiring duplicate escalation records through competing submissions. The transcript is bounded before storage, so an unusually long exchange does not turn an otherwise small handoff into an unbounded payload.
The request survives without the summary.
That stored transcript is more than an input to summarization. It is the fallback context that allows the workflow to continue when a summary cannot be produced. The summary-pending state makes the unfinished enhancement explicit. The business request does not have to disappear just because the enhancement is temporarily unavailable.
Worker recovery introduces its own timing problem. A worker can stop after taking responsibility for a task, or after a delivery attempt but before it records the result. Leases and stable message identifiers help the workflow recover without treating every retry as a completely new request. They still need to be interpreted alongside the delivery system’s behavior; a local retry policy cannot guarantee what every external mailbox will do.
Retention also matters after the work has been queued. A valid escalation at creation time can become ineligible for further processing when the conversation lifecycle changes. Checking again before background work proceeds prevents the queue from becoming a way to bypass a later deletion or invalidation decision.
I think of the summary as an improvement to the handoff, rather than its definition. A concise explanation can save the receiving team time, but the customer asked to reach a person. Preserving the request, its confirmed contact details and enough conversation context is the part the rest of the system must be able to depend on.
This is the same principle as the action workflow, applied to a different consequence. Record the meaningful business transition in durable state, then let asynchronous work advance it under current rules. A cache entry, a queued message or an in-flight provider call should not be the only evidence that the customer asked for something.
The questions I would take into a code review
The repository separates unit and contract checks from integration, browser and load-test areas. For this system, the most useful questions are specific: can a stale action execute after an edit; can two confirmations claim the same action; can a customer escape their site scope; and does a handoff still exist when summarization fails?
Source-level tests were inspected for action handling, grounding and human handoff. Their presence and intended scenarios are evidence of the verification design, but this portfolio preparation did not run the complete Reesponder test environment. No current coverage percentage, service-level objective or production success rate is inferred from those files.
The release model separates application source from persistent runtime data. Docker-based services and a separately delivered widget make a build an identifiable artifact. Maintenance still requires checking compatibility between the API, the worker and browsers holding an older widget, rather than assuming every client updates at the moment a server release changes.
For an action change, I would follow a single proposal across its lifetime. First it is created under a specific configuration. Then I would change that configuration, expire the conversation, or remove eligibility before confirming. Each variation asks whether the execution boundary really uses current state. A test that only exercises the successful button click would miss the reason for the boundary.
For grounding, I would keep the examples small enough that an expected answer can be stated precisely. The existing year-conflict and memory-requirement tests do that. I would also preserve negative examples: unrelated dates, multiple legitimate records and a review that attempts to return another action. A safeguard needs tests for what it must allow as well as what it must reject.
For handoff, I would interrupt the workflow between recording the request and producing the summary, then inspect the durable escalation. The important question is not merely whether an error was logged. It is whether the customer’s request still has a recoverable representation and whether processing it later still respects scope and retention.
PostgreSQL is authoritative because pending actions and escalations must survive process loss. Ephemeral coordination supports the workflow but does not replace it. That makes recovery state inspectable, at the cost of additional schema and transition logic.
Revision-based action eligibility makes configuration edits safer, at the cost of a retest step before an integration is usable again. The second grounding pass adds latency, but keeps a concrete factual check outside the first generation. Independent widget delivery reduces coupling to the main application release, while creating a compatibility responsibility.
These choices make the application less dependent on an ideal sequence of events. They also make it more demanding to change: state transitions, schema constraints, workers and older clients have to continue agreeing on what each state means. I would rather make that responsibility explicit than hide it behind a single “AI request” abstraction.
What I take from Reesponder is a practical way to evaluate an AI feature. Look past the fluent response and ask what evidence supports it, what authority it has, and what state remains when the next step fails. Those questions connect the model to ordinary software engineering, and they are where I find the most consequential design work.
Sources and verification scope
This essay describes source, release artifacts and public product material reviewed on 28 October 2026. The test results above refer to that review, not a new test run accompanying this article. Failure sequences are engineering walkthroughs, not claims about customer incidents.
TypeScript · Next.js · Fastify · PostgreSQL · pgvector · LLM integration
Return to the beginning

