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 example2026-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:
toolnames an existing lookup tool. It must be read-only.arguments.datereads the parent's date and passes it to that lookup.items_pathfinds the array in the lookup's full response.id_pathandlabel_pathare relative to one array item, not the response root.id_sensitivereplaces the outward-facing option IDs with opaque IDs. The original token stays available for internal mapping.facts.timecaptures a display value for a later confirmation preview. It does not authorize execution.mapcopies the chosen item'sslot_tokeninto the parent'sslot_token. Left is the parent destination; right is the selected item source.ttl_msgives 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:
- Validate the date. A missing user-owned date returns
needs_inputbefore any lookup. - Call availability with that date.
- Return
needs_selectioncontaining only options backed by the API. - Wait for the user's choice. No booking POST has happened.
- Map the selected token and validate the completed parent input.
- Prepare the booking and return
needs_confirmation. - 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 result | Runtime behavior | Host responsibility |
|---|---|---|
| No items | needs_input, possibly with an empty missing list | Explain that nothing is available and ask for another date or preference. |
| One item, no explicit preference | Selects it automatically | Continue handling the next status; a write still needs confirmation. |
| Several items | needs_selection | Render the returned options. |
| Invalid array, duplicate IDs, or non-string labels | DEPENDENCY_SELECTION_INVALID | Fix the response contract or selection paths. |
| Selected item lacks a mapped field | DEPENDENCY_UNRESOLVED | Check 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.