Skip to content

Application deep dives

FabrCore 2.0 · Release and package availability

These application stories include examples from several FabrCore versions; each article identifies its source generation. For a new build, start with the current host setup and a matching package set, then adapt the workflow. Stable 2.0.0 publication is pending. Source quick start · Release migration

APPLICATION DEEP DIVE · Voice customer service and pickup ordering

From a phone call to a confirmed pickup order

Follow an incremental build from a tenant-owned phone registration to a conversational pickup-ordering agent, streamed speech, trusted pricing tools, explicit caller confirmation, and two distinct recovery models.

The finished workflow: A caller builds and revises an order through trusted tools, hears the complete priced review, confirms it on a later turn, and receives a locally persisted confirmation with an explicit recovery policy.

THE BUILD, AT A GLANCE

Architecture, records, and the path through the work

Explore three views of the same application. The data model shows conceptual relationships rather than a complete database schema.

A text agent inside a voice pipeline. Speech transport is application code; FabrCore manages the conversation, configured tools, and saved agent state.
Speech transport is application code; FabrCore manages the conversation, configured tools, and saved agent state.Open full-size diagram in a new tab
Read the diagram as text
  • The call pipeline sends transcripts to the agent and consumes response text.
  • The agent streams text from the selected chat model.
  • Catalog tools own item validation and authoritative prices.
  • Confirmed local orders are recorded and flushed to FabrCore agent state.
  • The configured call-control plugin resolves the current active call.
Call identity, cart state, and history. This view shows the catalog-ordering variant. The separate draft-resume variant uses its own storage records.
This view shows the catalog-ordering variant. The separate draft-resume variant uses its own storage records.Open full-size diagram in a new tab
Read the diagram as text
  • One phone registration handles many calls.
  • Each active call contains a sequence of transcript turns.
  • Caller identity is scoped to the phone registration.
  • The catalog agent uses a stable caller-derived handle; caller ID is not authentication.
  • The catalog workflow creates a fresh in-memory cart on initialization.
  • Completed history belongs to the caller-scoped agent state.
  • The cart uses catalog IDs and deterministic prices.
  • The ordering session binds review to a cart revision and turn.
From spoken request to confirmed local order. Caller confirmation is checked against the current cart revision and a later conversational turn.
Caller confirmation is checked against the current cart revision and a later conversational turn.Open full-size diagram in a new tab
Read the diagram as text
  • The application recognizes the caller speech.
  • A final transcript becomes an addressed FabrCore request.
  • The agent invokes catalog tools to edit the cart.
  • The cart produces authoritative totals and a revision-bound review token.
  • The caller hears the priced order before confirmation.
  • Edits invalidate the previous review and require another readback.
  • Confirmation on a later turn must match the current cart and review token.
  • The local completed-order snapshot is flushed to agent state.

ONE POSSIBLE CLIENT EXPERIENCE

A possible web interface

An optional operator console can show the transcript, authoritative cart and readback status while the caller uses the telephone. The application confirms local order state; connecting external fulfillment is a separate integration.

Illustrative operator console for a voice order with caller transcript, two pizzas totaling a fictional 28 dollar subtotal, pending confirmation and transfer-to-staff control.
Illustrative design · fictional data. AI-generated concept, not an existing application screenshot or a working interface. Your client can take a different form.Open full-size concept in a new tab

Follow the work

  1. A tenant-owned phone registration supplies desired status, connection settings, and selected agent behavior.
  2. A background service reconciles registrations with running SIP connections.
  3. An incoming call creates media resources and obtains a registration-scoped FabrCore principal context.
  4. The application creates or reconfigures an agent with its model, instructions, call-control plugin, and call arguments.
  5. Final speech transcripts become FabrCore request messages; response deltas feed application-owned speech playback.
  6. Local menu tools validate choices, calculate prices, and maintain authoritative order data.
  7. The caller hears a complete priced review and explicitly confirms it on a later turn.
  8. The application persists local completion information, records call termination, and cleans up media; transfer tools can connect a person when needed.

