Hooks and sirioctl notify

How an agent tells Sirio it is running, waiting or done, and where the hooks that tell it come from.

sirioctl notify is the command agents call to report their state. A hook runs it from inside the agent’s terminal, and Sirio takes the call as the strongest evidence of what the agent is doing; see Activity states. A chat needs no hooks, since Sirio drives its agent directly.

The call

bash
sirioctl notify --session "$SIRIO_PANE_ID" --status running
sirioctl notify --session "$SIRIO_PANE_ID" --status needs-input
sirioctl notify --session "$SIRIO_PANE_ID" --status done

--status accepts running, needs-input, done and error. --session names the pane and defaults to $SIRIO_PANE_ID, which every Sirio terminal exports. With neither, outside a Sirio terminal, the command does nothing and exits cleanly, so a hook in your own configuration stays quiet when you run the agent anywhere else.

With --title, the same command posts a desktop notification instead:

bash
sirioctl notify --title "Tests finished" --body "All 212 passed"

It takes --title, and optionally --subtitle and --body. sirioctl refuses a call that mixes --title with --session, --status, --agent-session or --stdin-json, because it cannot tell which of the two you meant. On macOS the notification comes from Sirio itself once you allow notifications in Settings → Permissions, through Script Editor until then, and not at all if you deny them; Tray, notifications and persistence has the other platforms.

Install Hooks

Settings → General → Agent Hooks → Install Hooks adds Sirio’s hooks to each agent’s user-level configuration, for agents you start yourself in a Sirio terminal. The card then lists each agent with the file it wrote:

  • Claude Code: settings.json in ~/.claude, or in $CLAUDE_CONFIG_DIR. SessionStart, Stop and Notification report needs-input, UserPromptSubmit reports running, and SessionEnd reports done. Sirio replaces those five hook lists and keeps every other key in the file, and it writes a fresh file over one that does not parse as a JSON object.
  • Codex: the notify key in config.toml under ~/.codex, or $CODEX_HOME, which reports needs-input. The rest of the file keeps its comments and its order, and a file that no longer parses stays as it is while the card reports the failure.
  • OpenCode: a plugin, sirio-session.js, in the plugins folder of OpenCode’s config directory (~/.config/opencode unless $OPENCODE_CONFIG_DIR or $XDG_CONFIG_HOME moves it). It tells Sirio the OpenCode session id through sirioctl session-ref and reports no state.
  • Pi and Oh-My-Pi: nothing. Pi has no hooks, and Oh-My-Pi reads a hook file only from its command line.

These hooks name no pane. sirioctl takes the pane from SIRIO_PANE_ID, so one file serves every Sirio terminal and does nothing outside one.

Hooks Sirio writes on launch

When Sirio starts an agent’s CLI in a terminal itself, the adapter writes hooks that name the pane outright, inside the worktree or on the command line. The five adapters says when that happens.

  • Claude Code: .claude/settings.local.json, with the same five hooks.
  • Codex: a -c notify=[…] command-line override. Sirio writes the JSON inside it without escaped slashes, because Codex parses the override as TOML.
  • Oh-My-Pi: .sirio/omp-hook.ts, which Sirio passes with --hook. It reports running when a session or a turn starts, needs-input when a turn ends and done when the session shuts down.
  • OpenCode: .opencode/plugin/sirio-session.js, the same session-id plugin, which names the pane.

Pi gets nothing. Sirio reads OpenCode’s state from its terminal title and its process, and Pi’s from its title.

Turning it off

Turning off the control socket in Settings → General leaves the hooks nothing to reach, so agents fall back to their title, their screen and their process.