docs: Document Aura HITL webhook header forwarding and tool_call_intent - #90
docs: Document Aura HITL webhook header forwarding and tool_call_intent#90promptless[bot] wants to merge 2 commits into
Conversation
Document two merged Aura HITL enhancements on the approval webhook: - headers / headers_from_request on [hitl.route] (webhook mode) for attaching static and request-forwarded headers to the approval POST - the optional tool_call_intent field in the webhook items[] payload Adds a security warning about forwarding sensitive headers, a config reference entry, and 'untrusted' to the Vale accept vocabulary. Relates to: mezmo/aura#490, mezmo/aura#489
|
|
||
| - `tool_name` — the gated MCP tool being called. | ||
| - `arguments` — the tool call arguments. | ||
| - `tool_call_intent` — **Optional.** A short natural-language rationale the agent |
There was a problem hiding this comment.
ApprovalItem.tool_call_intent is Option<String> with #[serde(default, skip_serializing_if = "Option::is_none")] — confirms it is optional and omitted (not null/empty) when absent.
| untrusted and escape or sanitize it like any other model output. | ||
| - Omitted entirely from the payload (not `null`, not an empty string) when the | ||
| agent supplies no rationale, so its absence does not change the protocol | ||
| `version`. |
There was a problem hiding this comment.
PROTOCOL_VERSION const = 1, unchanged by the new optional field — confirms "does not change the protocol version" claim.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
| Aura does not act on it automatically. Because it is agent-generated free text, | ||
| a webhook receiver that displays it to a human approver should treat it as | ||
| untrusted and escape or sanitize it like any other model output. | ||
| - Omitted entirely from the payload (not `null`, not an empty string) when the |
There was a problem hiding this comment.
non_blank() collapses empty/whitespace-only agent reasoning to None, so blank rationale never appears on the wire as an empty string.
| agent supplies no rationale, so its absence does not change the protocol | ||
| `version`. | ||
|
|
||
| `tool_call_intent` appears only in the webhook payload's `items[]`. It is not |
There was a problem hiding this comment.
ApprovalRequested, ApprovalPending, and ApprovalCompleted SSE DTOs have no tool_call_intent field — confirms it appears only in the webhook items[] payload, not the SSE events or conversational pending prompt.
| @@ -196,6 +259,9 @@ posting a decision. `aura.approval_completed` includes `decision_id`, terminal | |||
| `timed_out`, `cancelled`, and `errored`; `errored` means the approval channel | |||
| failed before a human decision was obtained. | |||
|
|
|||
There was a problem hiding this comment.
Same SSE DTOs (ApprovalRequested/Pending/Completed) confirm the lifecycle events do not carry tool_call_intent.
|
|
||
| ## Forward headers to the webhook | ||
|
|
||
| The webhook route accepts two optional `[hitl.route]` keys (valid only when |
There was a problem hiding this comment.
DecisionRouteConfig::Webhook variant adds optional headers and headers_from_request fields (#[serde(default)]), valid only for mode = "webhook".
|
|
||
| - `headers` — a static map; values are sent verbatim on every approval POST. | ||
| - `headers_from_request` — maps **outgoing header name → incoming request header | ||
| name**; the incoming lookup is case-insensitive. |
There was a problem hiding this comment.
apply_request_header_mappings does a case-insensitive (.to_lowercase()) lookup of the mapped incoming request header name.
| - `headers` — a static map; values are sent verbatim on every approval POST. | ||
| - `headers_from_request` — maps **outgoing header name → incoming request header | ||
| name**; the incoming lookup is case-insensitive. | ||
| - Precedence: when the mapped incoming request header is present, the forwarded |
There was a problem hiding this comment.
resolve_webhook_headers overlays apply_request_header_mappings results onto the lowercased static headers map, so a present mapped request header overrides the matching static value; absent request header leaves the static value as fallback.
| value overrides any static `headers` value for the same (case-normalized) name. | ||
| - When the incoming header is absent, the static `headers` value remains as a | ||
| fallback. | ||
| - Aura sends all resolved outgoing header names in lowercase. |
There was a problem hiding this comment.
Static header keys are lowercased and apply_request_header_mappings inserts forwarded values under a lowercased outbound key, so all resolved outgoing header names are lowercase.
| fallback. | ||
| - Aura sends all resolved outgoing header names in lowercase. | ||
| - Both keys are optional; when both are absent, the approval POST is unchanged. | ||
| - Invalid header names or values are skipped with a warning and never abort the |
There was a problem hiding this comment.
Invalid HeaderName/HeaderValue conversions are skipped with tracing::warn! and never abort the request; the invalid value itself is never logged (only the key name).
| mode = "webhook" | ||
| url = "https://approvals.example.com/aura" | ||
| timeout_secs = 300 | ||
| headers = { "Authorization" = "Bearer {{ env.APPROVAL_WEBHOOK_TOKEN }}" } |
There was a problem hiding this comment.
resolve_env_vars runs a whole-file regex substitution pass on the TOML text before parsing, so {{ env.VAR }} works in any string value, including [hitl.route].headers.
| url = "https://approvals.example.com/aura" | ||
| timeout_secs = 300 | ||
| headers = { "Authorization" = "Bearer {{ env.APPROVAL_WEBHOOK_TOKEN }}" } | ||
| headers_from_request = { "x-request-id" = "x-request-id" } |
There was a problem hiding this comment.
Parser test uses the identical inline-table TOML shape (headers = { "x-tenant" = "sre-prod" }) confirming the docs' example syntax parses correctly.
| | `mode` | string | — | **Required.** `"conversational"` (attended, over an open SSE stream) or `"webhook"` (unattended, posts to a URL). | | ||
| | `timeout_secs` | integer | `60` (conversational) / `300` (webhook) | Seconds to wait for a decision before failing closed. | | ||
| | `url` | string | — | **Required for `webhook` mode only.** Must start with `http://` or `https://`. | | ||
| | `headers` | table | `{}` | **Webhook mode only.** Static outgoing headers `{ "header-name" = "value" }` sent on every approval POST. | |
There was a problem hiding this comment.
headers/headers_from_request table fields on DecisionRouteConfig::Webhook, both #[serde(default)] (default {}), webhook-mode only.
…forwarding # Conflicts: # aura/hitl.mdx
Open in Promptless
Two merged Aura pull requests extended the human-in-the-loop (HITL) approval webhook, and the docs did not yet cover either change. This suggestion documents both on the Aura HITL page and configuration reference.
[hitl.route]keys available in webhook mode —headers(static outgoing headers, supporting{{ env.VAR }}interpolation) andheaders_from_request(forward a header from the incoming API request onto the approval POST). Covers precedence, case-insensitive lookup, lowercased outgoing names, the CLI-standalone (AURA_EXTRA_HEADERS) behavior, and a security warning about forwarding sensitive headers such asauthorization.tool_call_intent(mezmo/aura #489): documents the new optional field in the webhook payload'sitems[]— the agent's rationale for a gated tool call, shown to the human approver. Notes that it is agent-generated (treat as untrusted), omitted entirely when absent, does not change the protocolversion, and appears only in the webhook payload (not the SSE approval events or the conversational route).Files touched:
aura/hitl.mdx,aura/configuration-reference.mdx, andvale/styles/config/vocabularies/Mintlify/accept.txt(addsuntrusted).Trigger Events
Tip: Sort by Shortest Review in the Dashboard to find quick wins ⚡