Data contracts
Hash and struct shapes agents must preserve when editing lib code. Changing a contract requires updating tests and this doc.
Catalogue search
Canonical files: lib/auslib_search.rb, lib/search_query.rb, lib/search_report.rb
SearchQuery.parse(text, max_length:)
{ query: "banjo paterson", empty: false }
{ query: nil, empty: true } # blank input
*_search.search(...)
Success:
{
query: String,
total: Integer,
items: [
{ title: String, url: String, ... } # domain-specific keys
]
}
Failure:
{ error: :usage } # empty query
{ error: :fetch, query: String|nil } # HTTP/parse failure
{ error: :config, query: String|nil } # missing API key (auslib only)
SearchLookup.format(result, report:, config_errors:)
result[:error] |
Returns |
|---|---|
:usage |
report.build_usage |
:fetch |
report.build_fetch_error(result[:query]) |
:config |
report.build_config_error if config_errors: true, else fetch error |
| (none) | report.build_report(query:, total:, items:) |
lookup(...) public API
Catalogue plugins call *Search.lookup which returns a String (IRC reply), not a raw hash.
AI plan session
Canonical files: lib/ai_plan_session.rb, lib/rabbot_meta_plan_session.rb
Pending session (pending(...))
{
session_id: String,
asker: String,
question: String, # aip only
description: String, # rabbot_meta only
questions: [
{
id: String,
prompt: String,
options: [{ id: String, label: String }],
allow_multiple: false
}
],
model: String,
build_state: Hash # rabbot_meta only
}
Stored in RabbotDb as JSON under plugin "aip" / "rabbot_meta", key "pending_plan" / "pending_build_plan".
AiPlanAgent::PlanResult / RabbotMetaAgent::AgentResult
# keyword struct fields success: Boolean text: String session_id: String|nil questions: Array|nil needs_input: Boolean input_tokens: Integer output_tokens: Integer duration_ms: Integer error: String|nil # AgentResult also: todos: Array
Questions normalized by CursorAgentQuestions.normalize.
Directchat wire
Canonical file: lib/directchat_wire.rb
Wire line format: DCHAT1 <TYPE> <json>
decode(line) → Hash or nil
:type |
Keys |
|---|---|
:session |
:session → session hash (symbol keys) |
:relay |
:session_id, :from, :text |
:stop |
:session_id |
:hello |
:bot |
Session hash (DirectchatSessionCodec)
{
id: String,
channel: String, # e.g. "#channel"
requester: String, # nick
assignments: [
{ bot: String, participant: String, invited_at: String|nil }
],
created_at: String,
status: String, # default "active"
bot_count: Integer|nil
}
JSON storage uses string keys via stringify(session).
Games
Canonical file: lib/game.rb
Credits
-
Game::STARTING_CREDITS= 1000 -
Game::MIN_BET= 1,Game::MAX_BET= 1000 -
Game.valid_amount?(n)— bet range check -
Game.balance_for(user, store:)— Integer -
Game.default_store—PersistentStoreplugin"game", key"store"
Per-game state lives in lib/*_game.rb modules; plugins call Game for credits and IRC card styling.
YouTube (yt)
Canonical files: lib/yt_oauth.rb, lib/yt_playlist.rb, lib/yt_commands.rb
OAuth device flow
YtOAuth.start_device_flow(bot_nick:, fetch:) → { success:, verification_url:, user_code:, expires_in: } or { success: false, error: }
YtOAuth.poll_device_flow(bot_nick:, fetch:) → { success:, account: } or { success: false, pending: true, error: } or { success: false, error: }
Account hash (stored in RabbotDb, scoped to bot nick):
{
"refresh_token" => String,
"channel_id" => String,
"channel_title" => String,
"linked_at" => String # ISO8601
}
Playlist operations
YtPlaylist.list_playlists → { success:, playlists: [{ id:, title:, privacy: }] }
YtPlaylist.create_playlist → { success:, playlist: { id:, title:, privacy: } }
YtPlaylist.show_playlist → { success:, playlist:, items: [{ item_id:, video_id:, title: }] }
YtPlaylist.add_video / remove_item / delete_playlist → { success:, ... } or { success: false, error: }
Commands
YtCommands.dispatch(...) → { reply: String } or { error: String }
RabbotDb plugin JSON
Canonical file: lib/rabbot_db.rb
RabbotDb.get_plugin_json( plugin: "my_plugin", key: "my_key", network: nil, # optional scope channel: nil, nick: nil, default: nil ) RabbotDb.set_plugin_json( plugin:, key:, value:, # value: Hash or nil to clear network:, channel:, nick: )
Use normalize_network, normalize_channel, normalize_nick before writes.
User levels: RabbotDb::LEVEL_ADMIN, LEVEL_VOICE, etc. — see RABBOT_DB.md.
Async duel games
Canonical files: lib/async_duel.rb, lib/async_duel_handler.rb
Challenge flow stores state in RabbotDb under the plugin name. Handler expects { reply: String } or { error: String } from game modules.
Help metadata (plugins)
Every plugin should have:
# Help: description — !command usage — responders: all_loaded|owner|room_reader
Parsed by HelpParser.list_commands for !help and PluginCatalog.
IRC reply cards
Canonical file: lib/irc_reply.rb
IrcReply.card(tag:, title:, body_lines:, footer_parts:, heading_style:) → multi-line String with IRC colour codes.
Body lines should be plain text; colour only on tag, heading, footer — see CONVENTIONS.md.
MessageProgress::ReplyContext
MessageProgress::ReplyContext.new( lag_ms: Integer, ping_ms: Integer|nil, piece_index: Integer, # 0-based chunk index total_pieces: Integer, queued: true|false|nil # Bot output queue defer active )
FloodSafe#reply_pieces with with_progress: :auto prefixes lines when MessageProgress.should_prefix? is true (2+ logical lines or 2+ chunks). Explicit with_progress: true on a single-line reply adds the 100 status gutter only.
Per-channel opt-out: BotOptions key no_progress_gutter (boolean) disables the gutter when true.
Label:value body rows in reports should use SearchReport.colon_body_lines([{ label:, value: }, ...]) before joining body_lines.