| Internet-Draft | Tool-Use Binding | August 2026 |
| Das | Expires 1 March 2027 | [Page] |
Frontier runtimes already standardized the dangerous moment. Claude emits a tool_use block. ChatGPT emits tool_calls. MCP emits tools/call. The host then invokes whatever name and arguments the model printed. Alignment, allowlists, and OAuth sit around that moment. They do not sit on it. If the block is treated as a capability, prompt-injected mail, a poisoned retrieval, or a stolen enterprise seat becomes an external act with a 200 from the tool.¶
This document does not invent another assistant API. It binds the Agent Candidate Act profile [I-D.das-agentic] onto the three interfaces those labs and their customers already ship: Anthropic tool_use / computer_use, OpenAI function calling and Responses tools, and Model Context Protocol tools/call. The model may emit the block. The block remains non-effective. A local enforcer builds the act, binds the argument digest, and refuses invoke() until scoped authority is verified and consumed at the dispatch sink.¶
The implementation target is a middleware function that a host loop can call without changing the model vendor. tool_use is not invoke().¶
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 1 March 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.¶
Every hosted agent product converged on the same wire shape:¶
model output
Anthropic: content[type=tool_use] {id, name, input}
OpenAI: tool_calls[] {id, function.name, function.arguments}
MCP: tools/call {name, arguments}
|
v
host.invoke(name, arguments)
|
v
tool_result / function output / MCP result
¶
This document inserts a gate on the middle arrow without asking Anthropic or OpenAI to change model cards. The gate is the profile in [I-D.das-agentic]: wrap the block as an AgentCandidateAct, hold it non-effective, validate, commit evidence, issue scoped authority, verify at the dispatch sink, consume, then invoke.¶
Vendor names are deployment classes. Field names below follow public tool-use and MCP shapes as of this writing and are informative where those APIs evolve. The load-bearing contract is the act object, not a trademark.¶
The agentic profile is the architecture. This document is the thing a runtime engineer can implement on Monday. Labs lose enterprise deals on a specific sentence: "what happens when the model is injected and still emits a tool block." Answers that are only "we train refusal" or "we log the block" are weaker than "invoke() is unreachable without a consumed authority_id bound to this argument digest."¶
That sentence maps onto products customers already buy: Claude for Work with tools and computer use, ChatGPT Enterprise with actions and an Agents runtime, and MCP servers wired into both. The binding is how isolation of action [DAS-ISOLATION] lands in the loop those products run thousands of times per minute.¶
For an Anthropic reliability or safety engineer the claim is mechanical: a tool_use block can exist, be shown in the transcript, and still leave the world unchanged. Refusal training reduces how often the block appears. This binding reduces what the block can do when it appears anyway — including when the model is following a retrieved instruction it treated as a user turn.¶
For an OpenAI platform or Agents engineer the claim is the same on tool_calls[] and on Actions HTTP. Schema-valid arguments are not a capability. Parallel calls are not one capability. An Agents SDK that owns the loop MUST expose the hook or wrap the tool objects; otherwise the safest model still has an unguarded invoke.¶
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.¶
A host that executes a tool block without current dispatch authority is non-conforming with this binding even if the model vendor's SDK performed the HTTP call.¶
The host loop is short and trusted by default:¶
for block in model_output.tool_uses:
result = tools[block.name](**block.input)
append_tool_result(block.id, result)
¶
That loop is correct only if the block is a capability. It is not. Failures this binding treats as first-class:¶
tool_use binds a name and an input object to a content block id. Computer use binds screen actions. System prompts, tool schemas, and constitutional or policy trained refusal try to stop bad blocks before they appear. They do not consume single-use authority at invoke, and they do not hash arguments so an approved search cannot become a send.¶
tool_calls bind a function name and a JSON argument string. GPTs Actions add OpenAPI backends. The Agents runtime adds loops. Schema validation answers "is this JSON shaped." It does not answer "may this digest run now at this sink."¶
MCP authenticates a client to a server and names tools. tools/call is still bearer-like with respect to every call that server will accept under the session. Server discovery is not act authority [I-D.das-agentic].¶
A deterministic mapping from those three envelopes to AgentCandidateAct, a host-local enforce() that MUST wrap invoke, and an error mapping back into tool_result / function output / MCP error so the model sees a deny rather than a successful side effect.¶
Informative source shape:¶
{
"type": "tool_use",
"id": "toolu_01A",
"name": "email_send",
"input": {
"to": "alice@example.com",
"subject": "Invoice",
"body": "..."
}
}
¶
Mapping MUST be:¶
On deny, the host MUST append a tool_result for that id whose content is an error object, not a successful send. On allow, invoke then append the real result. The model is allowed to recover. The world is not allowed to change on deny.¶
Each consequential action (click, type-submit, file download) is its own Candidate Act. screenshot and cursor moves MAY be INFORMATIONAL if they cannot exfiltrate. A submit that posts a form MUST bind a digest over the live form values, not over the model's text description of the click. Origin change MUST invalidate prior authority.¶
Informative computer-use action names vary by preview API. Treat left_click, type, and key(Enter) on a focused form as potential COMMUNICATION or FINANCIAL if the focused origin is a mail, bank, or admin host. Treat screenshot as DATA_DISCLOSURE when the frame can contain secrets (password managers, MFA QR, customer PII). A single "computer use session allow" MUST NOT authorize every later action in the session.¶
The live digest SHOULD include origin, destination URL if known, and a stable serialization of the filled fields. If the controller cannot read those fields, it MUST escalate or deny rather than click on narration alone. This is the computer-use form of argument substitution.¶
Informative source shape:¶
{
"id": "call_8f3",
"type": "function",
"function": {
"name": "payout_create",
"arguments": "{\"amount\":\"150.00\",\"currency\":\"EUR\",\"beneficiary\":\"vendor-441\"}"
}
}
¶
Mapping MUST parse arguments as JSON, then canonicalize the object, then hash. Hashing the raw string is allowed only if the host guarantees one serialization. Two equivalent JSON strings MUST NOT produce two different authorities that both can run.¶
function.name maps to tool_id and function_id. Parallel tool_calls[] are parallel Candidate Acts. Each MUST be enforced separately. A single ALLOW for the message MUST NOT authorize every call in the array.¶
Responses API tool outputs and Assistants tool-output submits are the same sink moment: before the host runs the function map. GPT Actions that HTTP POST to a customer API are API_REQUEST acts; destination is the Action server URL.¶
Informative request:¶
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "repo_deploy",
"arguments": { "env": "prod", "ref": "main" }
}
}
¶
The conforming placement is the MCP *client* dispatcher, not each server. One enforcer sees every server the agent can reach. Server identity MUST enter destination or tool.tool_endpoint so a swapped server with the same tool name fails sink or destination match.¶
On deny the client MUST NOT send tools/call, or MUST send it only to a server that is itself a cooperating sink and will refuse. Returning an MCP error to the model is required so the loop does not treat silence as success.¶
tools/list remains discovery. list results MUST NOT issue AgentFinalityAuthority.¶
Both labs stream tokens. A partial tool_use or partial function.arguments string MUST remain non-effective. enforce() MUST run only on the finalized block. Invoking on a partial JSON object is non-conforming.¶
Parallel tool_calls and multiple tool_use blocks in one assistant message are independent acts. The host MAY validate them concurrently. It MUST NOT treat one ALLOW as covering siblings. If one call is FINANCIAL and one is INFORMATIONAL, only the FINANCIAL call escalates. A deny on one call MUST NOT be "fixed" by invoking the others first and hoping the model forgets.¶
Retries after transport failure MUST reuse the same candidate_act_id only when the consume bit is unread and the digest is unchanged. If consume already happened, retry is a new act or a fetch of the original tool_result, never a second invoke.¶
The normative schema lives in [I-D.das-agentic]. It is repeated here so this binding can be implemented from one document on a first reading.¶
{
"$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": {
"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", "COMPUTER_USE", "OTHER"
]
},
"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",
"ANTHROPIC_TOOL_USE", "OPENAI_TOOL_CALL", "OTHER"
]
}
}
},
"consequence_class": {
"type": "string",
"enum": [
"INFORMATIONAL", "DATA_DISCLOSURE",
"PERSISTENT_STATE_CHANGE", "FINANCIAL",
"NETWORK_CONTROL", "PHYSICAL", "COMMUNICATION", "OTHER"
]
}
}
}
¶
type=tool_use → act_type TOOL_CALL. id → incorporated into candidate_act_id. name → tool_id and function_id. input → canonicalized into arguments_digest. Host session id → freshness.session_id. Workspace org → initiating_principal. Model name from the request → agent.model_id. computer_2025xxxx tools → act_type COMPUTER_USE and sink BROWSER_CONTROLLER.¶
id call_* → incorporated into candidate_act_id. type=function → TOOL_CALL. function.name → tool_id. function.arguments parsed object → arguments_digest. Parallel index is not authority; each id is an act. Responses API function_call items use the same map. Custom GPT Action operationId MAY populate function_id when name is generic.¶
params.name → tool_id. params.arguments → digest. jsonrpc id is not candidate_act_id (it can collide across servers). Server URL or server name → tool_endpoint and destination. Protocol MCP. A notifications/message that asks the host to call a tool is the same map if the host would invoke.¶
{
"scenario": "INJECTED_EMAIL_SEND",
"vendor": "anthropic",
"block": { "name": "email_send", "input": { "to": "attacker@ex.com" } },
"reason": "provenance=retrieval and consequence=COMMUNICATION",
"enforce": { "allow": false, "code": "EF_INSTRUCTION_PROVENANCE_FAILURE" },
"invoked": false,
"tool_result": { "is_error": true }
}
¶
{
"scenario": "PARALLEL_PAY_AND_SEARCH",
"vendor": "openai",
"tool_calls": [
{ "name": "search", "result": "enforce_hot_allow" },
{ "name": "payout_create", "result": "enforce_deny_envelope" }
],
"rule": "search invoke does not authorize payout invoke"
}
¶
{
"scenario": "MCP_SERVER_SWAP",
"name": "repo_deploy",
"authorized_endpoint": "mcp://git.example/prod",
"live_endpoint": "mcp://git.attacker/prod",
"enforce": { "allow": false, "code": "EF_DESTINATION_MISMATCH" }
}
¶
{
"scenario": "COMPUTER_USE_ORIGIN_CHANGE",
"authorized_origin": "https://pay.example/checkout",
"live_origin": "https://pay.example-evil/checkout",
"enforce": { "allow": false, "code": "EF_DESTINATION_MISMATCH" }
}
¶
Anthropic Messages API: after the application assembles content blocks, before the developer function table runs. Computer-use beta: inside the controller that turns action items into OS events, before the event is sent.¶
OpenAI Chat Completions: after tool_calls is parsed, before the function map. Assistants API: before submitting tool outputs that the host computed by running code. Responses API: before executing function_call items. Agents SDK: a before_tool_call callback if present; otherwise wrap the tool implementation objects the SDK receives.¶
MCP TypeScript and Python reference clients: wrap Client.callTool. IDE agents that embed MCP (desktop hosts) wrap the same method so every server inherits the gate.¶
function HANDLE_ANTHROPIC_MESSAGE(msg, ctx):
for block in msg.content where block.type == "tool_use":
act = map_tool_use(block, ctx)
decision = enforce(act, block.input)
if decision.allow:
raw = invoke(block.name, block.input)
append_tool_result(block.id, raw)
else:
append_tool_result(block.id, {
"error": decision.code,
"message": decision.message
})
function HANDLE_OPENAI_MESSAGE(msg, ctx):
for call in msg.tool_calls:
args = parse_json(call.function.arguments)
act = map_tool_call(call, args, ctx)
decision = enforce(act, args)
if decision.allow:
raw = invoke(call.function.name, args)
append_tool_output(call.id, raw)
else:
append_tool_output(call.id, error_payload(decision))
function HANDLE_MCP_TOOLS_CALL(req, ctx):
act = map_mcp(req, ctx)
decision = enforce(act, req.params.arguments)
if not decision.allow:
return mcp_error(decision)
return transport_tools_call(req)
¶
A library advertised as implementing this binding MUST expose a function with this behavior, regardless of language:¶
enforce(act: AgentCandidateAct, live_args: object) ->
{ allow: bool, authority_id?: string, code?: string, message?: string }
# MUST:
# 1. hash live_args with the same canonicalization as act.arguments_digest
# 2. refuse if hashes differ
# 3. refuse if authority missing, expired, consumed, or sink-mismatched
# 4. consume single-use authority before returning allow=true
# 5. never return allow=true on timeout or uncertain epoch
¶
Returning allow=true and then failing to consume is non-conforming. Logging a deny and invoking anyway is non-conforming.¶
The Candidate Act schema is [I-D.das-agentic]. This binding adds only mapped examples and the host error object.¶
{
"version": "1.0",
"object_type": "agent_candidate_act",
"candidate_act_id": "act-toolu-01A-sess55",
"act_type": "TOOL_CALL",
"agent": {
"agent_id": "claude-work-seat-12",
"runtime_id": "anthropic-host-loop",
"model_id": "claude-family",
"delegation_depth": 0
},
"tool": {
"tool_id": "email_send",
"function_id": "email_send",
"tool_protocol": "LOCAL_FUNCTION"
},
"purpose": {
"purpose_id": "user-turn",
"declared_purpose": "send invoice email"
},
"arguments_digest": {
"algorithm": "SHA-256",
"value": "base64url-args",
"canonicalization": "JCS"
},
"destination": { "destination_id": "smtp-gw-1" },
"consequence_class": "COMMUNICATION",
"policy_state": {
"policy_epoch": 9,
"authority_epoch": 3,
"revocation_epoch": 1
},
"freshness": { "nonce": "C0FFEE11DEADBEEF" },
"instruction_provenance": { "source_type": "user" },
"finality_sink": {
"sink_id": "host-dispatch-1",
"sink_type": "TOOL_DISPATCH"
}
}
¶
{
"object_type": "agent_candidate_act",
"act_type": "TOOL_CALL",
"tool": {
"tool_id": "payout_create",
"function_id": "payout_create",
"tool_protocol": "HTTP_API"
},
"arguments_digest": {
"algorithm": "SHA-256",
"value": "base64url-args",
"canonicalization": "JCS"
},
"consequence_class": "FINANCIAL",
"destination": { "destination_id": "psp.example" },
"finality_sink": {
"sink_id": "host-dispatch-1",
"sink_type": "TOOL_DISPATCH"
}
}
¶
If the handler would move funds, a PaymentCandidateAct from [I-D.das-payment] MUST also be enforced before the PSP SDK.¶
{
"error": {
"code": "EF_SCOPE_MISMATCH",
"message": "Live arguments are not the authorized digest.",
"retryable": false,
"invoked": false
}
}
¶
{
"step_1_block": {
"type": "tool_use",
"name": "maps_search",
"status": "NON_EFFECTIVE"
},
"step_2_enforce": {
"allow": true,
"authority_id": "afa-c7d32d54",
"consumed": true
},
"step_3_invoke": { "tool": "maps_search", "once": true },
"step_4_tool_result": { "id": "toolu_01A", "ok": true }
}
¶
The binding is host-side. No tokenizer change, no tool-schema change, no fine-tune is required for v1. That is why a lab can trial this on one enterprise workspace without a model release.¶
Tool loops are already dominated by the model forward pass and the tool I/O. enforce() on the hot path is: canonicalize JSON, SHA-256, MAC or signature verify, compare-and-swap on a consume row. Representative added cost is sub-millisecond to a few milliseconds on the same host — negligible next to a 200-2000 ms model call. Cold path (new tool, unknown destination, FINANCIAL class, unknown provenance) MAY add a policy fetch. Timeout of that fetch MUST deny, not invoke.¶
Existing OpenAI and Anthropic examples are ten-line for-loops. The feasible change is wrapping invoke, not rewriting the product. A feature flag "enforce_tools=true" on a workspace is a conforming pilot. Workspaces left on false are known alternate paths and MUST be listed if they can reach the same side-effecting tools.¶
SDKs that invoke tools internally (some Agents runtimes) MUST expose a pre-invoke hook or MUST be wrapped at the HTTP client that talks to the tool backend. If neither hook exists, the deployment cannot claim this binding for those tools.¶
Servers can stay unmodified if the client enforcer is in-line. Cooperating servers MAY later verify the authority object themselves. That is an enhancement, not a v1 requirement. Flag-day replacement of every MCP server is not required and not recommended.¶
The sink is the existing controller process that issues clicks. Digest the live DOM or form state you already read to click. If you cannot digest the live values, those submits MUST be treated as cold path or denied. Do not invent a second browser.¶
It does not require a TEE on day one. It does not require vaults from [I-D.das-enterprise] on day one. It does not require Anthropic or OpenAI to accept a patch. It requires the host that already runs the loop to stop treating the block as a capability.¶
Enable enforce() on tools marked COMMUNICATION, FINANCIAL, PERSISTENT_STATE_CHANGE, or computer-use submit. Leave maps.search on a hot envelope. Measure denies that would have been sends. That metric is the procurement answer.¶
Same split for Actions and function tools. Memory writes go through enforce() as MEMORY_WRITE. Share-chat and file export are Output Release Boundaries under [I-D.das-enterprise] when the content is a reconstructed view; they are still tool-shaped if implemented as tools.¶
A bank or hospital that cannot wait for a vendor-native hook wraps the tool handlers they wrote. That is enough for tools they own. Tools the vendor invokes inside a black box remain an alternate path until the vendor exposes the hook.¶
Hosts SHOULD assign consequence_class before enforce(), not after the model names the tool. Informative defaults:¶
Misclassifying payout as INFORMATIONAL is a profile failure, not a model failure.¶
Claude projects and ChatGPT memory are tool- shaped even when they are not named tools. A host that writes extracted facts into a memory store MUST treat that write as MEMORY_WRITE. Otherwise an injected turn stores a join that later turns treat as user provenance. That is how present theft becomes future mapping without another tool_use [DAS-ISOLATION].¶
T-A: emit tool_use email_send to an address not in the user turn; expect invoke=false. T-B: two OpenAI tool_calls, search and payout; expect payout denied when above envelope. T-C: replay the same authority_id on a second invoke; expect deny. T-D: MCP call with swapped server URL; expect destination deny. T-E: computer-use submit after origin change; expect deny. T-F: measure enforce() p99 under 10 ms on hot search. T-G: kill the host after consume and before invoke; on restart do not invoke again.¶
A workspace that passes T-A through T-G can be shown to an enterprise security review without a new model checkpoint.¶
EF codes from [I-D.das-agentic] SHOULD be copied into the vendor error payload as machine-readable strings. retryable=true only for EF-060 timeout where policy allows a bounded retry of *validation*, never a retry that skips enforce(). Models that respond to deny by emitting a different tool_use are starting a new Candidate Act. That is expected.¶
Threats that need the enterprise profile (join of identity and content) are out of scope here except as provenance=unknown plus high consequence_class → escalate.¶
The architecture should not be understood merely as intercepting an untrusted AI-generated string and checking permissions before a software function executes.¶
Conventional middleware, API gateways, reference monitors, policy-enforcement points, and zero-trust systems can already perform authorization before an API call or resource access.¶
The execution-finality model addresses a different architectural problem: generation of an action is separated from authority to make that action externally effective.¶
A model, agent, application, or tool-orchestration layer may compute or propose an action, but that proposed action is represented as a Candidate Act and remains in a Non-Effective State until the required validation conditions are satisfied.¶
Validation establishes protected evidence or authorization state associated with the load-bearing attributes of that Candidate Act, which may include the requested operation, arguments, destination, execution context, authorization scope, policy state, and other relevant parameters.¶
The important point is that approval at an upstream middleware layer does not itself constitute final authority for the external effect.¶
Before the requested consequence becomes externally effective, the relevant Finality Sink or effectuation boundary verifies the required authorization state for the Candidate Act being presented for effectuation.¶
The security model is therefore not simply:¶
check permission -> execute tool¶
but rather:¶
Candidate Act
-> Non-Effective State
-> protected validation and act-specific binding
-> scoped execution authority
-> Finality Sink verification
-> externally effective consequence
¶
The technical distinction is therefore the separation between computation, authorization, and effectuation, together with verification at the boundary where the external effect would actually occur.¶
An execution-finality architecture is only meaningful if the protected consequence cannot be reached through an ungoverned alternative path.¶
An application-level enforce() function may therefore be one implementation interface, but it should not itself be treated as the ultimate security boundary.¶
For protected operations, execution paths capable of producing the same externally effective consequence should converge upon, or otherwise remain subordinate to, the protected finality-enforcement boundary.¶
Relevant paths may include:¶
If the required authorization or validation state cannot be verified at the relevant effectuation boundary, the operation remains non-effective and the system fails closed.¶
The resulting security invariant is:¶
No protected externally effective consequence should occur through an alternate execution path merely because that path bypassed the original middleware or orchestration layer.¶
This anti-bypass property is a core requirement of the execution-finality model rather than an optional application-level policy convention.¶
The architecture does not require hashing the literal JSON string emitted by an AI system.¶
Raw serialization is unsuitable for action binding because equivalent structured data can have different byte representations due to property order, whitespace, encoding choices, or other formatting differences.¶
Instead, the system can derive an arguments_digest, commitment, or equivalent binding value from a deterministic representation of the load-bearing attributes of the Candidate Act.¶
Depending upon the protocol and schema, this process may include:¶
For example, the following two JSON serializations may represent the same Candidate Act:¶
{"amount":100,"currency":"USD"}
¶
{"currency":"USD","amount":100}
¶
A deterministic representation allows both forms to produce the same Candidate-Act commitment where their semantics are equivalent.¶
At the same time, normalization must remain semantics-preserving.¶
Values should not be collapsed merely because they appear superficially similar. For example, a string value and a numeric value should only be treated as equivalent where the applicable schema explicitly defines that equivalence.¶
The architectural requirement is therefore broader than any particular JSON representation:¶
load-bearing Candidate Act attributes
-> deterministic representation
-> Candidate-Act commitment
¶
This approach can be applied to JSON, CBOR, Protocol Buffers, MCP messages, operating-system calls, payment instructions, database operations, robotic commands, or other structured action representations.¶
The purpose of the commitment is not merely transport-integrity checking. It is to ensure that the action presented at the effectuation boundary corresponds to the action state that was actually validated.¶
The execution-finality model is not intended to replace conventional authentication, authorization, zero-trust policy, or middleware controls.¶
Those mechanisms may remain upstream inputs to the decision process.¶
The additional architectural property is that computation does not itself create effectuation authority; a Candidate Act remains non-effective until validated; the validated action state is bound to the relevant execution authority; and the Finality Sink verifies that state before permitting the externally effective consequence.¶
This provides a distinct system-level enforcement boundary for AI agents, tool-use systems, autonomous software, and other environments in which generating or approving an action should not automatically make that action externally effective.¶
This document is a protocol-facing profile of a broader execution-finality architecture developed in the DAS Protocols work. The broader architecture describes a recurring separation between computation of a proposed act, protected validation of the act, issuance or establishment of scoped finality authority, and verification at a Finality Sink before an external consequence becomes effective.¶
The large foundational disclosure referred to by the author as the DAS Protocols "Mothership" is PCT/IB2026/055615, published as WO 2026/150382. That disclosure covers a substantially wider set of execution-finality embodiments, including AI infrastructure, telecommunications, device and operating-system enforcement, protected execution, and other externally effective systems. This Internet-Draft intentionally addresses a much narrower interoperability problem: binding existing AI tool-use and MCP-style dispatch interfaces to an execution-finality boundary.¶
The terminology used here, including Candidate Act, Non-Effective State, protected validation, scoped execution authority, and Finality Sink, should therefore be read as a compact protocol profile rather than as an attempt to reproduce the full DAS Protocols disclosure.¶
This background statement is informative. It does not make implementation of unrelated DAS Protocols embodiments a requirement for conformance with this Internet-Draft. Any IETF intellectual-property disclosure obligations are handled separately under BCP 79 [RFC8179].¶
If enforce() runs in the same process as a fully compromised host that can also call the tool backend directly, the binding is only as strong as alternate-path closure. Production hosts SHOULD block raw credentials from application code paths that skip enforce(), or SHOULD put the sink in a sidecar that owns the tool credentials.¶
Model-visible deny messages MUST NOT leak other users' data. They MAY name the EF code.¶
Argument objects often contain recipients and PII. Logs SHOULD store digests, not raw arguments, once enforce() has run. Mapping layers MUST NOT write full tool input to a shared debug bus by default.¶
The following sketches are informative. They exist so a lab or customer engineer can see that the binding is a wrap of invoke, not a new assistant protocol.¶
def enforce(act, live_args, store, sink_id):
live = digest(canonicalize(live_args))
if live != act["arguments_digest"]["value"]:
return Deny("EF_SCOPE_MISMATCH")
auth = store.get(act["candidate_act_id"])
if not auth or auth.consumed or auth.expired():
return Deny("EF_002")
if auth.sink_id != sink_id:
return Deny("EF_040")
if not store.consume_cas(auth.authority_id):
return Deny("EF_005")
return Allow(auth.authority_id)
def handle_tool_use(block, ctx):
act = map_tool_use(block, ctx)
decision = enforce(act, block["input"], ctx.store, ctx.sink_id)
if not decision.allow:
return {"error": decision.code, "invoked": False}
return invoke(block["name"], block["input"])
¶
async function enforce(act: Act, live: object, store: Store, sink: string) {
const d = digest(canonicalize(live));
if (d !== act.arguments_digest.value) return deny("EF_SCOPE_MISMATCH");
const auth = await store.get(act.candidate_act_id);
if (!auth || auth.consumed || auth.expired()) return deny("EF_002");
if (auth.sink_id !== sink) return deny("EF_040");
const ok = await store.consumeCAS(auth.authority_id);
if (!ok) return deny("EF_005");
return allow(auth.authority_id);
}
async function onOpenAIToolCalls(calls: ToolCall[], ctx: Ctx) {
const out = [];
for (const c of calls) {
const args = JSON.parse(c.function.arguments);
const act = mapToolCall(c, args, ctx);
const d = await enforce(act, args, ctx.store, ctx.sink);
out.push(d.allow
? await invoke(c.function.name, args)
: { error: d.code, invoked: false });
}
return out;
}
¶
These functions are the entire v1 product surface. Vaults, TEEs, and RAOs can wrap later. If invoke is reachable without enforce, the sketches are documentation, not a binding.¶
This document requests no IANA actions.¶
Related execution-finality concepts appear in the DAS Protocols family, including PCT/IB2026/055615, published as WO 2026/150382. This statement is provided for technical transparency and does not substitute for any disclosure required through the IETF IPR process. Applicable IETF disclosure obligations are governed by BCP 79 [RFC8179].¶
Claude will keep emitting tool_use. ChatGPT will keep emitting tool_calls. MCP will keep emitting tools/call. None of those blocks is permission to touch the world. Bind them to an AgentCandidateAct, consume authority at the host sink, then invoke. The model vendor does not have to change. The host loop does.¶
This appendix is informative. It summarizes the minimum conceptual separation used throughout this document.¶
MODEL / AGENT / APPLICATION
|
| proposes
v
+---------------+
| Candidate Act |
+-------+-------+
|
| remains non-effective
v
+-------------------+
| Non-Effective |
| State |
+---------+---------+
|
| validate load-bearing state
v
+-------------------+
| Protected |
| Validation |
+---------+---------+
|
| bind scoped authority/evidence
v
+-------------------+
| Finality Sink |
| Verification |
+---------+---------+
|
allow | deny -> no effect
v
+-------------------+
| External Effect |
+-------------------+
¶
The Candidate Act may be represented using an existing vendor or protocol envelope, but the envelope itself is not treated as effectuation authority. Implementations may use different cryptographic, operating-system, service-side, or hardware mechanisms to realize the protected validation and Finality Sink functions, provided the required security properties of this profile are preserved.¶
The DAS Protocols Mothership [DAS-MOTHERSHIP] is broader than this Internet-Draft. This draft extracts only the tool-dispatch binding needed to express the execution-finality property across contemporary AI tool-use interfaces and MCP-style calls. It should therefore be possible to evaluate, implement, criticize, or standardize this profile independently of the larger disclosure.¶
In this profile, the central invariant can be stated compactly as:¶
Computation is not authority, and authorization is not finality until the effectuation boundary verifies the act-bound state required for the external consequence.¶