Give each agent a clear lifetime

Agents, application services, and the state they own
RoleLifetimeResponsibilityState
Registration reconcilerHosted background serviceStart, stop, and restart SIP registrations to match saved desired state.SQL registration records plus in-process timestamps and runtime instances.
Call pipelineOne active phone callAnswer SIP, decode audio, dispatch transcripts, stream speech, handle interruption, and release resources.In-process media, cancellation tokens, turn channels, and active-call tracking.
General voice agentCall-derived agent identityConduct a configurable conversation and expose call controls.Framework-created conversation session; no application business cart.
Menu information agentCall-derived agent identityAnswer from an embedded menu reference while declining order placement.Embedded reference and conversation session.
Draft-resuming order agentCall-derived agent identity with separately stored caller dataBuild numbered pizza and sandwich items, enforce completion, and drive deterministic checkout.FabrCore storage records keyed by registration and normalized caller; one-hour draft resume window and caller profile.
Catalog order agentStable caller-derived agent identity within a registration principalUse structured menu IDs, merge or split quantities, review totals, and retain completed-order history.Fresh in-memory cart during initialization; completed-order history loaded and flushed through FabrCore agent state.
Call-control pluginInitialized for the current call configurationTransfer, announce a transfer, hold, unhold, hang up, or send DTMF.Current call ID and SIP domain; live resources resolved through the active-call tracker.
About the source and code examples

Based on application generation 87c69ee (2026-07-27), reviewed 2026-09-13: .NET 10, FabrCore.Host/Sdk 1.3.1, Surface/Admin 0.4.10. Neutral examples are source-adapted excerpts, not standalone projects; builds and live calls were not run. Linked documentation may describe newer APIs.

Examples use neutral names. Application records, repositories and external adapters shown in excerpts are contracts you implement; they are not built-in FabrCore services. The client interface is yours to design.

Begin with a business record that owns the phone behavior

Start with a phone registration owned by a tenant. It records the SIP connection settings, chosen agent type, answer greeting, initial prompt, and system prompt. Desired status and runtime status are separate: an operator can request an active line even while registration is still in progress or has failed. SQL persistence and domain commands give the voice workflow a durable starting point before any model is invoked.

That distinction gives the developer a concrete first milestone: save an inactive registration, activate it, and observe the runtime status converge. A hosted service polls every ten seconds, starts missing active registrations, stops inactive ones, and restarts instances after configuration changes. Failed instances have a sixty-second restart cooldown. These are application services; FabrCore does not replace the registration aggregate or its database.

The three text fields have different jobs. The greeting is spoken directly through text-to-speech. The optional initial prompt is sent through the agent to initiate a conversational turn. The system prompt shapes ongoing behavior and is combined with specialized business instructions and call-control guidance. Keeping those roles explicit prevents an introductory announcement from being mistaken for durable conversation policy.

1. Application-defined registration members, excerpted from the domain aggregate
// Excerpt inside the application registration aggregate.
// Surrounding entity members and persistence mapping are omitted.
public string AnswerGreeting { get; private set; } = string.Empty;
public string InitialPrompt { get; private set; } = string.Empty;
public string SystemPrompt { get; private set; } = string.Empty;
public RegistrationDesiredStatus DesiredStatus { get; private set; }
public RegistrationRuntimeStatus RuntimeStatus { get; private set; }

public void SetDesiredStatus(RegistrationDesiredStatus status)
{
    DesiredStatus = status;
    UpdatedAtUtc = DateTime.UtcNow;
}

Host the processing runtime and select a named model

Next, add the FabrCore server to the application's dependency-injection container. AddFabrCoreServer installs the runtime integration, and UseFabrCoreServer activates its host integration after the application is built. Domain services, the active-call tracker, speech configuration, and registration management stay alongside it. This composition lets an agent resolve application services without making the language model responsible for connection management.

