ai newsMi., 4. Nov.
EilmeldungGoogle’s first Suncatcher satellite runs an AI workload in orbit

“Available” Is Not Booked: A Race-Safe Contract for AI Property Viewings

“Available” Is Not Booked: A Race-Safe Contract for AI Property Viewings A voice agent can identify a property, understand a caller, read a calendar, and still create a bad appointment. The dangerous gap is between “the slot looked free” and “the booking system committed it.” If two calls cross tha

dev.toMittwoch 04:34 · 04.11.21 Min. Lesezeit

Posted on Nov 4 • Originally published at vocalis.pro

A voice agent can identify a property, understand a caller, read a calendar, and still create a bad appointment. The dangerous gap is between “the slot looked free” and “the booking system committed it.” If two calls cross that gap, both callers can hear a confident confirmation while only one viewing can happen. The decision rule is simple: say “confirmed” only after the system that owns the viewing has durably accepted one specific booking, under a conflict rule that also applies to concurrent requests. Our real-estate agency overview describes the voice-agent use case; this guide adds the operational contract a buyer should demand before letting an agent confirm viewings.

The useful question is not “Can the agent find open times?” It is “What happens when two people choose the same time, a tool call is repeated, the calendar response is lost, or the listing’s overlap policy changes?” The answer should be visible in the state model, database constraint, tool response, caller wording, and audit receipt—not left to a prompt instruction such as “do not double book.”

A product demo often shows a happy path: a caller asks for a viewing, the agent offers a time, the caller agrees, and a calendar card appears. Product pages make similar claims. Listline, for example, says it checks real availability and books showings directly to a calendar. Those statements describe intended capability, not the concurrency semantics behind a write. They do not tell a buyer whether an offer is a temporary hold, whether simultaneous requests are serialized, or what the agent says after a timeout. Treat the distinction as a procurement question, not an accusation about a vendor.

Google Calendar’s FreeBusy endpoint returns free/busy information for a set of calendars. Microsoft Graph’s getSchedule endpoint likewise returns availability information. These are valuable reads, but neither endpoint description says that reading an open interval reserves it. The act that writes an event is a separate operation. Therefore, a caller must not hear “you are booked” merely because an availability lookup returned an empty busy interval. Google’s FreeBusy reference and Microsoft’s getSchedule documentation support that narrow distinction.

The system should give the agent a result with an explicit status such as confirmed, conflict, pending_reconciliation, or rejected. The agent translates that status into a sentence. It must never infer a stronger outcome than the scheduler returned. If the write outcome is unknown, say that the request is being checked and provide a reference or a callback path; do not silently create another event or offer a different time while the first write may still exist.

A viewing is not just a start time. It binds a property, a date and time, the person or team responsible for access, a duration, a buffer rule, and often an attendee or contact record. A valid contract names which system owns each field. The listing system may own property status and showing rules; a scheduling service may own the booking; a calendar may mirror it; a CRM may store the prospect and follow-up. If two systems can both accept the same slot independently, a “calendar connected” badge does not settle the source-of-truth question.

Write down the conflict key before testing. For a simple one-agent, one-property showing, it could be (property_id, agent_id, start_time, duration). If the seller permits a fixed capacity of two concurrent showings, the guard must represent that capacity instead of assuming one. If one appointment consumes several resources—property access, an agent, a key holder, or a room—every constrained resource must be checked and committed as one logical operation. Reserving only the agent’s calendar while the listing calendar remains writable leaves a second path around the rule.

The slot’s time representation also belongs in the contract. Keep an unambiguous instant, the property’s local time zone, the intended duration, and the displayed local time. Ask the caller to clarify an ambiguous “Saturday at nine” before writing. Test daylight-saving transitions, because a local clock label can be skipped or repeated when the offset changes. A proper confirmation repeats the property and local date, time, and time zone; a bare “tomorrow morning” is not a durable receipt.

Finally, decide whether overlap is a conflict or a business rule. ShowingTime’s help material notes that some markets allow overlapping showings and may also enforce buffer time. That is a useful contradiction to the blanket slogan “no double bookings”: a safe system enforces the listing’s configured capacity, which may be one, several, or intentionally unrestricted. See ShowingTime’s explanation of overlapping appointments and buffers.

Use explicit states, and give each state one permitted caller message. The lifecycle separates discovering an option from committing it. OFFERED is not a hold; COMMITTING is not confirmation; UNKNOWN is not failure. These distinctions matter whenever an API call can time out after the other system has saved the event.

