Model request/response and notification events, a subagent, and a provider offer

Sample mod for the roadmap E events and registrations. It:
model.request (systemAppend),model.response,Notification (Claude Code's hook name, mapped to notification),PostToolUse / PostToolUseFailure and shows the last subagent result with SubagentStop (mapped to tool.result, tool.error, subagent.stop),events-demo-doc-checker subagent from agents/doc-checker.md with $.agent.register,$.provider.register.Not installed by default. Copy this folder to ~/.pawn/mods/events-demo and allow it in the review dialog. The dialog rates it High risk because it changes what is sent to the model and offers a provider. The provider appears under Settings → Mods → Providers offered by mods; it is only added, turned off, when you click Add.
Docs: docs/agent/MODS.md.
hooks/register.js 86 lines1// events-demo: the roadmap E events and registrations in one small mod.
2let calls = 0
3let lastTools = []
4let notes = 0
5let toolRuns = 0
6let toolFails = 0
7let lastSubagent = 'none'
8
9// $.slot calls are written out literally so the review dialog can list them.
10function draw($) {
11 const el = $.ui.resolve({})
12 $.slot.sidebar({
13 id: 'events',
14 title: 'Events demo',
15 tree: [
16 el.Text({ text: 'Model calls: ' + calls }),
17 el.Text({ text: 'Last tools: ' + (lastTools.join(', ') || 'none') }),
18 el.Text({ text: 'Notifications seen: ' + notes }),
19 el.Text({ text: 'Tool runs: ' + toolRuns + ' (failed ' + toolFails + ')' }),
20 el.Text({ text: 'Last subagent: ' + lastSubagent })
21 ]
22 })
23}
24
25export function register(on) {
26 on('session.start', async ($, e, next) => {
27 if (e.plugin !== $.plugin.name) return next(e)
28
29 // A subagent from a Claude Code agent file shipped with the mod.
30 const md = await $.fs.read($.plugin.root + '/agents/doc-checker.md')
31 $.agent.register({ name: 'doc-checker', markdown: md })
32
33 // A provider offer. Nothing changes until you click Add in Settings → Mods.
34 $.provider.register({
35 id: 'local-ollama',
36 name: 'Local Ollama',
37 apiFormat: 'openai',
38 baseUrl: 'http://localhost:11434/v1',
39 models: [{ modelId: 'qwen3:8b', label: 'Qwen3 8B', contextWindow: 32768, supportsTools: true }]
40 })
41
42 draw($)
43 return next(e)
44 })
45
46 // Before each model call: add one short instruction to the system prompt.
47 on('model.request', async ($, e, next) => {
48 return next({ ...e, systemAppend: 'When you finish a change, name the files you touched.' })
49 })
50
51 // After each model call: count it (observe only).
52 on('model.response', async ($, e, next) => {
53 calls += 1
54 lastTools = e.toolCalls.slice(0, 3)
55 draw($)
56 return next(e)
57 })
58
59 // Claude Code's Notification hook name works too.
60 on('Notification', async ($, e, next) => {
61 notes += 1
62 draw($)
63 return next(e)
64 })
65
66 // Claude Code's PostToolUse / PostToolUseFailure: after a tool really ran.
67 on('PostToolUse', async ($, e, next) => {
68 toolRuns += 1
69 draw($)
70 return next(e)
71 })
72 on('PostToolUseFailure', async ($, e, next) => {
73 toolRuns += 1
74 toolFails += 1
75 draw($)
76 return next(e)
77 })
78
79 // Claude Code's SubagentStop: when a subagent finishes (ok, error, or aborted).
80 on('SubagentStop', async ($, e, next) => {
81 lastSubagent = e.agent_type + ' ' + e.status + ' (' + e.rounds + ' rounds)'
82 draw($)
83 return next(e)
84 })
85}
86