Apple does not give Foundation Models a per-tool CPU quota. You set limits in the executor. Four limits cover most Edge FDE tools.
struct ResourceBudget: Sendable {
var timeout: Duration = .seconds(8)
var maxBytes: Int = 1_000_000
var maxFiles: Int = 4
var networkNames: Set<String> = []
}
func checkBudget(request: ToolRequest, budget: ResourceBudget, payloadBytes: Int) throws {
if request.fileIDs.count > budget.maxFiles {
throw ExecutorError.fileNotAllowed
}
if payloadBytes > budget.maxBytes {
throw ExecutorError.fileNotAllowed
}
if request.url != nil, !budget.networkNames.contains(request.name) {
throw ExecutorError.networkDenied
}
}
Timeout: use a racing Task.sleep as in lesson 03, then cancel the work. Cancellation only stops work that checks Task.isCancelled. For a macOS helper, send a cancel over XPC and kill the request, not the whole helper, if you can.
Memory: cap bytes you will read into RAM. Do not load a photo library as one string. You cannot set a hard process RSS limit from Swift on iOS the way a Linux cgroup would. Be honest about that. Cap the payload you accept.
Files: pass ids your app already resolved, not raw paths from the model. On macOS, pass security-scoped bookmarks. On iOS, pass URLs from the document picker or your container.
Network: deny by default. If a tool must fetch, allow that tool name only, use HTTPS, and keep the host list in your code. App Transport Security still applies.
Read Talk to tools with MCP, Stop prompt attacks and leaks, and Make several agents agree first so a model-supplied host name cannot become your network even if the tool is allowed.
Next, wire the executor into Foundation Models tools.
Key concepts
- Apple gives no per-tool CPU quota. Limits live in your executor.
- A budget sets timeout, max bytes, max files, and allowed network names.
- Pass app-resolved file ids, not raw paths from the model.
- Deny network by default. Allow named tools only over HTTPS.
Takeaways
- Resolve files in the app, not from model text.
- Timeout and cancel cooperative work. Cap bytes read into RAM.
- Model text never picks a path directly.