AGENTO
v1.1.0 · Documentation
Documentation/Compose workflows/Resolve API-owned inputs

Resolve API-owned inputs

2026-10-15·source 4bebed4·6 min read·13 / 27

You will learn: Obtain API-owned values before the parent operation runs.

A user can ask for an appointment on a date. They cannot know the opaque slot_token your API requires. A dependency turns that gap into an explicit lookup and, when needed, a real choice.

The parent tool declares what must be known before it can execute. AGENTO runs read-only tools to obtain those facts, maps them into the parent's input, then resumes the parent. It is not a sequence the model improvises.

Use the runnable booking example alongside this guide. Read mapping first if $.date is unfamiliar.

Separate user-owned and API-owned input

Our book tool needs two fields:

  • date: supplied by the user, for example 2026-10-01.
  • slot_token: supplied by the availability API after a valid slot is chosen.

Both belong in the execution input schema, including required: [date, slot_token]. But the dependency fills slot_token. The compiler removes top-level dependency-produced fields from the model-facing schema. The model extracts a date; it is not asked to invent a slot token.

That removal is currently top-level. Prefer flat fields for API-owned identifiers instead of assuming arbitrary nested targets will be hidden from the model.

Add the lookup to the parent

This fragment belongs directly on book, next to tool, behavior, and request. It assumes an availability tool accepting date and returning the response below.

depends_on:
  - tool: availability
    when: always
    arguments:
      date: $.date
    select:
      items_path: $.slots
      id_path: $.slot_token
      label_path: $.label
      id_sensitive: true
      facts:
        time: $.label
    map:
      $.slot_token: $.slot_token
    ttl_ms: 30000
{
  "slots": [
    { "slot_token": "slot-a", "label": "10:00" },
    { "slot_token": "slot-b", "label": "11:00" }
  ]
}

Read the declaration from top to bottom:

  1. tool names an existing lookup tool. It must be read-only.
  2. arguments.date reads the parent's date and passes it to that lookup.
  3. items_path finds the array in the lookup's full response.
  4. id_path and label_path are relative to one array item, not the response root.
  5. id_sensitive replaces the outward-facing option IDs with opaque IDs. The original token stays available for internal mapping.
  6. facts.time captures a display value for a later confirmation preview. It does not authorize execution.
  7. map copies the chosen item's slot_token into the parent's slot_token. Left is the parent destination; right is the selected item source.
  8. ttl_ms gives these facts 30 seconds of validity. It also limits how long a dependent confirmation can remain valid.

If the API returns { data: { slots: [...] } }, change items_path to $.data.slots. Leave id_path: $.slot_token unchanged because it still reads one item.

Trace one invocation

let result = await runtime.invoke({
  sessionId: "booking-demo",
  tool: "book",
  arguments: { date: "2026-10-01" },
});

With two slots, the sequence is:

  1. Validate the date. A missing user-owned date returns needs_input before any lookup.
  2. Call availability with that date.
  3. Return needs_selection containing only options backed by the API.
  4. Wait for the user's choice. No booking POST has happened.
  5. Map the selected token and validate the completed parent input.
  6. Prepare the booking and return needs_confirmation.
  7. Only an explicit confirm() call sends the POST.

The UI should render every returned option and submit the option the user actually selected:

// In the UI choice handler, after the user selects an option:
const next = await runtime.select({
  sessionId,
  selectionId: result.selection!.id,
  choice: selectedOption.id,
});

Do not substitute the array index for choice, and do not automatically choose options[0] in a real interface. A pending chat() can recognize displayed one-based numbers or an exact label; select() expects the returned ID.

What changes with the number of results?

Lookup resultRuntime behaviorHost responsibility
No itemsneeds_input, possibly with an empty missing listExplain that nothing is available and ask for another date or preference.
One item, no explicit preferenceSelects it automaticallyContinue handling the next status; a write still needs confirmation.
Several itemsneeds_selectionRender the returned options.
Invalid array, duplicate IDs, or non-string labelsDEPENDENCY_SELECTION_INVALIDFix the response contract or selection paths.
Selected item lacks a mapped fieldDEPENDENCY_UNRESOLVEDCheck the right-hand side of map against one actual item.

An empty result is not a malformed result. Preserve this distinction in your UI.

Match a preference without inventing an ID

Add an optional preferred_time string to the parent's schema, then add this inside the dependency's select:

match:
  input_path: $.preferred_time
  item_path: $.label

A value such as 10:00 is compared using trimmed, case-insensitive string equality. This is not fuzzy time parsing: 4 PM does not equal 16:00 unless the extracted input and API value already use the same representation.

One exact match is selected automatically. Multiple exact matches still require a choice. No exact matches show alternatives, even if only one unrelated option exists.

Choose a freshness policy

The default is when: always. It resolves the dependency even when the destination was supplied by the caller, but can reuse a valid cached result for matching dependency arguments and session context. “Always” does not mean “perform HTTP on every turn.”

when: missing skips a not-yet-resolved dependency only when all its mapped destinations are already present. Use it when a trusted host, navigation step, or reference has already supplied the required value. It is not a validation check against the API: a direct caller can provide a value that skips the lookup.

After a dependency has resolved, its cache still expires. Changing input or context can invalidate previously prepared choices. A confirmation cannot outlive the earliest dependency or selection expiry, even if its own TTL is longer.

Use a short TTL for availability and prices. Longer-lived reference catalogs can use a longer TTL. Increasing TTL reduces lookup traffic while increasing the risk that a shown choice is no longer valid at the API.

Chain lookups only when the next one needs them

A full service booking can form book → availability → services. Declare the service lookup on availability, because availability needs a service ID before it can run. AGENTO resolves the deepest read first, then returns to its parent.

Within a parent's list, dependencies resolve in declaration order; they are not parallel tasks. A later dependency can read parent fields filled by an earlier map. Its arguments cannot directly use source: dependency; map the required value into a declared parent input first.

Cycles, duplicate dependencies, overlapping destination mappings, missing tool IDs, and effectful dependency tools are rejected at compilation. session.max_steps bounds orchestration work; it is not simply the number of tools in your file.

Use a dependency without a choice

If a lookup returns one object, omit select. The right-hand paths then read the whole response:

# Parent fragment; current-customer returns { customer: { id: "cus-42" } }.
depends_on:
  - tool: current-customer
    map:
      $.customer_id: $.customer.id

Declare customer_id on the parent and define the read-only lookup. Adding or removing select changes the root seen by map; forgetting this is a common source of unresolved values.

Prove the behavior before production

Test no date, zero slots, one slot, several slots, duplicate IDs, a missing token, an expired choice, and a changed host context. Assert that the write count stays zero until confirmation. Then test an API 409/410 with dependency refresh recovery, which must produce a new review step rather than silently book a different slot.