A demo agent that can call anything looks clever. A shipped EDGE FDE agent has a finite set of states: gather, decide, act, review, done. Each state has a Profile, a tool allow-list, and a list of exits. If the model asks for an illegal exit, the app says no.
Do not import LangChain, LangGraph, or a Python graph runner. Those stacks hide the loop. On iOS 27 and macOS 27 you already have Dynamic Profiles and tools. The missing piece is your enum and your transition table.
enum Mode: String, Sendable { case gather, decide, act, review, done }
let legal: [Mode: Set<Mode>] = [
.gather: [.decide],
.decide: [.act, .gather],
.act: [.review],
.review: [.done, .gather],
.done: []
]
WWDC26 session 242 describes orchestration patterns on top of Profiles. It still expects you to own mode. The Crafts sample switches analysis and brainstorm from app state. That is a state machine, even when the file is named Profile.
Next, bind each state to one active Profile.
Key concepts
- Shipped agents have finite states: gather, decide, act, review, done.
- Each state has a Profile, a tool allow-list, and legal exits.
- Write a
Modeenum and a legal transition table. - WWDC26 orchestration patterns still expect you to own mode.
Takeaways
- Named states make the next step visible.
- Do not import LangChain or LangGraph.
- A teammate should point at current state and say what can happen next.