an event is something glrs announces while it runs. an extension handles one
with g.on.
these are not the entries written to a session file. events are announcements; entries are the record.
most handlers only observe. for the events marked below, what the handler returns changes what happens next; returning nothing leaves it alone.
export default (g) => {
g.on("tool_call", ({ name }) => (name === "write" ? "this session is read-only" : undefined));
};
<event> handler failed: <message>, prefixed (extension) in the TUI, on stderr under -p.false ends the chain. no later handler runs.undefined wins.| event | payload | returning |
|---|---|---|
session_start |
{root} |
|
session_end |
{root} |
|
input |
{text} |
string replaces what was typed, false swallows it |
user_bash |
{command} |
|
turn_start |
{text} |
|
turn_end |
{text} |
|
idle |
{} |
|
message |
{kind: "text" | "reasoning", text} |
|
before_request |
{prompt, messages} |
string appended to this turn's message |
tool_call |
{name, input} |
string or false blocks the call |
tool_start |
{name, input} |
|
tool_end |
{name, input, ok, result, detail, elapsedMs} |
string replaces what the model is told the tool returned |
model_select |
{model, variant?} |
|
usage |
{input, output, cached, cost?, contextTokens} |
|
reasoning |
{text, elapsedMs} |
|
error |
{message} |
|
compact |
{dropped, kept, automatic} |
|
context |
{messages, step} |
ModelMessage[] replaces what this call sends |
before_provider_request |
{url, headers, body} |
headers merge over the request's, body replaces it |
after_provider_response |
{url, status, headers} |
|
agent_start |
{ prompt } |
nothing |
agent_end |
{ text } |
nothing |
before_agent_start |
{ prompt, systemPrompt } |
a string replaces the prompt, false cancels the turn, an object replaces either field |
session_before_compact |
{ automatic, instruction? } |
false cancels it, an object supplies the summary or the instruction |
session_before_fork |
{ id, at? } |
false cancels the fork |
session_before_switch |
{ from, to } |
false cancels the switch |
session_shutdown |
{ root } |
nothing, awaited before the process exits |
glrs -p fires every event except input, user_bash, model_select and compact.
context fires once per stream attempt, not once per step. step is the attempt number, from 1, and a re-sent stream fires it again.context replaces what one call sends. the stored conversation is untouched.before_request.messages is a count of stored messages, not the messages. read them in context.-p, before_request.messages is always 0.tool_call reaches the model as the tool's result: ERROR: <your string>, or ERROR: an extension blocked <name> for this turn. for false. the turn continues.idle then turn_end. -p fires turn_end then idle.session_end, so work on the way out finishes. the TUI's screen stops as soon as it resolves, so printing there lands nowhere.see also: extensions, a turn