TramAI - governed AI workflows for Java and Kotlin

AI Agent Tool Governance

Tool calling turns an AI system from a text generator into an actor. A model that can invoke a tool can write data, send email, delete records, create payments, invoke MCP tools, and call external APIs. That makes tool calling a security boundary — and the boundary needs governance.

Tool governance is the practice of deciding, at runtime, whether a tool may be exposed to the model and whether a specific tool call may execute.

Why Tool Calling Is a Security Boundary

A model's text output cannot directly mutate your systems. A tool call can. The moment you register a tool, the model gains the ability to trigger real side effects through your application's own code paths.

Tool capabilityWhat an ungoverned call can do
Write dataInsert or update records in your database
Send emailSend mail as your application or users
Delete recordsDestroy data, possibly without recovery
Create paymentsMove money or create chargeable transactions
Invoke MCP toolsReach every capability the MCP server exposes
Call external APIsTrigger side effects in downstream systems

Prompt instructions are not a control here. "Tell the model not to delete records" is advice; the runtime decision "this delete call is DENIED" is enforcement.

The Three Decisions: ALLOW, DENY, REQUIRE_APPROVAL

TramAI's policy engine models tool governance with three decision types:

DecisionMeaningWhen
AllowThe tool call executesLow-risk tools (customer.read)
DenyThe tool call is blocked, execution never startsDisabled or prohibited actions (account.delete)
RequireApprovalThe tool call suspends until a human decidesHigh-risk actions (payment.execute)

The decision type is evaluated at runtime against the actual call context, not configured once at registration.

Two Boundaries: Exposure and Execution

TramAI enforces tool policy at two distinct points, plus a third for the return path:

Enforcement pointWhat it gates
BEFORE_TOOL_EXPOSUREWhether the model is even shown the tool definition — the model cannot call what it cannot see
BEFORE_TOOL_EXECUTIONWhether the actual call runs
BEFORE_TOOL_RESULT_REINJECTIONWhether the tool result is fed back into the model context

This distinction matters:

Exposure permission is not execution permission.

A tool can be visible to the model — so the model can plan around its existence — while the actual execution is denied or gated behind human approval. Both decisions are recorded as separate evidence events.

Example: Policy Wrapper Denying a Tool

val baselinePolicy = DefaultPolicyEngine(PolicyConfiguration.preview())
val denyingPolicy = PolicyEngine { context ->
    if (context.enforcementPoint == EnforcementPoint.BEFORE_TOOL_EXECUTION
        && context.toolName == "account_delete") {
        PolicyDecision.Deny(
            reason = "Account deletion is disabled",
            reasonCode = "account-delete-disabled"
        )
    } else {
        baselinePolicy.evaluate(context)
    }
}

val engine = TramaiEngine(
    provider = provider,
    toolRegistry = ToolRegistry(mapOf("account_delete" to tool)),
    policyEngine = denyingPolicy,
    policyDecisionAuditEmitter = emitter,
)

When the model calls account_delete:

  1. BEFORE_TOOL_EXPOSURE — the tool is exposed (the model may plan).
  2. BEFORE_TOOL_EXECUTION — the policy engine returns Deny.
  3. The tool never executes — the provider is not called again, and the arguments are never applied.
  4. The denial is recorded as policy.decision evidence with reason code account-delete-disabled.

Approval-Gated Tools

For high-risk actions, the runtime decision is RequireApproval: the tool execution suspends, a cryptographically bound approval challenge is created (binding the workflow run ID, tool name, argument digest, policy version, and workflow digest), and execution resumes only when an external authorizer presents the matching token. See human approval for the lifecycle in depth.

Tool Permission Evidence

Every tool enforcement decision is exported as a dedicated tool.permission evidence event, separate from generic policy.decision events. A reviewer can reconstruct, for each tool call:

  • which enforcement point made the decision
  • whether it was ALLOW, DENY, or REQUIRE_APPROVAL
  • which tool and stream the decision belongs to
  • the audit event it came from

This is the difference between "we have tool calling" and "we can prove what each tool call was permitted to do."

Security Metadata Per Tool

Tools carry security metadata that drives policy evaluation — permission (e.g. customer.read, account.delete, payment.execute), risk level, and whether human approval is required. The runtime combines this metadata with policy at each enforcement point. The deny-by-default posture means: no tool, permission, or model is allowed unless explicitly configured.

MCP and Tool Governance

MCP servers expose tools over a standardized protocol, which means the same governance questions apply to every MCP capability. TramAI's tool permission model covers MCP-bound tools through the same policy engine and enforcement points. Note the honest boundary:

  • Implemented / evolving — the MCP workflow server, and tool governance applied to tools in the runtime.
  • Not implemented (roadmap) — a governed remote MCP tool connector. Governing remote MCP tools end-to-end is future direction.

See MCP governance for the full picture.

When Not to Use This

Tool governance is overhead when there is no tool surface to protect:

  • Read-only workloads with no tool calls — nothing to gate.
  • Tools with zero side effects and public data — Allow-everything policy is fine.
  • Prototypes — use the plain builder; add the sovereign profile when tools can mutate state.

The rule of thumb: the moment a tool can write, send, delete, or pay, it needs a decision boundary, not a convention.