stateDiagram-v2 [*] --> REQUESTED REQUESTED --> OFFERED: fresh availability read OFFERED --> SELECTED: caller chooses exact slot SELECTED --> COMMITTING: stable request key COMMITTING --> CONFIRMED: authoritative durable success COMMITTING --> CONFLICT: write rejected as occupied COMMITTING --> UNKNOWN: timeout or lost response UNKNOWN --> CONFIRMED: reconcile finds same booking UNKNOWN --> CONFLICT: reconcile finds competing booking UNKNOWN --> COMMITTING: retry same intent and same key CONFLICT --> OFFERED: refresh and offer alternatives CONFIRMED --> CANCEL_PENDING: caller requests cancellation CANCEL_PENDING --> CANCELLED: cancellation confirmed CONFIRMED --> RESCHEDULE_PENDING: caller requests a move RESCHEDULE_PENDING --> CONFIRMED: replacement committed, old slot released REQUESTED means the caller has identified the property and requested a time. Capture only what is necessary to find the listing and respond. OFFERED means the scheduler returned candidates at a particular time.

It is a snapshot with a timestamp and source, not an entitlement to the slot. SELECTED means the caller chose one exact candidate and the agent has echoed the local date and time back for confirmation.

COMMITTING begins when the application sends a write using a stable key for this logical request. A duplicate tool call, transport retry, or worker retry must carry the same key. When the calendar service gives a durable success response and the application stores the event identifier and listing reference, the state can become CONFIRMED. If a provider has an explicit hold operation, the contract must say when that hold expires and whether it blocks other booking channels. Do not call a best-effort event insert a hold.

CONFLICT means the authoritative write rejected the request under its configured rule. Refresh availability, offer new candidates, and obtain a new choice. Do not auto-select the next slot without caller approval. UNKNOWN means the client did not learn the outcome. The write could have succeeded even though the response was lost. Search by the stable request key or provider event ID, reconcile, and only then decide whether to retry. If the provider cannot support reliable reconciliation, route to a person and use cautious language.

A cancellation or reschedule is another transaction, not a text edit. During a move, preserve the existing confirmed appointment until the replacement is committed, unless the business explicitly prefers releasing first and accepting the risk of losing both slots. Log which policy applies. If the replacement write becomes unknown, reconcile it before canceling the old event. This avoids ending with neither a confirmed time nor a clear explanation.

The classic failure is a check-then-act sequence. Request A reads “free.” Request B reads “free.” A writes an event. B writes an event. Both decisions were locally reasonable, but no shared rule serialized the writes. Refreshing availability just before insertion narrows the interval in which this can happen. It does not, on its own, make the read and write atomic. Cronofy’s guidance recommends checking availability again after the user chooses a slot, which is a useful mitigation; a buyer should still ask what the booking endpoint does if two final checks overlap. A second read is a risk reduction, not proof of exclusivity. Cronofy describes this race window.

The strongest guard belongs where the booking is committed. If you own a relational database, use a transaction plus a uniqueness or exclusion rule that reflects the business conflict. PostgreSQL documents unique constraints over a set of columns and exclusion constraints for conflicts between values; its transaction documentation also explains that Serializable transactions can fail and must be retried. Do not rely on an application-level “SELECT, then INSERT” alone: two callers can both pass the check before either insert commits. The database must decide which competing write wins. See PostgreSQL constraints and transaction isolation.

For fixed-length slots with one appointment per listing and time, an illustrative schema can make duplicate intent and occupied slots separate constraints:

CREATE TABLE viewing_booking ( booking_id uuid PRIMARY KEY, request_key text NOT NULL UNIQUE, property_id text NOT NULL, agent_id text NOT NULL, starts_at timestamptz NOT NULL, ends_at timestamptz NOT NULL, status text NOT NULL CHECK ( status IN ( 'held', 'confirmed', 'cancelled', 'expired' )), provider_event_id text, created_at timestamptz NOT NULL DEFAULT now (), CHECK ( ends_at > starts_at ) ); CREATE UNIQUE INDEX one_live_booking_per_property_agent_start ON viewing_booking ( property_id, agent_id, starts_at ) WHERE status IN ( 'held', 'confirmed' ); -- Choose an exact-slot or interval-overlap constraint that matches -- listing policy; do not copy this key blindly. Enter fullscreen mode Exit fullscreen mode That example only blocks identical start instants for the same property and agent. It does not block overlapping intervals with different start times.