Configure the model independently of the phone registration. The call path sets Models to sip-realtime; initialization can also accept a ModelConfig argument that overrides that choice. The named entry in ModelConfigurations connects that logical name to provider, model, endpoint, and credential alias. The same alias can therefore serve several agent types without copying provider settings into every registration.

The name sip-realtime describes the application's chosen profile. Execution uses a streaming chat client with separate speech recognition and synthesis. That separation is useful: the agent accepts text and produces text, while the phone adapter owns codecs, recognition events, speech voices, and playback. Changing business tools does not require rebuilding that transport path.

Enable FabrCore's in-memory message monitor to correlate requests, responses, and tool calls while developing. The application caps captured payloads at 8,000 characters, tool arguments at 4,000, and buffered calls at 1,000, and supplies a redaction callback. Monitoring explains processing behavior; order records carry business outcomes. Keeping those responsibilities separate makes a short-lived diagnostic buffer a useful tool without turning it into the order database.

2. FabrCore host and bounded monitoring registration excerpt
var builder = WebApplication.CreateBuilder(args);
var options = new FabrCoreServerOptions();
options.UseInMemoryAgentMessageMonitor(capture =>
{
    capture.CapturePayloads = true;
    capture.MaxPayloadChars = 8_000;
    capture.MaxToolArgsChars = 4_000;
    capture.MaxBufferedCalls = 1_000;
    capture.Redact = value => Regex.Replace(
        value,
        @"(?i)(api[_-]?key|subscription[_-]?key|password|secret|token)\s*[:=]\s*[^\s,;\}\]]+",
        "$1=***");
});
builder.AddFabrCoreServer(options);

// Register domain, speech, and principal-context services here.
// Remaining startup is omitted; this is an excerpt, not a full host.
var app = builder.Build();
app.UseFabrCoreServer();

Explore the features used here:

Give each call the right agent identity and configuration

After answering an incoming call, obtain an ISurfacePrincipalContext through GetOrCreateAsync. This is the neutral client-to-runtime contract used here: get a principal context, create an agent, and exchange addressed messages. The principal handle is derived from the registration ID, so callers to one registration share that outer scope. The individual agent handle supplies the conversation identity within it.

Most agent types receive a handle derived from the call ID. The catalog order agent instead derives a stable handle from normalized caller identity and a truncated SHA-256 hash. Returning calls can reach the same logical agent within the registration. ForceReconfigure is enabled for this case to apply current call-control arguments and rebuild call-specific initialization. Caller ID provides continuity; it remains distinct from authenticated customer identity.

AgentConfiguration connects identity to behavior: AgentType selects the registered alias, Models chooses a named model configuration, SystemPrompt supplies assembled instructions, Plugins selects call controls, and Args carries call-specific settings. The excerpt shows the catalog path with neutral identifiers. The source also passes registration and caller arguments used by its alternative draft-resuming ordering implementation.

3. Adapted catalog-agent creation; identity and prompt variables are application-provided
// Inside the application's call-start method.
// principalFactory is ISurfacePrincipalContextFactory.
var context = await principalFactory.GetOrCreateAsync(
    principalHandle, CancellationToken.None);

await context.CreateAgent(new AgentConfiguration
{
    Handle = agentHandle,
    AgentType = "catalog-ordering", // Neutral replacement for source alias.
    Models = "sip-realtime",
    ForceReconfigure = true,
    SystemPrompt = assembledSystemPrompt,
    Plugins = new List<string> { "sip-controls" },
    Args = new Dictionary<string, string>
    {
        ["sip-controls:CallId"] = callId,
        ["sip-controls:SipDomain"] = sipDomain
    }
});
var addressedHandle = $"{principalHandle}:{agentHandle}";

Explore the features used here:

Build one shared conversation lifecycle before adding business tools

