Durable tools and developer-hosted results
Durable tools and developer-hosted results for Q integrations.
Managed (durable_temporal: true) agent runs journal tool intent before execution and commit results before returning them to the model. Each invocation has a stable ID, immutable input digest, read/write/execute effect and local/external execution location. This contract does not promise exactly-once effects in external systems.
Register a developer-hosted tool
Add up to 16 definitions to the existing run-creation body. Definitions belong to that run and cannot replace built-in tools. Normal run authorisation, scopes, mode restrictions and mandatory tool approvals still apply.
{
"prompt": "Record the synthetic delivery and report its receipt.",
"mode": "agent",
"durable_temporal": true,
"response_mode": "receipt",
"external_tools": [{
"name": "external_delivery",
"description": "Record a delivery in the caller's system.",
"effect": "write",
"input_schema": {
"type": "object",
"properties": {"item": {"type": "string"}},
"required": ["item"],
"additionalProperties": false
},
"output_schema": {
"type": "object",
"properties": {"receipt": {"type": "string"}},
"required": ["receipt"],
"additionalProperties": false
}
}]
}Use an idempotency key for run creation. Q advertises the tool to the planner but never calls your infrastructure itself. When approved dispatch reaches it, Q records awaiting_result; the run becomes awaiting_input with error_message: tool_result_required.
Poll, execute, submit, resume
Use the same tenant credential and original principal, with ai:answer, for all operations. Workspace, delegation and known source permissions are rechecked. Invocation IDs and stream keys are not authorisation tokens.
-
GET /v1/agents/{agent_id}/runs/{run_id}/tool-invocationsreturns up to 16 receipts. Pending external entries additionally containargumentsandoutput_schema. Follownext_afterwith?after=...to finish that scan; restart at the first page on the next poll. Completed/local receipts expose no stored result body. -
Persist your ownership of the invocation before performing the external action. Use
invocation_idas the downstream idempotency key when the destination supports it. Multiple polling workers need their own durable claim/lease; polling does not claim an invocation. After an uncertain external response, reconcile with the destination before doing any further write. -
POST /v1/agents/{agent_id}/runs/{run_id}/tool-invocations/{invocation_id}/resultwith the exact digest and output:{"input_digest": "<digest from the pending entry>", "output": {"receipt": "synthetic-receipt"}}Output must match the saved schema. Identical submissions return the same completed receipt while the original execution generation is active; changed output conflicts. The body cannot override tenant, principal, schema, destination or run state. Submission does not dispatch or resume the run.
-
Once all results have been accepted and the run is waiting,
POST /v1/agents/{agent_id}/runs/{run_id}/resumewith{}and an idempotency key. Q restores the observations and continues the original run with its original deadline. Resume can return a saved receipt with uncertain Temporal dispatch; use run reconciliation.
Prepared Python SDK 1.1.0 helpers: q.agents.runs.tool_invocations(...), submit_tool_result(..., input_digest=..., output=...), resume(...). TypeScript: q.agents.runs.toolInvocations(...), submitToolResult(..., {input_digest, output}), resume(...). These archives remain unpublished; raw HTTP is the version-independent path.
Validation, errors and recovery
Public schemas support type, properties, required, additionalProperties, items, enum, numeric minimum/maximum, string/array length bounds and description/title/default. Maximum schema size is 16 KiB, depth 8 and 256 nodes. References, regular expressions, composition and executable extensions are rejected. Inputs must be objects. A submitted external output is at most 64 KiB of JSON; use your own appropriately scoped artifact reference for larger data.
Errors use Q's standard error contract. Invalid schemas/outputs return 422; foreign or unauthorised runs/invocations return 404; conflicting digests/results, expired/terminal runs and superseded workflow generations return 409. Revoked sources return 403; changed or unverifiable evidence returns 409. Retrying rejected output never executes a tool. A lost submission response can be retried with the identical body, or reconciled by listing invocation receipts.
A local invocation with started and no committed result has an unknown outcome. Worker recovery stops with side_effect_outcome_unknown; normal resume returns run_reconciliation_required. Inspect the external system. If compensation is appropriate, perform and verify it there, then cancel the blocked Q run before creating new work. Q provides no generic compensation executor. A local write/execute tool returning an error is also treated conservatively because an error can follow a partial effect.
Completed local invocations reuse their saved result for the same identity, subject to evidence access checks. Intent IDs distinguish separate intentional operations. Run journals retain at most 256 invocations and 8 MiB; requests/results are bounded to 128/256 KiB. They are deleted with the owning run, never by stream expiry. Operators must include these tables in tenant deletion/retention policy and preserve fences while a run can execute.
Event replay retains its evidence restrictions. External/provider derivatives without a revocation contract may require refetch instead of replay. Legacy inline runs, uninstrumented delegation/recovery actions and automatic cloud PR finalisation do not gain a new durability promise here. See ADR 0017 for the execution boundary.