← Engineering agent harnesses

Part 9 of 18 · 7 min read

Authority, approvals and the trust boundary

Separating conversational confirmation from authorization, scoping actions and designing durable human approval.

Arunank Sharan · Kuzushi Labs ·

In this article

An agent can understand a request perfectly and still be the wrong actor to carry it out. A guest asking for a refund, a host asking for an operational report and an administrator changing a tool definition have different authority even when all three requests arrive as text.

The client platform put tool policy between the model’s proposal and execution. That policy included allowlists, confirmation behaviour, approval handling and trusted scope. The essential design question was which decisions belonged to the conversation and which required application evidence.

A model proposes an action; scope, policy and approval determine whether it can cross the execution boundary.

A prompt is not authorization

“Only access the current guest’s booking” is useful guidance, but the backend must enforce the same restriction independently. The model can misunderstand context, respond to injected content or simply emit the wrong ID.

The implementation strips protected account and tenant keys from model arguments and supplies context from the runtime. Tool allowlists are checked at execution. Guest-specific flows also use the resolved stay set and focus. Backend account-scoped access remains the final authority on the domain record.

These controls form a chain. Missing any link can undermine the others. For example, a correctly authenticated agent service calling an unscoped backend endpoint still exposes an unsafe operation.

Three different meanings of “yes”

A guest can confirm that the leaking shower is in the upstairs bathroom. They can confirm they want the maintenance request filed. A host can approve an action that policy reserves for an operator. These are different kinds of confirmation.

The tool interface includes conversational confirmation markers, and the runtime has a durable approval path. They should not be conflated. A model-generated confirm=true can represent the model’s interpretation of the conversation; it is not independent proof that an authorized human approved a high-impact mutation.

For consequential actions, the application needs a stored approval bound to the action, actor and scope. A sentence in history is not a sufficient replacement for that record.

Approval resumption is a new execution boundary

When an action needs approval, persist a proposal and return a waiting outcome. Resumption must restore the proposal’s identity and authorization context, then revalidate its preconditions. The approval can be rejected, expire, or become stale because the underlying resource changed.

There are two common execution models: continue a durable workflow, or start a fresh run from the approved proposal. A fresh run can fetch current context, but it must not treat the new model plan as equivalent to the old proposal. A changed action needs a new approval even when the user’s original request has not changed.

Bind authority to the approved action, not to the conversation turn as a whole. For example, approval to extend a reservation by one night must not also authorise cancellation or a different price. The application checks that binding immediately before the mutation, after any fresh planning has taken place.

Bind approval to the proposed action

A reference approval record should include the authenticated tenant, approver, tool name, canonical argument digest, relevant resource version, expiry and current status.

import hashlib
import json

def action_digest(tenant, tool, args, resource_version):
    payload = {
        "tenant": tenant,
        "tool": tool,
        "args": args,
        "resource_version": resource_version,
    }
    wire = json.dumps(payload, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(wire.encode()).hexdigest()

assert action_digest("a", "modify_booking", {"nights": 3}, 7) != \
       action_digest("a", "modify_booking", {"nights": 4}, 7)

This example binds the grant to the action’s meaning. The digest is useful only if the record is stored and checked by trusted code. It is not a credential that the model can grant itself.

On execution, verify that the approval is pending or claimable, belongs to the correct tenant, matches the action and has not expired. If the underlying booking changed after approval, revalidate or request a fresh decision. Approval of an old price should not authorize a different price silently.

Preview and apply are separate phases

Some operations are naturally two-phase. A booking modification can first show the proposed dates, price difference and consequences. Applying the change uses that preview’s validated parameters and current domain state.

This reduces ambiguity. “Yes” can then refer to a concrete proposal with an identity, not whichever plan the model happens to reconstruct. The UI can display the proposal, while messaging can refer to a short action summary.

The same principle applies to bulk host operations. Before changing ten tasks, identify the exact task set. A dynamic query such as “all overdue tasks” can match a different set by the time an approval is used.

Permissions need an audience model

The guest concierge and host copilot have different tool surfaces. An operator console is another audience again. Editing a published tool can change execution for many users, so configuration access is an operational privilege rather than ordinary chat access.

A role model should distinguish viewing traces, running evaluations, trying tools, editing drafts, publishing configuration and approving domain actions. Even if an early deployment combines several roles for a small team, those permissions should remain conceptually separable.

Raw traces can include guest context. A person allowed to edit a prompt does not automatically need access to every guest’s messages. Likewise, an evaluation reviewer can often work with sanitized cases rather than live account data.

Retrieved content remains untrusted input

Property manuals, support messages and tool-returned free text can contain instructions. The model must treat them as evidence, not as authority to change its operating rules.

The stronger protection is structural. A retrieved instruction cannot add a tool to the allowlist, alter the authenticated tenant or mint an approval record. It can still influence language, so evaluation should include adversarial content inside the knowledge and tool-result channels, not only hostile user messages.

For a future general web-fetch or code-execution tool, the trust model changes substantially. Network destinations, filesystem access, execution isolation and credential exposure become additional boundaries. The hospitality runtime’s ordinary backend tools should not be described as an unrestricted computer-use sandbox.

Dry-run mode has a specific guarantee

A dry-run mode can intercept mutating tools and return controlled fixture outcomes. Its scope needs to be explicit: a simulation may still perform real reads unless those are redirected too. Use a test environment with synthetic records when validating complete workflows; a simulated success does not establish that a real backend mutation would succeed.

The guarantee also depends on accurate tool metadata. A mutating endpoint mislabeled as a read defeats a write short-circuit. Tool-contract review therefore belongs alongside dry-run testing.

A dry run, an approved action and a completed action should be visually distinct in the portal. Otherwise an operator can mistake a convincing simulation for operational evidence.

Evaluate authority with negative cases

The most valuable tests are often requests that must not succeed: another tenant’s ID, an expired approval, a changed action after approval, a write requested through a read-only persona, or a duplicate resume.

For each case, assert that the side-effect handler was not called. A polite refusal alone is insufficient; the model could have performed the action before apologising. The authorization result and the conversational result need separate observations.

Authority becomes manageable when it is represented as application state. The model can help a user express intent. The service remains responsible for proving that an allowed actor authorized the specific action being executed.