The shared voice base derives from FabrCoreAgentProxy. Its OnInitialize override resolves configured tools, allows a specialization to add more tools, and calls CreateChatClientAgent. That FabrCore helper returns both an agent and a conversation session. The source retains them as Microsoft agent-framework AIAgent and AgentSession objects and reuses the session while processing messages.

Model selection has an explicit precedence: a ModelConfig argument, then config.Models, then sip-realtime. Thread identity comes from the configured handle or the host handle. These details matter when adding another specialization: changing a business prompt should not require copying speech handling or inventing a second session lifecycle. The general phone agent is consequently a small class marked with AgentAlias("sip") and inheriting the shared base. AgentAlias makes that type selectable through AgentConfiguration.AgentType.

The base caps output at 500 tokens; the draft-resuming and catalog order agents raise that to 750 and 1,000 respectively to accommodate readbacks. A PrepareUserInput hook adds authoritative order context before each caller transcript. That context is produced by application code, while the original transcript remains available to the surrounding call pipeline.

4. Shared FabrCore agent initialization excerpt
// Excerpt inside an application class deriving FabrCoreAgentProxy.
// AddAgentToolsAsync and ConfigureChatOptions are application hooks.
public override async Task OnInitialize()
{
    var modelConfigName =
        config.Args?.GetValueOrDefault("ModelConfig") ??
        config.Models ?? "sip-realtime";
    var tools = await ResolveConfiguredToolsAsync();
    await AddAgentToolsAsync(tools);

    var result = await CreateChatClientAgent(
        modelConfigName,
        threadId: config.Handle ?? fabrcoreAgentHost.GetHandle(),
        tools: tools,
        ConfigureChatOptions);
    _agent = result.Agent;
    _session = result.Session;
    // Source also resolves the voice-stream registry and current call ID.
}

Explore the features used here:

Connect request messages to incremental speech playback

Final speech-recognition results enter ProcessAgentTurnAsync. The method serializes turns with a per-call semaphore and sends an AgentMessage through SendAndReceiveMessage. ToHandle names the agent, FromHandle identifies the registration principal, and Kind is Request. Call and turn IDs travel in Args so text generation and audio playback can be correlated without changing the caller's message.

Inside OnMessage, message.Response() creates the reply envelope. The agent calls RunStreamingAsync with the prepared input and retained session, appends text deltas into the final reply, and simultaneously writes those deltas to an application-owned voice-stream registry. FabrCore supplies addressed request/reply messaging; the channel registry and streaming text-to-speech player are application code.

This parallel path lets synthesis begin before the whole answer arrives. The pipeline suppresses output after transfer, hold, or hangup and supports interruption from speech-start events, with filtered partial transcripts as a fallback. It cancels response playback and clears queued audio. Audio cancellation and processing cancellation remain separate: RunStreamingAsync receives no cancellation token here, so interrupting speech does not guarantee that model generation or a running tool stops.

Give every turn its own correlation ID even when several turns belong to one call. BeginTurn creates the stream before dispatch; the agent writes deltas under that call-and-turn pair, then completes the stream. The channel is an in-process delivery aid, so a process restart loses pending speech. Persist the business result through the state APIs rather than relying on successful audio delivery.

5. Request/reply dispatch and the application-defined streaming contract
// Call-pipeline excerpt: context is ISurfacePrincipalContext.
var response = await context.SendAndReceiveMessage(new AgentMessage
{
    ToHandle = addressedHandle,
    FromHandle = principalHandle,
    Message = inputText,
    Kind = MessageKind.Request,
    Args = new Dictionary<string, string>
    {
        ["sip-voice:CallId"] = callId,
        ["sip-voice:TurnId"] = turnId
    }
});

