| Internet-Draft | Tool-Dispatch Finality | August 2026 |
| Das | Expires 28 February 2027 | [Page] |
An agentic model can emit a tool call that today's runtimes treat as something to execute. Allowlists, OAuth tokens, MCP server auth, sandboxes, output filters, and human approval decide whether an agent may reach a tool. They do not decide whether this generated call, with this argument digest, from this instruction chain, at this delegation depth, may take effect now.¶
That gap is the incident surface. Prompt-injected content, poisoned retrieval, a malicious tool response, or a delegated sub-agent can produce a call that looks like ordinary tool use. If the dispatcher executes whatever the model selected, policy that lived upstream becomes advisory.¶
This document specifies a dispatch-time gate. The model may compute a call. The call remains a Candidate Act. A Protected Enforcement Domain binds agent, tool, arguments, purpose, destination, provenance, and policy epochs, then issues scoped non-bearer authority. A Tool-Dispatch Finality Sink verifies that authority against the actual invocation immediately before the tool runs, then consumes it. The same gate applies to support, coding, payments, clinical, SOC, browser-use, and multi-agent MCP deployments. Tool selection is not execution authority.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 28 February 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Agent runtimes now sit on a short path:¶
user or retrieved text
-> model reasoning
-> tool_call{name, arguments}
-> runtime.dispatch()
-> external effect
¶
The third arrow is the one this document controls. The model is allowed to propose. The runtime is not allowed to treat the proposal as a capability. A proposed tool call, API request, browser action, shell command, message, file write, memory write, payment request, or agent delegation remains non-effective until act-specific authority is verified at the dispatch boundary.¶
This profile uses the two-boundary execution-finality chain also applied to AI-native networks [I-D.das-6g-finality] and precision-bounded egress [I-D.das-egress]. It specifies only the agent-dispatch predicates: tool and function identity, argument digest, instruction provenance, delegation depth, consequence class, and alternate-path closure across MCP, HTTP, browser, and shell.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Failure to establish current dispatch authority MUST NOT be converted into permission to invoke the tool.¶
Tool-using agents fail in a different place than chat models. A chat model that is tricked produces text. An agent that is tricked produces an invocation. The invocation may send mail, write memory, move money, open a browser, run a shell, or hand work to another agent. The model's next token is then an action.¶
The practical failures look like this:¶
In each case some upstream control was satisfied: the agent was authenticated, the tool was in the list, a token existed, or a human had approved "use tools" for the session. The missing question is the one asked at the sink: may this exact act become effective now.¶
Current stacks already contain important controls. This profile is designed to consume them, not to replace them.¶
Hosted tool-use interfaces expose a list of functions and JSON schemas to the model. That answers "which tools exist for this session." It does not bind a particular argument vector, destination, or instruction provenance. A model that is allowed bash and http.post can still emit the wrong one.¶
Tokens establish that the runtime or user may call a server. MCP server authentication establishes that the dispatcher is talking to a named server. Possession of a token is bearer-like with respect to every call the token can make. It is not act-specific, single-use, or argument-bound unless the application adds that layer.¶
OS sandboxes, containers, and browser isolation reduce blast radius. They do not decide whether a permitted tool inside the sandbox should fire. A sandboxed agent that can send email can still send the wrong email.¶
Constitutional or policy-trained refusal, classifiers, and tool-argument filters try to stop bad calls before dispatch. They are probabilistic. An attacker who wins the prompt still needs a second gate that does not trust the model. A filter that is uncertain MUST NOT be treated as ALLOW at the sink.¶
"Allow this agent to use tools" or a one-time confirm for a tool class is a session decision. It goes stale when parental policy, enterprise policy, destination, or delegation depth changes. It also does not hash arguments, so approval of a search is reused as approval of a transfer.¶
OPA-style engines, graph orchestrators, and guardrail middleware can deny a call. If the same effect can be reached by a second path — raw HTTP, a plugin, a shell pipe, another agent — a policy decision on the first path is not finality. A component is a Finality Sink only if bypassing it makes the effect technically non-completable.¶
The profile is meant to sit in a production dispatcher, not only in an architecture review. The cases below are the deployments where a model already has tools and where an allowlist is no longer the last control.¶
A support agent is allowed to read tickets, search a knowledge base, and draft replies. The same runtime often also has refund, credit, account-close, and data-export tools. Session consent says "this bot may use CRM tools." Prompt-injected ticket text or a retrieved policy PDF can cause the model to select refund.create or customer.export_pii.¶
The sink binds function_id, arguments_digest (amount, beneficiary, ticket id), consequence_class FINANCIAL or DATA_DISCLOSURE, and instruction provenance. A digest issued for refund.create at 12.00 MUST NOT authorize 1,200.00. Export of a whole address book remains non-effective when provenance is retrieval rather than an authenticated supervisor instruction.¶
Coding agents already call repo.read, test.run, pull_request.open, and, in some fleets, deploy or cloud.cli. Sandboxing the workspace does not distinguish "open a PR on a feature branch" from "apply this Terraform to prod." A poisoned README or CI log can instruct the agent to raise IAM privileges or disable an alert.¶
Consequence class PERSISTENT_STATE_CHANGE or NETWORK_CONTROL, destination (org/repo/account), and delegation_depth are the load-bearing fields. Authority for git.commit on branch feature/x MUST NOT authorize kubectl apply in another cluster. Computer-use agents that drive an IDE or a cloud console are the same profile with sink_type BROWSER_CONTROLLER or SHELL_GATEWAY.¶
Checkout, payout, and bookkeeping agents hold tools that move value. PCI and bank APIs already authenticate the application. They do not hash the model's chosen recipient, amount, and memo. Indirect injection in an invoice PDF or a supplier email is enough to retarget a payment tool the agent was allowed to use.¶
The Candidate Act MUST carry consequence_class FINANCIAL, a destination recipient_id, and a single-use authority bound to the arguments digest. Replay of yesterday's payout authority, or reuse at a second payment sink, MUST fail. Human approval, where required, is an input to the PED, not a substitute for sink verification.¶
Scheduling and chart-summary agents need lookup tools. The same session must not silently acquire record.disclose, prescription.send, or research.export_cohort. Existing EHR OAuth scopes are session-wide. A retrieved clinical note that contains "forward this chart to the address below" is an instruction provenance problem, not an authentication problem.¶
DATA_DISCLOSURE acts SHOULD escalate when provenance is not user or clinician. Destination jurisdiction and recipient_id bind cross-border or consumer-app egress. This document does not define medical policy. It keeps unauthorized disclosure non-completable when that policy says no.¶
SOC and IT agents query SIEM, open tickets, isolate hosts, rotate credentials, and in some designs push firewall or identity changes. Speed is the reason they exist. The failure mode is an attacker-authored alert or ticket that the model treats as an instruction to disable_monitoring or add_allowlist_entry.¶
NETWORK_CONTROL and PERSISTENT_STATE_CHANGE calls SHOULD leave the hot path. The sink binds tool, destination system, and arguments digest so that isolate(host=A) cannot be substituted with isolate(host=controller). Delegation from a triage agent to an enrichment agent MUST NOT inherit containment-tool authority.¶
Mail-send, calendar-accept, drive-share, and memory-write tools are already shipped in consumer and enterprise assistants. "You may send email on my behalf" is a session grant. Injected calendar notes and retrieved threads are the classic indirect-prompt path to message.send or file.share with a new recipient.¶
COMMUNICATION and DATA_DISCLOSURE classes bind recipient_id and arguments digest. A draft authorized for to=alice@example.com MUST fail if the live call is to=attacker@example.net. Memory writes that persist injected instructions are themselves Candidate Acts of type MEMORY_WRITE; leaving them unconstrained trains the next turn's provenance failure.¶
Agents that click, type, and submit forms cross the same boundary as an API tool, except the effect is whatever the current page will accept. Site isolation and a visible browser do not bind "pay this merchant 40" versus "pay this merchant 4000," or prevent a page script from becoming an instruction.¶
The sink is BROWSER_CONTROLLER. Each consequential submission is a Candidate Act. Navigation to a new origin is a destination change and MUST invalidate authority issued for the previous origin. Accessibility and screenshot tools that can reconstruct secrets are DATA_DISCLOSURE acts, not INFORMATIONAL ones.¶
Voice agents invoke the same CRM and payment tools under a latency budget. The hot path in Section 13 exists for that reason. Cached envelopes MAY allow account.lookup and script.play. account.charge, number.port, and recording.export remain cold-path or human-gated classes. Timeout MUST NOT place the charge.¶
Enterprises are connecting many MCP servers and specialist agents. Discovery of a server, a tool name, or a marketplace card is not authority. A planner agent that may call search MUST NOT, by delegation, confer shell or payout tools on a worker. AGENT_DELEGATION is a Candidate Act. Depth, parent_agent_id, and permitted consequence class travel with the worker. MCP/server substitution (T7) is a sink-binding and endpoint-binding check, not a model check.¶
A deployment can treat the profile as live when these are true in production logs: (1) every high-risk tool invoke has a consumed authority_id; (2) argument-digest mismatches are denials, not retries that skip the sink; (3) delegation beyond policy.max_depth never reaches invoke(); (4) a second path (raw HTTP, shell, plugin) cannot complete the same effect without the same bindings. Those four tests are more useful than a slide that says the agent is "policy aligned."¶
Candidate Act, Non-Effective State, Protected Enforcement Domain (PED), Protected Validation Evidence, scoped non-bearer finality authority, and Finality Sink are used as in [I-D.das-6g-finality].¶
A Candidate Act MUST NOT become effective merely because the model selected a tool, the tool is in the allowlist, a token exists, or an upstream safety model returned ALLOW.¶
model emits tool_call(name, args)
|
v
AGENT CANDIDATE ACT
|
v
Non-Effective State
|
v
Protected Enforcement Domain
principal, agent, model
tool, function, args digest
purpose, destination
instruction provenance
delegation depth
policy / revocation epochs
intended dispatch sink
|
v
evidence + scoped authority
|
v
TOOL-DISPATCH FINALITY SINK
|
+-- PASS -> invoke once, consume authority
|
`-- FAIL -> tool is not invoked
The following procedures are normative in behavior, not in programming language. An implementation MAY collapse PED and sink into one protected service if both roles remain distinguishable: evidence is committed before authority is usable, and the invocation is checked immediately before effect.¶
function ON_MODEL_TOOL_CALL(model_out, ctx):
act = AgentCandidateAct{
candidate_act_id: fresh_id(),
act_type: map_act_type(model_out),
agent: ctx.agent,
tool: {id: model_out.name,
function_id: model_out.name,
endpoint: resolve(model_out.name),
protocol: ctx.protocol},
arguments_digest: HASH(canonicalize(model_out.arguments)),
purpose: ctx.declared_purpose,
destination: resolved_destination(model_out),
consequence_class: classify(model_out.name),
instruction_provenance: ctx.provenance,
delegation_depth: ctx.depth,
policy_state: current_epochs(),
freshness: {nonce: fresh_nonce()},
finality_sink: ctx.dispatch_sink,
expires_at: now() + short_ttl
}
HOLD_NON_EFFECTIVE(act)
return PED_VALIDATE(act, model_out.arguments)
¶
function PED_VALIDATE(act, raw_args):
if malformed(act):
return DENY(MALFORMED_ACT)
if not fresh(act.freshness.nonce):
return DENY(REPLAY_OR_STALE)
if act.policy_state != current_epochs():
return DENY(EPOCH_MISMATCH)
if not allowlisted(act.agent, act.tool):
return DENY(TOOL_NOT_AUTHORIZED)
if act.destination unknown or jurisdiction unresolved:
return ESCALATE_OR_DENY(DESTINATION_UNRESOLVED)
if act.delegation_depth > policy.max_depth:
return DENY(DELEGATION_EXCEEDED)
if provenance_untrusted(act) and
act.consequence_class in HIGH_RISK:
return ESCALATE_OR_DENY(INSTRUCTION_PROVENANCE_FAILURE)
if args_exceed_schema(act.tool, raw_args):
return DENY(SCOPE_MISMATCH)
evidence = COMMIT_PROTECTED_EVIDENCE(act)
authority = ISSUE_SCOPED_AUTHORITY(act, evidence)
# act remains non-effective
return ALLOW(authority)
¶
function DISPATCH_SINK_INVOKE(act, authority, raw_args):
live_digest = HASH(canonicalize(raw_args))
if authority missing or integrity_fail(authority):
return DENY(NO_OR_INVALID_AUTHORITY)
if HASH(act) != authority.candidate_act_digest:
return DENY(ACT_MISMATCH)
if live_digest != act.arguments_digest:
return DENY(ARGS_SUBSTITUTION)
if authority.sink_id != THIS_SINK:
return DENY(SINK_MISMATCH)
if authority.tool_id != act.tool.id or
authority.function_id != act.tool.function_id:
return DENY(TOOL_SUBSTITUTION)
if expired(authority) or consumed(authority):
return DENY(STALE_OR_USED)
if authority.epochs != current_epochs():
return DENY(EPOCH_MISMATCH)
if act.destination != authority.permitted_destination:
return DENY(DESTINATION_MISMATCH)
ATOMICALLY:
consume(authority)
advance_replay_state(act.freshness.nonce)
result = invoke(act.tool, raw_args)
record_sink_evidence(act, authority, result)
return result
¶
The required property is that invoke() is unreachable when any check fails. Logging a denial and then calling the tool is non-conforming.¶
Authority issued for maps.search / nearby / digest D1 MUST NOT authorize maps.search / nearby / digest D2, a different function on the same server, a shell action, or the same call at another sink. DISPLAY-equivalent mistakes in this profile are tool substitution and argument substitution.¶
An authority for TOOL_DISPATCH MUST NOT automatically authorize SHELL_GATEWAY or BROWSER_CONTROLLER. An authority for delegation_depth 0 MUST NOT automatically authorize depth 2. An authority created under revocation epoch N MUST fail when the protected state is at epoch N+1.¶
The protocol does not require the model to detect injection. It requires that a consequential call influenced by retrieved text, tool output, memory, or another agent still cannot execute unless provenance and consequence-class predicates pass. Where provenance is UNKNOWN and the class is FINANCIAL, PERSISTENT_STATE_CHANGE, PHYSICAL, or NETWORK_CONTROL, the PED SHOULD escalate or deny rather than allow on the hot path.¶
Discovery of an MCP server, marketplace entry, or advertised tool MUST NOT by itself create dispatch authority. Delegation to a sub-agent is itself a Candidate Act of type AGENT_DELEGATION. Implementations SHOULD track cumulative depth, tool-call velocity, and consequence class across the chain and withhold further authority when the envelope is exceeded.¶
If the same protected effect can be produced by MCP, a raw HTTP client, a browser controller, a shell, an IPC helper, or a memory/file write, each path MUST either enforce this profile or be unable to produce the effect. Moving the call from the tool dispatcher to curl MUST NOT remove the requirement.¶
Objects are UTF-8 JSON. A transport MAY be local IPC, an OS broker, HTTPS, or an MCP middleware hook. Transport MUST preserve integrity, sink identity, freshness, and non-bearer semantics. Implementations MUST reject unknown load-bearing fields when additionalProperties is false.¶
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:ietf:params:json-schema:agent-finality:candidate-act:1",
"title": "AgentCandidateAct",
"type": "object",
"additionalProperties": false,
"required": [
"version", "object_type", "candidate_act_id", "act_type",
"created_at", "expires_at", "initiating_principal", "agent",
"tool", "purpose", "arguments_digest", "consequence_class",
"policy_state", "freshness", "finality_sink"
],
"properties": {
"version": { "type": "string", "const": "1.0" },
"object_type": {
"type": "string",
"const": "agent_candidate_act"
},
"candidate_act_id": {
"type": "string",
"minLength": 16,
"maxLength": 128
},
"act_type": {
"type": "string",
"enum": [
"TOOL_CALL", "FUNCTION_CALL", "API_REQUEST",
"BROWSER_ACTION", "SHELL_ACTION", "MESSAGE_SEND",
"FILE_WRITE", "MEMORY_WRITE", "AGENT_DELEGATION",
"PAYMENT_REQUEST", "OTHER"
]
},
"created_at": { "type": "string", "format": "date-time" },
"expires_at": { "type": "string", "format": "date-time" },
"initiating_principal": {
"type": "object",
"required": ["principal_id", "principal_type"],
"properties": {
"principal_id": { "type": "string" },
"principal_type": {
"type": "string",
"enum": ["user", "service", "enterprise", "device", "agent"]
},
"authorization_context_id": { "type": "string" }
}
},
"agent": {
"type": "object",
"required": ["agent_id", "runtime_id"],
"properties": {
"agent_id": { "type": "string" },
"runtime_id": { "type": "string" },
"model_id": { "type": "string" },
"model_version": { "type": "string" },
"alf_digest": { "type": "string" },
"delegation_depth": { "type": "integer", "minimum": 0 },
"parent_agent_id": { "type": "string" }
}
},
"tool": {
"type": "object",
"required": ["tool_id", "function_id"],
"properties": {
"tool_id": { "type": "string" },
"function_id": { "type": "string" },
"tool_endpoint": { "type": "string" },
"tool_protocol": {
"type": "string",
"enum": [
"MCP", "HTTP_API", "LOCAL_FUNCTION",
"BROWSER", "SHELL", "A2A", "OTHER"
]
}
}
},
"purpose": {
"type": "object",
"required": ["purpose_id", "declared_purpose"],
"properties": {
"purpose_id": { "type": "string" },
"declared_purpose": { "type": "string" },
"user_intent_reference": { "type": "string" },
"purpose_epoch": { "type": "integer", "minimum": 0 }
}
},
"arguments_digest": {
"type": "object",
"required": ["algorithm", "value", "canonicalization"],
"properties": {
"algorithm": {
"type": "string",
"enum": ["SHA-256", "SHA-384", "SHA-512"]
},
"value": { "type": "string" },
"canonicalization": {
"type": "string",
"enum": ["JCS", "implementation-defined"]
}
}
},
"destination": {
"type": "object",
"properties": {
"destination_id": { "type": "string" },
"endpoint": { "type": "string" },
"jurisdiction": { "type": "string" },
"recipient_id": { "type": "string" }
}
},
"consequence_class": {
"type": "string",
"enum": [
"INFORMATIONAL", "DATA_DISCLOSURE",
"PERSISTENT_STATE_CHANGE", "FINANCIAL",
"NETWORK_CONTROL", "PHYSICAL", "COMMUNICATION", "OTHER"
]
},
"policy_state": {
"type": "object",
"required": ["policy_epoch", "authority_epoch", "revocation_epoch"],
"properties": {
"policy_epoch": { "type": "integer", "minimum": 0 },
"authority_epoch": { "type": "integer", "minimum": 0 },
"revocation_epoch": { "type": "integer", "minimum": 0 },
"policy_profile_id": { "type": "string" }
}
},
"freshness": {
"type": "object",
"required": ["nonce"],
"properties": {
"nonce": { "type": "string", "minLength": 16 },
"sequence": { "type": "integer", "minimum": 0 },
"session_id": { "type": "string" }
}
},
"instruction_provenance": {
"type": "object",
"properties": {
"instruction_digest": { "type": "string" },
"source_type": {
"type": "string",
"enum": [
"user", "system", "developer", "tool",
"retrieval", "memory", "agent"
]
},
"trusted_chain_digest": { "type": "string" }
}
},
"finality_sink": {
"type": "object",
"required": ["sink_id", "sink_type"],
"properties": {
"sink_id": { "type": "string" },
"sink_type": {
"type": "string",
"enum": [
"TOOL_DISPATCH", "API_GATEWAY", "BROWSER_CONTROLLER",
"SHELL_GATEWAY", "MESSAGE_GATEWAY", "FILE_COMMIT",
"MEMORY_COMMIT", "OTHER"
]
}
}
}
}
}
¶
{
"version": "1.0",
"object_type": "protected_validation_evidence",
"evidence_id": "pve-6d6ddc91",
"candidate_act_id": "act-7f39b8b8",
"decision": "ALLOW",
"validated_predicates": {
"principal_authorized": true,
"agent_identity_valid": true,
"tool_scope_valid": true,
"purpose_valid": true,
"instruction_provenance_valid": true,
"destination_valid": true,
"policy_epoch_valid": true,
"revocation_state_valid": true,
"freshness_valid": true,
"sink_binding_valid": true
},
"protected_state": {
"state_reference": "ped-state-901",
"monotonic_counter": 8481
},
"issued_at": "2026-08-26T17:40:01Z",
"protector": {
"type": "TEE_SIGNATURE",
"key_id": "ped-key-12",
"signature": "base64url-signature"
}
}
¶
{
"version": "1.0",
"object_type": "agent_finality_authority",
"authority_id": "afa-c7d32d54",
"candidate_act_id": "act-7f39b8b8",
"evidence_id": "pve-6d6ddc91",
"scope": {
"act_type": "TOOL_CALL",
"tool_id": "maps.search",
"function_id": "nearby",
"permitted_destination": "tool.example",
"permitted_consequence_class": "DATA_DISCLOSURE"
},
"binding": {
"candidate_act_digest": {
"algorithm": "SHA-256",
"value": "base64url-act-digest"
},
"nonce": "A81D4F42B779CC21",
"policy_epoch": 42,
"revocation_epoch": 7,
"finality_sink_id": "dispatch-sink-01"
},
"lifetime": {
"issued_at": "2026-08-26T17:40:01Z",
"expires_at": "2026-08-26T17:40:10Z",
"single_use": true
},
"issuer": {
"ped_id": "ped-runtime-01",
"key_id": "ped-key-12",
"signature": "base64url-signature"
}
}
¶
{
"operation": "DispatchSinkVerify",
"request_id": "req-10aa19",
"decision": "ALLOW",
"verification": {
"authority_signature": "VALID",
"candidate_act_binding": "MATCH",
"tool_scope": "MATCH",
"args_digest": "MATCH",
"destination": "MATCH",
"nonce": "FRESH",
"policy_epoch": "CURRENT",
"revocation_epoch": "CURRENT",
"consumption_state": "UNUSED",
"sink_binding": "MATCH"
},
"consumption": {
"authority_id": "afa-c7d32d54",
"status": "CONSUMED"
},
"effectuation": { "permitted": true, "effect_id": "effect-bf3b5a" }
}
¶
{
"operation": "DispatchSinkVerify",
"request_id": "req-10ab00",
"decision": "DENY",
"error": {
"code": "EF_SCOPE_MISMATCH",
"message": "Requested function is outside authorized tool scope.",
"retryable": false
},
"effectuation": { "permitted": false }
}
¶
{
"step_1_model": {
"emitted": "maps.search.nearby",
"status": "NON_EFFECTIVE"
},
"step_2_candidate_act": {
"candidate_act_id": "act-7f39b8b8",
"act_type": "TOOL_CALL",
"agent": {
"agent_id": "assistant-23",
"model_id": "model-X",
"delegation_depth": 0
},
"tool": {
"tool_id": "maps.search",
"function_id": "nearby",
"tool_protocol": "MCP"
},
"purpose": {
"purpose_id": "nearby-service-discovery",
"declared_purpose": "Find nearby pharmacies"
},
"arguments_digest": {
"algorithm": "SHA-256",
"value": "base64url-args-digest",
"canonicalization": "JCS"
},
"destination": { "destination_id": "tool.example", "jurisdiction": "IN" },
"consequence_class": "DATA_DISCLOSURE",
"instruction_provenance": { "source_type": "user" },
"finality_sink": {
"sink_id": "dispatch-sink-01",
"sink_type": "TOOL_DISPATCH"
}
},
"step_3_authority": { "authority_id": "afa-c7d32d54", "single_use": true },
"step_4_sink": { "decision": "ALLOW", "authority_consumed": true },
"step_5_effect": "maps.search.nearby invoked once with digest-bound args"
}
¶
Repeated calls inside a fixed envelope — same agent, tool, function, destination, consequence class, and provenance class — MAY use cached policy and short-lived authority. The sink check is still mandatory. New tools, unknown destinations, depth increases, FINANCIAL or PHYSICAL class, provenance UNKNOWN, or runtime anomaly SHOULD escalate. Timeout is not ALLOW.¶
Illustrative codes, not IANA assignments: EF-002 NO_FINALITY_AUTHORITY, EF-005 AUTHORITY_ALREADY_USED, EF-006 REPLAY_DETECTED, EF-012 SCOPE_MISMATCH, EF-013 PURPOSE_MISMATCH, EF-020 DESTINATION_MISMATCH, EF-040 SINK_MISMATCH, EF-053 INSTRUCTION_PROVENANCE_FAILURE, EF-070 ESCALATION_REQUIRED, EF-080 FAIL_CLOSED.¶
Threats the sink is intended to make non-completable without current authority include: T1 direct prompt injection, T2 indirect prompt injection, T3 poisoned retrieval, T4 poisoned memory, T5 malicious tool response, T6 tool substitution, T7 MCP/server substitution, T8 unauthorized delegation, T9 recursive escalation, T10 stale user authority, T11 destination substitution, T14 authority replay, T15 cross-sink reuse, and T17 alternate-path effectuation.¶
The model is not trusted to police these. The property is mechanical: invoke() does not run. Residual risk remains if an unclassified generated argument string is itself a program the tool will interpret, or if a path around the sink exists. Implementations MUST treat those as profile failures, not as acceptable best-effort modes.¶
Candidate Act descriptors can reveal user intent, tool choice, and destination. Implementations SHOULD hash or seal raw arguments and prompts on untrusted paths and SHOULD NOT log full tool arguments when the digest is sufficient for verification.¶
This document requests no IANA actions.¶
Certain concepts are associated with pending applications in the DAS Protocols family, including PCT/IB2026/054453, PCT/IB2026/055615, PCT/IB2026/055760, PCT/IB2026/055870, PCT/IB2026/056058, and PCT/IB2026/053385. IETF disclosure should follow BCP 79 [RFC8179].¶
The model may select a tool. That selection is computation. External execution starts only after the Candidate Act is validated, evidence is committed, scoped authority is issued, and the dispatch sink verifies the live invocation. Tool selection is not execution authority.¶