If the rule is “one live showing at a time,” a range/exclusion constraint or a serializable transaction over the actual interval may be more appropriate. If overlapping showings are allowed up to a capacity, a counter with atomic updates or reservation model must enforce that capacity under concurrent writes. The code is a design sketch, not a drop-in migration; test database extensions, resource keys, cancellation semantics, and provider capabilities before production.

A booking service that delegates writes to an external calendar may not be able to wrap the calendar and CRM in one database transaction. In that case, use a durable workflow record: create a request with a unique idempotency key, call the provider, persist the response, then update local state. If the provider succeeds but the local write fails, query the provider using the stored key or event identity before retrying. If it does not expose a lookup or idempotent write, mark the outcome unknown and queue reconciliation. A distributed transaction is not created just because two API calls appear in one function.

A caller can repeat a request after silence. A speech tool can be emitted twice. Middleware can retry on a network error. The correct behavior depends on whether the repeated call represents the same logical intent. Assign a stable request key when the caller selects a specific slot—derived from a random identifier stored against that intent, not merely from the spoken phrase. Reuse that key through retries and reconciliation. A new caller choice, a new property, or a new time is a new intent and therefore receives a new key.

Google’s Calendar create-event guide explains that a client-supplied event ID can prevent duplicate event creation if a request fails after the Calendar backend has executed it. Google’s error guidance documents a duplicate-ID 409. That is an idempotency building block, not a slot-capacity lock: two different IDs can still represent two events at the same time unless a separate booking policy prevents them. See Google’s create-events guide and Calendar API errors.

Google’s SRE discussion of distributed periodic jobs makes the broader point: external side effects that may be retried after partial failure need idempotent behavior, and some systems prefer skipping a launch to risking a duplicate. A viewing workflow should use the same discipline. The side effect is not merely “call the calendar”; it is “create one appointment for this request key, under the current listing policy, and return the same outcome when asked again.” The SRE chapter on periodic scheduling and idempotency provides the reliability framing.

A response timeout is the hard case. The application sends the write; the calendar commits; the network drops before the response arrives. If the workflow now creates a fresh key and retries, it may create a second event. Instead, retain the original key and reconcile. If the booking is found, return its existing confirmation. If it is absent and the provider can authoritatively establish absence, retry with the same key. If the answer remains uncertain, do not confirm and do not issue a different booking automatically. Return pending_reconciliation to the agent, which can tell the caller that the request is being checked and promise a response channel, not an appointment.

Search results and product pages frequently emphasize “real availability,” direct calendar booking, and no double-booking. Listline’s page is a useful example of the promise buyers encounter. A vendor video hosted on Vimeo presents a real-estate assistant handling an inquiry and showing a transcript and CRM update after the call. That format can help a buyer judge conversation and visible post-call evidence. A single happy-path clip cannot establish simultaneous-request behavior, duplicate-tool suppression, stale availability handling, or the response after a lost write. Treat it as a demonstration, not a reliability test.

There is a concrete user question in Retell’s public support community: a report describes repeated booking tool calls after a streaming timeout. The thread reports five identical calls reaching an endpoint concurrently, with several writes succeeding and the flow then creating additional times. The vendor response attributes the incident to missing de-duplication of concurrent calls and recommends a concurrency-safe backend guard such as a unique constraint or short-lived lock. This is one public incident report, not a prevalence study, vendor-wide defect rate, or proof that the same failure occurs in every integration. It does expose a test a buyer can reproduce: one confirmed choice, duplicate parallel tool calls, and a timeout near the write. Read the Retell support thread.

Forrester’s 2026 practitioner discussion describes conversational agents taking on more complex actions while emphasizing controls, implementation effort, and backend testing. Its blog reports practitioner interviews, useful qualitative evidence about deployment work; it is not a benchmark of real-estate appointment correctness. The implication for a buyer is modest: assess the application and integration, not just the model’s speech. Forrester’s lessons from practitioners.

The recommendation is an inference from those sources: conversation quality, availability visibility, and transaction integrity are separate capabilities. A supplier may be excellent at the first two while relying on an external calendar that cannot reserve a slot atomically. Ask for evidence of the write contract and the unknown-outcome recovery path. If the answer is “the prompt tells the model to call once,” the core control is not present. A prompt can reduce duplicate calls; it cannot serialize concurrent requests emitted by separate calls or processes.

Keep three statements separate during a review. Fact: Google and Microsoft document availability reads separately from event creation, and the Retell community thread records one reported duplicate-write incident. Inference: a read-only availability response cannot, by itself, prove that a later write will win a conflict. Recommendation: require the commit boundary to enforce the listing’s capacity rule and require the agent to speak only from the resulting status. The sources support the distinction; the 12-case suite below is an original test proposal, not a measured result.

