Hooking without replacing
Events are dicts, so you can observe and react while still passing everything through.Replacing the loop
fn is an async generator that yields events. Accept **kw so new keyword
arguments do not break your loop as the SDK grows.
The kit
Session
Session is the part worth understanding, because it is what makes a run
survivable.
add_user writes to disk before the model is called, so a dropped connection
never loses what the person said. On a regenerate or retry, context.replay is
true and the turn is already saved, so add_user adds nothing and your loop
needs no special case. rollback removes only the assistant tail, so a failed
turn does not corrupt the transcript. clear and compact change only what
context() returns. The turns on disk stay as they were.
The provider interface
stream yields UI events, bare strings for text deltas, then exactly one Turn
carrying the assistant content, stop reason and token counts. Adding a new
provider means one file that conforms to this protocol.
The message shape is Anthropic’s, which is the richest superset across vendors.
Each provider translates from it to its own wire format.
What you give up
Writing your own loop means owning the behavior the default one provides: retries with backoff, clearing and compaction before overflow, one recovery replay on a context error, checkpointing between tool batches, heartbeats during long tool runs, cost logging, and unwinding cleanly on cancellation. Start by wrappingdefault_loop and intercepting what you need, then replace it
only when wrapping stops being enough.
Next
Interface
Branding, themes, the explore menu and the example gallery.