AGENTO
v1.1.0 · Documentation
Documentation/Compose workflows/Confirm writes

Confirm writes

2026-10-15·source 4bebed4·5 min read·14 / 27

You will learn: Prepare a reviewable action and execute only after confirmation.

A side effect is an operation that changes business state: creating a booking, updating a profile, charging an account, or deleting an item. A model's decision to call a tool is not user approval for that change.

In AGENTO, the effect classification is executable policy. It influences compilation, confirmation, dependency eligibility, and retry safety.

Choose the effect that describes the operation

behavior.effectTypical operationEnforced consequence
read-onlyGET availability or HEAD metadataDefault; eligible as an automatic dependency.
side-effectPOST booking or PATCH profileRequires confirmation.required: true; cannot be an automatic dependency.
destructiveDELETE booking or irreversible removalRequires confirmation and has the same core write guards; it does not add a second approval screen automatically.

The current compiler rejects every non-GET/HEAD method marked read-only. Even a POST search endpoint must be configured as effectful in this release. Do not label it read-only just to bypass confirmation.

Start with the booking operation

This is the behavior block of the complete booking config:

behavior:
  effect: side-effect
  confirmation:
    required: true
    ttl_ms: 30000
    preview:
      date: $.date
      time: { source: selection, path: $.time }
      notes: $.notes

Without the effect declaration, POST fails compilation with CONFIG_EFFECT_INVALID. With an effect but without required confirmation, compilation fails with CONFIG_CONFIRMATION_INVALID. These are startup errors, not UI hints.

Adding this block changes execution: after resolving inputs and dependencies, AGENTO prepares and stores the HTTP request, returns needs_confirmation, and waits. It has not sent the booking yet.

Design a preview a person can verify

The preview is a map, not a template string. date and notes read tool input. time reads a declared selection fact, such as the chosen slot label. Display names and times are more useful than opaque IDs.

Selection facts are presentation-only. Use dependency mappings to supply the actual token for HTTP. Preview paths cannot expose credential-like or declared private fields; generated values are prohibited in previews. Undeclared selection facts fail compilation.

If you omit preview, the runtime falls back to model-visible input fields. That may be technically valid but less informative: dependency-produced IDs are hidden from the model, and the user's chosen time may be absent. Define an explicit preview for consequential actions.

The host owns the approval action

A result contains a confirmation ID, expiry, and preview. Render the preview and an explicit confirmation control. In that control's server handler:

// Called only after the user approves the displayed action.
const next = await runtime.confirm({
  sessionId: authenticatedConversationId,
  confirmationId: displayedConfirmation.id,
  auth: { token: userAccessToken },
});

Use the same authorized session and context. Do not accept an arbitrary session ID from a browser. Return the next status to the UI; confirmation can still fail at the downstream API.

A chat reply such as “yes” or “نعم” does not send the write. It returns the pending confirmation again. Your host must call confirm() with the current ID. Use cancel() for rejection; hiding a card is not cancellation.

Understand expiry and invalidation

confirmation.ttl_ms defaults to 300,000 milliseconds. Effective expiry is the earliest of this deadline and the selections/dependencies it relies on. A five-minute confirmation cannot make a 30-second slot token fresh for five minutes.

The runtime binds the confirmation to the prepared request and relevant execution context. A changed operation, changed context, expired dependency, wrong ID, or replayed approval produces a stale-confirmation outcome. Do not retry an old ID; reconstruct the intended action and ask for a new review.

Consumption is not the same as exactly-once delivery

AGENTO persists consumption before sending HTTP. The same approval is not meant to execute repeatedly. However, a network timeout can occur after the API accepted the booking, and a worker can crash between sending and recording success.

For important writes, make the backend support idempotency and an operation-status lookup. TOOL_EXECUTION_UNCERTAIN means reconcile the operation, not blindly issue a second booking.

# request fragment, only when the API honors this header:
idempotency:
  header: Idempotency-Key
  source: generated
retry:
  attempts: 3

The key is attached to the prepared request and reused across its transport attempts. A completely new invocation prepares a new key. This setting does not implement server-side deduplication for you. See HTTP policy.

Refresh an unavailable choice, then ask again

If the API returns 409 or 410 because the selected slot disappeared, add:

# Merge into behavior on the parent write tool.
recovery:
  refresh_dependency: availability
  statuses: [409, 410]

The named dependency must be directly declared by the tool. Runtime discards its stale values, refreshes the read-only lookup, and returns a new selection or confirmation. It does not silently approve a replacement.

Recovery is appropriate only if those API statuses actually mean the dependency became stale. A 409 for a different business rule should not automatically be interpreted as “choose a different slot.”

Test the policy

Run the booking fixture, then assert these boundaries: zero POSTs before approval, one POST after approval, no second POST from a repeated ID, stale IDs rejected after expiry, and a 409 refresh requiring another review. These tests prove the policy more directly than checking that a confirmation card renders.