The following is an original synthetic test asset, designed on 4 November 2026. It contains 12 proposed cases; no live agent, calendar, CRM, customer record, or production booking was exercised. The cases are deliberately small enough for a staging run and broad enough to test consequential distinctions. The suite does not estimate failure frequency, prove legal compliance, or compare vendors. For each test, use synthetic properties, contacts, and calendar data; capture the caller transcript, tool request, tool response, scheduler event, CRM row, and final user-facing message. A case passes only when those artifacts tell the same story.

Use the suite in two passes. First, call each scenario once to check basic state transitions. Second, repeat the concurrency and timeout cases with parallel requests and deliberate network fault injection. The suite specifies expected behavior; it does not claim that those tests were run or that a vendor passes them. A buyer can add cases for multiple calendars, access instructions, property status changes, and caller corrections without changing the central rule: only the authoritative commit creates confirmation.

Do not return only free-text such as “looks good” or “try 10:30.” Return a typed result that makes it difficult for the model to overstate the outcome. The exact schema depends on the scheduler, but it should carry request identity, status, listing reference, local time zone, and any booking ID needed for reconciliation. Keep sensitive caller data out of tool output unless the next step needs it.

{ "request_key": "req_01J...", "status": "confirmed", "property_ref": "listing_7842", "starts_at": "2026-11-07T10:30:00+01:00", "time_zone": "Europe/Paris", "ends_at": "2026-11-07T11:00:00+01:00", "booking_id": "book_91f...", "provider_event_id": "evt_7c2...", "policy_version": "showing-policy-12", "committed_at": "2026-11-04T09:18:11Z" } Enter fullscreen mode Exit fullscreen mode The sample values are illustrative, not observed data. For conflict, return no confirmation ID and include a signal that availability must be refreshed. For pending_reconciliation, keep the same request_key, return the last known status and next-check instruction, and prevent a second fresh booking attempt.

For rejected, distinguish a business-rule refusal from a technical failure. For cancelled, return the appointment reference and completed cancellation state. A response contract is useful only if the service records the same state and the agent’s wording is conditional on it.

A compact tool handler might look like this:

A viewing request needs enough information to identify the listing, coordinate a time, and contact the caller about the appointment. It rarely needs an unbounded transcript to complete that booking. The UK Government’s Data and AI Ethics Framework says projects should map data flows, explain what personal data is used and why, and consider minimisation, retention, and access. That guidance is not a substitute for the law that applies in every country; it is a practical control source for teams. See the Government framework.

Set a retention rule for audio, transcripts, extracted contact details, and booking records separately. A calendar needs the event and contact channel; a debugging workflow may need a short-lived redacted trace; neither automatically justifies indefinite audio retention. Decide who can view transcripts, whether they are used for model improvement, how a caller can correct a wrong time or phone number, and how to delete data that is no longer needed. Do not put full call transcripts into a calendar description if a short reference and operational note are sufficient.

Real-estate agents also need to constrain answers about protected characteristics and neighborhood demographics. The booking assistant should answer listing facts from an approved source, collect appointment logistics, and route questions that require professional judgment to a human. Do not let the model infer suitability, eligibility, safety, or neighborhood composition from a caller’s accent, name, family status, or other proxy. This guide is not legal advice and does not decide which housing or privacy rules apply to a specific agency. Have local counsel or a qualified compliance owner validate configured rules and notices.

The privacy and fairness boundary should affect test fixtures. Use synthetic phone numbers and fictional listings. Do not paste actual caller recordings into prompts, public bug reports, or vendor support threads. Confirm that logs needed to prove a booking do not expose more personal data than the people maintaining the integration need to see. A good receipt proves the transaction without replicating the entire conversation.

The model says “confirmed” before the tool returns. This is a release blocker. The expected message after selection is a neutral “I’m placing that request now”; confirmation follows only after a durable confirmed result. If the agent speaks while the tool runs, interrupt the flow in test and verify that it does not promise the outcome prematurely.

A timeout creates a new request key. This defeats idempotency. Retry the same operation with the same key, then reconcile before creating anything else. A vendor that cannot show how a repeated key maps back to an existing event should not make unconditional confirmations.

The database check is a separate SELECT. A check followed by a write can race. Look for a unique constraint, exclusion rule, lock, or provider guarantee that covers the actual resource and interval. Ask for two simultaneous requests in staging and their resulting records, not just a screenshot of a busy calendar.