// Separate application interface, not a FabrCore SDK interface.
public interface IVoiceResponseStreamRegistry
{
    VoiceResponseTurn BeginTurn(string callId, string turnId);
    bool TryWriteDelta(string callId, string turnId, string textDelta);
    bool CompleteTurn(string callId, string turnId, Exception? error = null);
}
// VoiceResponseTurn is the application's channel-backed turn object.

Explore the features used here:

Move menu choices and money into deterministic tools

With one reliable conversation path established, the business layer becomes a collection of local tools. The catalog agent loads an embedded structured menu with stable item, variant, and option IDs. Search and quote tools expose those choices. Add and update methods validate required options and calculate prices in code, including the catalog's tax rule. The language model chooses tools and explains results; it is instructed to use their monetary outputs.

FabrCore's configured plugin tools and specialization tools meet in the same list. The specialization wraps its methods with AIFunctionFactory.Create, using Description attributes to explain arguments and intended use. Application-owned ordering services remain responsible for validation. A prompt saying that a price is authoritative would be insufficient without these local calculations and rejection paths.

The catalog workflow combines identically configured items by increasing quantity and provides atomic tools for changing only some units of a combined line. The alternative pizza-and-sandwich workflow tracks numbered incomplete builders, asks for missing choices, and requires item verification before commitment. A separate menu-information agent explicitly declines ordering. These are implemented alternative behaviors, not one agent silently switching between incompatible cart models.

Each tool returns structured data together with a spokenResponse. The model can use stable IDs and internal item numbers for subsequent edits while speaking a concise business explanation. Before another turn, PrepareUserInput serializes the current order and prepends it to the transcript. This refresh prevents earlier conversational descriptions from becoming the working cart after a tool has changed quantities or options.

6. Adapted catalog tool exposure and actual quote method shape
// Excerpts inside the specialized application agent.
// CatalogOrderSession, MenuCatalog, CompletedOrderHistory, Ordering,
// SearchCatalog, and Serialize are neutral application-defined names.
protected override async Task AddAgentToolsAsync(IList<AITool> tools)
{
    _ordering = new CatalogOrderSession(MenuCatalog.Load());
    _orderHistory = await GetStateOrCreateAsync(
        "completed-orders", () => new CompletedOrderHistory());
    _currentTurn = 0;
    tools.Add(AIFunctionFactory.Create(SearchCatalog));
    tools.Add(AIFunctionFactory.Create(QuoteMenuItem));
    tools.Add(AIFunctionFactory.Create(ReviewOrder));
    tools.Add(AIFunctionFactory.Create(SubmitOrder));
    // Additional add, update, split, and history tools are omitted.
}

[Description("Quote a menu item using stable catalog IDs without adding it.")]
private string QuoteMenuItem(
    string itemId, int quantity = 1,
    string? variantId = null, string? optionIds = null,
    string? specialInstructions = null) =>
    Serialize(Ordering.QuoteStandardItem(
        itemId, quantity, variantId, optionIds, specialInstructions));

Explore the features used here:

Treat human assistance as a real call-control operation

Make reaching a person an executable capability. The call-control plugin implements transfer, announced transfer, hold, unhold, hangup, and DTMF operations against the active SIP call. An agent can request these operations during the conversation and receive a success or failure result. The useful contract is the state of the call after the tool returns, not the fluency of a promise to connect someone.

The plugin carries PluginAlias("sip-controls"), implements IFabrCorePlugin, and reads namespaced settings with GetPluginSetting during InitializeAsync. ResolveConfiguredToolsAsync makes configured plugin tools available to the shared agent. A call ID connects the plugin to the active-call tracker, and the SIP domain helps validate destinations. This small initialization contract keeps transport handles out of model-generated arguments.

AnnouncedTransfer places the caller on hold, makes an outbound call, speaks an announcement, and attempts attended transfer. Success suppresses further agent speech. Failure paths clean up the outbound resources and attempt to restore the caller. The person joins through this phone transfer; order confirmation still belongs to the caller's explicit response in the checkout conversation.

