module

Arcana::Hook

arcana hook <event> [--agent claude|codex]: what the Claude Code and Codex session hooks run, so both agents register, check mail and sign off the same way. Reads the hook's JSON payload on stdin.

  • session-start: registers the project's handle (from .ai/config.yml) under its owner token, and tells the agent, in its context, who it is on the bus and that a "held" handle is its own.
  • stop: asks the agent to read waiting mail before it stops, once per change in the count.
  • session-end: moves unread mail into .ai/mail/inbox/ as notes (when the project has .ai/), then marks the handle offline. The handle stays registered, so mail sent between sessions waits for the next agent.

A hook must never break the session: a bus that is down, or any other error, means no output and exit 0.

Constants

EVENTS = {"session-start", "stop", "session-end"}

Class methods

drain(bus : Rest, cfg : AgentConfig) : Array(String)

Move unread mail into .ai/mail/inbox/, one note per message (see the dot-ai skill's mail conventions). Returns the paths written.

Source
mail_flag(handle : String) : String
Source
run(args : Array(String), input : IO = STDIN, output : IO = STDOUT, error : IO = STDERR, base_url : String = ENV["ARCANA_URL"]? || "http://127.0.0.1:19118") : Int32

Returns the process exit code.

Source
session_end(bus : Rest, project_dir : String, payload : JSON::Any) : Int32
Source
session_start(bus : Rest, project_dir : String, output : IO) : Int32
Source
stop(bus : Rest, project_dir : String, agent : String, payload : JSON::Any, output : IO, error : IO) : Int32
Source
write_note(inbox : String, env : JSON::Any) : String

A mail file: header lines, a blank line, then the message. Named by the sender, <handle>--<YYYY-MM-DD-HHMMSS>-<id>.md, as outbound mail is named by its recipient.

Source

Nested types