A Zulip bot agent to sit in our Black Sun. Ever evolving
1(*---------------------------------------------------------------------------
2 Copyright (c) 2026 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Per-channel and per-DM Claude session management.
7
8 Sessions maintain conversation context independently for each Zulip
9 channel or DM thread, enabling multi-turn conversations with Claude. *)
10
11val src : Logs.Src.t
12(** Log source for session management. *)
13
14(** Role in a conversation turn *)
15type role = User | Assistant
16
17(** A conversation turn in a session *)
18type turn = { role : role; content : string; timestamp : float }
19
20(** Session scope - either a channel+topic or a DM with a user *)
21type scope =
22 | Channel of { stream : string; topic : string }
23 | Direct of { user_email : string; user_full_name : string }
24
25val scope_to_string : scope -> string
26(** [scope_to_string scope] returns a human-readable description of the scope. *)
27
28val scope_to_mention : scope -> string
29(** [scope_to_mention scope] returns a Zulip-compatible linked mention.
30 For channels, returns [#**stream>topic**] format.
31 For DMs, returns [DM with `email`] format. *)
32
33(** Session data *)
34type t
35
36val empty : now:float -> t
37(** [empty ~now] creates an empty session. *)
38
39val max_turns : int
40(** Maximum turns to keep in a session for context window management. *)
41
42val scope_of_message : Zulip_bot.Message.t -> scope
43(** [scope_of_message msg] extracts the session scope from a Zulip message.
44 Channel messages use stream+topic as scope, DMs use sender email. *)
45
46val load : Zulip_bot.Storage.t -> scope:scope -> now:float -> t
47(** [load storage ~scope ~now] loads a session from storage.
48 Returns an empty session if none exists or if the session has expired. *)
49
50val save : Zulip_bot.Storage.t -> scope:scope -> t -> unit
51(** [save storage ~scope session] persists the session to storage. *)
52
53val add_user_message : t -> content:string -> now:float -> t
54(** [add_user_message session ~content ~now] adds a user message to the session. *)
55
56val add_assistant_message : t -> content:string -> now:float -> t
57(** [add_assistant_message session ~content ~now] adds an assistant response. *)
58
59val clear : Zulip_bot.Storage.t -> scope:scope -> unit
60(** [clear storage ~scope] removes the session from storage. *)
61
62val build_context : t -> string option
63(** [build_context session] builds a context string from the session history
64 for inclusion in Claude prompts. Returns [None] if the session is empty. *)
65
66val stats : t -> string
67(** [stats session] returns human-readable session statistics. *)