7. Plugin initialization excerpt and callable operation signatures
// Excerpt inside SipControlsPlugin : IFabrCorePlugin.
// The class carries [PluginAlias("sip-controls")].
public Task InitializeAsync(
    AgentConfiguration config, IServiceProvider serviceProvider)
{
    _callId = config.GetPluginSetting("sip-controls", "CallId")
        ?? throw new InvalidOperationException("CallId is required.");
    _sipDomain = (config.GetPluginSetting("sip-controls", "SipDomain")
        ?? throw new InvalidOperationException("SipDomain is required.")).Trim();
    return Task.CompletedTask;
}

// Signature excerpts only; source bodies perform SIP operations:
// public async Task<string> TransferCall(string extension)
// public async Task<string> AnnouncedTransfer(string extension, string announcement)
// public async Task<string> HoldCall()
// public Task<string> UnholdCall()
// public Task<string> HangupCall()
// public async Task<string> SendDtmf(string digits)

Explore the features used here:

Make caller confirmation a checked transition

Checkout is where a conversational workflow needs a precise boundary. The catalog order service requires a nonempty cart and pickup name before review. ReviewOrder captures the current revision, priced totals, a review token, and the current turn number. Its spoken response supplies the complete readback. Every subsequent edit invalidates the basis for that confirmation.

SubmitOrder rejects missing confirmation, an absent or stale review, a mismatched token, and confirmation on the same turn as review. This prevents one tool-calling sequence from reviewing and submitting without another caller turn. The boolean asserting caller confirmation still comes from model interpretation; the code enforces timing and consistency rather than independently proving the caller's words.

After local submission succeeds, record a completed-order snapshot, call SetState, and await FlushStateAsync. FabrCore agent state supplies the persistence API for that history: SetState buffers the value and FlushStateAsync is the explicit write boundary used by this agent. The confirmation represents a locally completed pickup order. External kitchen acceptance and payment are separate transactions beyond this workflow.

8. Adapted submit tool; validation belongs to the application ordering service
// Excerpt inside the catalog order agent.
// Ordering.SubmitOrder checks draft status, confirmation, review token,
// matching revision, and currentTurn > review.ReviewedOnTurn.
// Ordering, OrderHistory, and Serialize are application-defined helpers.
private async Task<string> SubmitOrder(
    string reviewToken, bool callerConfirmedTotal)
{
    var result = Ordering.SubmitOrder(
        reviewToken, callerConfirmedTotal, _currentTurn);
    if (result.Success)
    {
        OrderHistory.Record(Ordering);
        SetState("completed-orders", OrderHistory); // Neutral state-key adaptation.
        await FlushStateAsync();
    }
    return Serialize(result);
}

// Initialization elsewhere uses the same neutral key:
// _orderHistory = await GetStateOrCreateAsync(
//     "completed-orders", () => new CompletedOrderHistory());
// CompletedOrderHistory is the renamed application history model.

Explore the features used here:

Recover the right state instead of promising universal memory

The two ordering implementations intentionally recover different things. The draft-resuming agent uses IFabrCoreStorageProvider directly through GetAsync and UpsertAsync. Its application store keys records by registration and a hash of normalized caller identity. A draft resumes only when its update time is not in the future and is less than one hour old. Completed, cancelled, or expired orders produce a new draft; a separate profile can retain the pickup name.

The catalog agent instead loads completed-order history with GetStateOrCreateAsync and creates a fresh in-memory ordering session during initialization. Its history preserves configuration IDs, quantities, menu version, and prices for questions about previous orders. Reordering can use those IDs through normal add tools, allowing the current catalog to validate and price the new order. A stable agent handle is therefore not evidence that an interrupted cart survives reconfiguration.

On hangup, record the call end and dispose the active media pipeline. That cancels call resources and releases the speech session; it does not explicitly delete the FabrCore agent. Treat live media, conversation sessions, stored drafts, and completed history as different lifetimes. The durable business data belongs behind FabrCore's configured state or storage provider, while the application SQL database owns registrations and call records.

