Skip to main content
An interaction normally asks a person. A caller ask asks the run that called this one instead: the asking run pauses and hands its question up, and the calling run answers by resuming it. Nothing reaches a human inbox and nothing is delivered out of band — the question lives entirely between the two runs. A tool or agent raises one by passing to="caller" to ask:
to="caller" is always mode="async" and requires an ambient state context — the ask parks the asking run and returns a suspension sentinel at once. The answer is handed back by whoever resolves the park, never by an answer door. A caller ask may carry a structured payload the resolving run reads in place of (or beside) the question; payload is forbidden on a to="user" ask.

Parks on the subject

A caller ask parks on the asking run’s subject — the same subject a state binding resolves. Every park on that subject is one parked entry, and the run that owns the subject sees the whole set. An entry carries its id and status and, while it is a live ask, its addressing and question fields:
A status is one of asking (a pending ask), running (an ask being resumed), finished (a resolved run’s waiting outcome holding a result), or failed (a resolved run’s waiting outcome that raised). An entry’s unset optional fields are absent, never present as null keys. A tool run through the background submit door answers a park exactly as the synchronous run-tool door does: its run record ends status: parked carrying the asks entries above when the tool asked its caller — else the suspension sentinel when only a to="user" ask is open — alongside the resumed_interactions it resolved on the way.

The door contract

A door that can drive a parkable run — a conversation route, a hook, or a schedule — carries up to four optional jq expressions, the door contract. Each runs over the door’s own input document with the run’s parked entries bound as the jq variable $parked, so an author branches on what is already parked: Each expression yields one value; a program that emits more than one, or a result outside the shape its column names, is a loud error, never a silent default. An undeclared $name is refused at save.
At most one non-cancel action runs per visit: a door resumes, or takes, or starts — cancels always run first. The order is fixed for every door.

Resume, take, and cancel

  • Resume an asking caller ask by naming its id with a payload: the payload is the answer, the parked run resumes under its own identity, and its next outcome — a result, fresh caller asks, or a re-park — comes back.
  • Take a finished waiting outcome by naming its bare id: the resolved run’s result is returned. Taking a failed outcome raises, so the taker fails as the resolved run did.
  • Cancel any parked interaction by naming its id: it and every run linked above it are torn down for good. A run a door started with a receiver is delivered a single failure.

Waiting outcomes

When a resumed run finishes but the run that resumed it cannot take the result inline — a hook or a schedule fire has no receiver — the terminal waits on the subject as a finished (or failed) parked entry. The subject’s next owner takes it. A waiting outcome never taken is dropped past a retention horizon and the drop is surfaced as a platform event, never lost silently.

Extras

extras_expr builds an extras mapping handed to the started target beside its kwargs. A target declares the extras keys it reads at registration; the visit refuses an undeclared key before the run starts, so an extras injection never reaches a target that never asked for it. extras ride only a start — never a resume, take, or cancel.

The generic tools

A run inspects and resolves the parks on its own subject with three builtin tools — the LLM-facing face of the same door contract: They read and write the same subject a door contract does, so an agent resolves a caller ask by hand exactly as a route resolves one by jq.

Hidden from the inbox

A caller ask is not a human question. Every user-facing read surface hides it and every user-facing resolution door refuses it: it never appears in the interactions inbox or its stream, and the human answer door rejects it — a caller ask is resolved only by its calling run resuming, never by a person.

The concurrency cap

Caller asks — and the waiting outcomes they leave behind — are bounded by their own ceiling, INTERACTIONS_MAX_CONCURRENT_CALLER (default 10000), independent of INTERACTIONS_MAX_CONCURRENT, which bounds to="user" asks. A caller ask at the cap is refused loudly, exactly as a user ask is at its cap; a finished run’s waiting outcome is counted here but never refused, so a result is never lost.

Expiry

A caller ask carries a deadline like any async park, and its on_expiry decides what a lapsed deadline does: kill (the default) tears the whole run chain down, while resume resumes the parked run with the reserved expiry marker so it takes its expiry branch. See async parks.

See also