The gate must sit in the tool, not only in the chat view. If a session can call IssueRefund directly, a single reply can still spend money. Keep side-effect tools off the voter sessions. Put them on a runner that checks Gate first.
struct IssueRefund: Tool {
let name = "issue_refund"
let description = "Refund an order. The app must already have an allow gate."
let gate: () async throws -> Gate
@Generable
struct Arguments {
var orderId: String
var amount: String
}
func call(arguments: Arguments) async throws -> String {
let decision = try await gate()
guard decision == .allow else {
throw GateError.blocked(decision)
}
return try RefundStore.shared.refund(orderId: arguments.orderId, amount: arguments.amount)
}
}
On voter sessions, set tool calling to disallowed, or attach no side-effect tools. Apple's ToolCallingMode can be allowed, disallowed, or required. Use disallowed while you collect votes. Use required on a runner only after Gate.allow, and give that runner an exit so it cannot loop. See WWDC26 session 242.
Checklist
- Write the action that must not run on one reply.
- Give each specialist its own session, or a child session with its own transcript.
- Tally weights in your code. Keep a threshold you can explain.
- On a split, run a judge. On escalate, show a person and stop.
- Keep side-effect tools off voter sessions. Check the gate inside the tool.
- Use Dynamic Profiles to switch seats. Do not treat one profile body as a parallel vote.
Primary sources: WWDC26 session 242 and Foundation Models.
Related courses: Pause and resume long AI jobs, Stop prompt attacks and leaks, and Talk to tools with MCP.
Key concepts
- The gate must sit in the tool, not only in the chat view.
- Voter sessions use no side-effect tools.
- The runner session may call the tool only after the gate allows it.
- Tool calling modes include allowed, disallowed, and required.
Takeaways
- Several sessions vote, you tally, a judge or person breaks a split, the tool checks the gate.
- Keep side-effect tools off voter sessions.
- One profile body is not a parallel vote.