Explore the features used here:

Finish with observable completion and deliberate recovery

Instrument the milestones that explain the caller experience: final transcript received, first model token, full response, first speech output, and interruption completion. The application logs these alongside call and turn IDs. FabrCore message monitoring adds visibility into agent requests and tool activity, while call-start and call-end records supply operational history. These observations answer different questions and should remain separately useful.

Exercise one complete journey from registration through answer, item changes, priced review, later-turn confirmation, persistence, and hangup. Then interrupt the same journey at its important boundaries. A stale review must require another readback. An unfinished builder must block checkout. An expired draft must start fresh. A failed transfer must release the outbound call and recover the waiting caller.

Persisting completion deserves its own failure scenario. The catalog service changes the in-memory order to Submitted before the agent flushes history. If that write fails, a generic retry can encounter an already-submitted cart. Handle this boundary explicitly when extending the workflow, rather than promising exactly-once completion from a conversational success message. The storage-backed alternative also saves the order and caller profile in separate writes.

Keep acceptance grounded in the behavior being delivered. The repository's focused tests cover stable caller handles, menu validation, deterministic prices, review timing, history serialization, draft expiry, duplicate incoming-call admission, and interrupted streaming playback. Those are useful checks for each incremental layer: identity first, a single message next, a validated cart after that, and finally a confirmed result with a clear recovery rule.

Explore the features used here:

Prove the workflow end to end

Use these acceptance scenarios as you implement the story. They describe checks to run in your environment.

  1. Save an inactive registration, request Active, and verify that the reconciler starts it and reports runtime status; modify its prompt and verify restart.
  2. Call a configured line and distinguish the direct spoken greeting from the optional initial agent prompt.
  3. Submit one final transcript and verify one addressed request, correlated call/turn IDs, incremental speech, and a final reply.
  4. Interrupt ongoing speech and verify that synthesis/playback is cancelled and stale queued audio does not resume.
  5. Request an invalid catalog option and verify rejection before any priced cart mutation.
  6. Combine identical items, change only one unit, and verify both resulting quantities and the deterministic total.
  7. Review an order and attempt submission in the same turn, with a stale token, and after an edit; each must fail until a fresh review and later caller confirmation.
  8. Complete an order and verify persisted history contains exact configuration IDs; separately verify the absence of an external kitchen or payment transaction.
  9. Reconnect to the draft-resuming agent within one hour, then after expiry; verify resume versus new-draft behavior.
  10. Reconnect to the catalog agent and verify completed history is available while the active ordering session starts fresh after initialization.
  11. Attempt an announced transfer to an unavailable destination and verify outbound cleanup and caller recovery.
  12. Hang up while a response is being generated and verify call-end recording and media cleanup; separately exercise persistence failure and document the resulting order state.

Implementation notes

Four selectable behaviors remain distinct: general conversation, informational menu guidance, draft-resuming ordering, and catalog ordering with completed history.

Submission completes local order state and persistence. Kitchen acceptance and payment are separate business transactions.

The model interprets caller confirmation; deterministic guards require a current review and a later turn. Generated approval text alone does not authorize completion.

Call and turn queues are in-process, unbounded, and non-durable. Speech interruption does not guarantee cancellation of model generation or tool execution.

The catalog agent recreates its active cart during initialization while retaining completed history. The alternative order agent resumes stored drafts only within its one-hour window.

Caller-derived identity supplies continuity, not authentication. Concurrent calls sharing an agent identity require explicit coordination.

Agent-state persistence, storage-provider records, and SQL domain records have distinct lifetimes and write boundaries. Local submission followed by a failed persistence write needs deliberate recovery.

Call cleanup disposes media without explicitly deleting the agent. Configure retention and storage durability for the intended deployment.