Tool invocation rules 

An installation may add a ToolInvocationRuleInterface implementation to restrict calls by their final arguments, actor, configuration, target or prior outcomes. Symfony autoconfiguration registers the rule. Its identifier() must return a unique, stable code matching [a-z][a-z0-9_.-]*, at most 64 ASCII characters long.

The existing ToolCallPolicyInterface first checks whether the tool remains permitted. The invocation policy then calls every rule until one denies. A rule returns ToolInvocationDecision::allow() or ToolInvocationDecision::deny('outside_site'). Reason codes use the same format as identifiers; arguments and exception text never form a denial message. No rules preserves the existing permission behavior.

The context contains the tool name, final arguments, configuration and ToolExecutionContext. The latter carries the initiating actor, acting backend user and run reference. Input resumes supply the validated human input after it has replaced the model's values for declared input fields. Approval resumes evaluate the same policy immediately before the approved call executes.

A refusal may carry an explanation, ToolInvocationDecision::deny('outside_site', 'Say what to do.'): one sentence for the person behind the run, in their language. The refused call's result passes it on after the codes. It is never recorded.

A rule may also be asked before an approval card is shown, so that nobody is asked to approve a call it refuses. It opts in by implementing PreApprovalInvocationRuleInterface, a marker without methods, and promises that its answer comes from the call, the configuration and the execution context alone, never from the history, and that it writes nothing. The loop then asks it, through PreApprovalInvocationPolicyInterface, where it decides whether a call suspends. A call it refuses on purpose does not suspend; it is settled apart, refused and recorded once as an invocation_denied event, and never executed on the strength of that refusal: if the rule allows it by then, it is refused for the approval it skipped. A refusal that reports a failure (rule_failed, history_incomplete, policy_failed) does not skip the card. Beside a call that waits, the refused call stays on the card with its explanation and is refused, whatever the rule says by then, when the turn is approved. The same rule is asked again immediately before every call executes, so it is asked at least twice for a call that waits for approval and has to be cheap. A rule that does not implement the marker is asked only when the call runs, as before. The built-in RequireDraftWorkspaceRule opts in (Editorial profile: drafts only).

Implement ToolTargetResolverInterface when the rule needs a target. Return a ToolInvocationTarget with the authoritative kind and identifier, or null for calls the resolver does not understand. Multiple resolvers claiming a call, or an exception while resolving it, refuse execution. A missing target remains null; a rule requiring a target must explicitly deny it. Target references are stored in the history, so they must contain identifiers, never credentials or document content.

ToolInvocationHistory is independent of the model transcript. Each observation contains the tool, outcome and optional target reference. Suspensions persist it and both resume paths carry it forward. A rule whose requiresCompleteHistory() returns true is refused when a legacy or damaged state cannot prove its history. A bare runLoop() call starts a complete empty history only when it assembles a fresh prompt and all three seed counters are zero. A call with skipAssembly or any nonzero seed counter must supply authoritative ToolExecutionContext::$initialInvocationHistory to prove its prior observations; otherwise the history remains incomplete. A rule exception refuses execution as rule_failed. Denials retain the rule identifier and stable reason in the invocation_denied governance event without arguments or result content. The event's reason is the bounded rule code, separate from the ToolDenialReason used for tool_denied events.