The fallback books a different slot automatically. A conflict should return control to the caller. It is not permission for the agent to choose the next time. A new time is a new decision and should receive a fresh confirmation and request key.

The calendar and CRM disagree. The calendar may contain an event while the CRM has no appointment, or the CRM may say booked while the event write failed. Define which system is authoritative, record external IDs, and run reconciliation. Do not mark both systems complete in one code path without checking each result.

Cancellation releases one system but not another. If the caller requests cancellation, reconcile calendar and CRM state. If one write fails, give the caller an honest partial outcome and create a human follow-up task. A canceled status should not be inferred from a successful speech act or a notification being queued.

The policy is too strict or too loose. A universal “no overlap” rule can block a listing that intentionally allows group showings. A universal “overlap is fine” rule can schedule two private tours when an owner requires exclusive access. Tie the constraint to a versioned property policy, including capacity and buffers, and test policy changes during an active booking flow.

Local time looks right but maps to the wrong instant. Preserve the time zone and repeat it in the caller’s local terms. Test repeated and skipped clock times around daylight-saving transitions. If a caller gives an ambiguous date, ask rather than guess.

A support ticket contains personal data. Reproduce with synthetic contacts and redact identifiers before sharing traces. Technical success does not justify exposing recordings or phone numbers broadly. Use a transaction receipt, sanitized request ID, and artificial listing.

A happy-path video is treated as a test report. A polished call can show usability and the shape of a receipt. It cannot establish retries, simultaneous commits, cancellation, time-zone handling, or outage behavior. Ask for exact inputs, API responses, final records, and expected-versus-actual results.

Use this table to decide whether to proceed, run a bounded pilot, or block autonomous confirmation. These are decision rules, not a weighted score or benchmark. A missing hard control can outweigh a fluent voice because the impact is a false promise to a caller.

Proceed only after hard transaction cases pass in staging. A bounded pilot can begin with one agent, fictional listings, and a limited time window, then move to staff-supervised calls if the operational owner agrees. Define a stop condition: any duplicate event, unsupported confirmation, untracked unknown outcome, or mismatch between caller message and saved record pauses autonomous booking until the failure is understood. Track counts of state outcomes, but do not call them a success rate until the denominator and exclusions are defined.

Start with read-only mode. Let the agent retrieve property facts and candidate slots, but have a person execute the write. Save the proposed time and availability timestamp so the team can see whether the agent selected a stale slot. This tests conversation and entity mapping without creating customer commitments.

Next, run synthetic write tests in a sandbox. Include two concurrent callers, repeated tool invocation, dropped responses, rejected writes, reschedule, and cancellation. Verify provider records, not only the transcript. Keep test calendars separate from staff calendars, and ensure notifications cannot reach real clients.

Then allow requests to remain pending for human confirmation. This reveals whether calendar, CRM, contact identity, event duration, time zone, access instructions, and listing policy are mapped correctly. Staff should see the exact transaction state and confirm or correct it without losing the original request key.

Move to autonomous confirmation only when the service enforces conflict handling and idempotency, an unknown result can be reconciled, and the owner has reviewed logs and caller wording. The operator responsible for calendar permissions should also own revocation when staff or vendors change. Keep an exception review and a way to stop new writes without disabling the phone line; a safe fallback can capture the request and arrange a callback.

For each release, keep an evidence packet: agent and tool-schema versions, listing policy version, test cases and results, redacted receipts, metrics definitions, recovery owner, and rollback method. Forrester’s practitioner discussion emphasizes that production work extends beyond the model and needs controls and testing around backend integrations. That is qualitative commentary, not a measured guarantee. Use it as a reminder to assign an integration owner, not as proof that a particular vendor is safe.

The operational conclusion is narrow: an AI agent may find times and conduct a natural conversation, but only the system that durably commits a booking can confirm it. If a vendor cannot explain how two simultaneous requests, one repeated tool call, and one lost response resolve at the write boundary, leave confirmation with a person. If it can demonstrate those states with a reusable suite and auditable receipts, run a controlled pilot and measure exceptions before expanding scope.

For a deployment that falls within the EU framework, consult the European Commission’s official AI Act overview for the current framework and implementation context. This reference does not determine whether a particular viewing assistant is high-risk or which obligations apply; that depends on the actual use, role, and deployment facts, so obtain a qualified assessment for the specific system.

Quelle: dev.to (dev.to). Original öffnen