-
Notifications
You must be signed in to change notification settings - Fork 2
Desks and rosters
Two snapshots, both borrowed, both supplied by the host on the way in. They
live in
src/roster/,
src/desk/
and
src/chat/.
Roster is a borrowed view of who is present: agent members, human people, and
whatever retirement or ordering the operator has applied on top. It is
constructed from slices the host already read, and it validates its own
structure before anything else uses it.
let members = [RosterMember { id: "alice".into(), name: Some("Alice".into()) }];
let roster = Roster::new(&members, &[], &[]);Agents and people are distinct on purpose. A person mention cannot start an
agent turn, and neither can a desk or an @everyone. Only a direct, active
agent mention can, which is one message, one turn enforced at
the level where a name is read.
A desk is a group conversation with an ordered member list. It is declared in a blueprint and then merged with what the operator did at runtime: added members, retired members, an explicit ordering, extra desks.
Desk {
id: "engineering".into(),
name: "Engineering".into(),
description: Some("Build the product".into()),
members: vec!["alice".into(), "bob".into()],
responder_mode: ResponderMode::Lead,
}DeskSet::new folds the declared desks with those overlays and hands back a
borrowed view. Member order matters: the first member who has not been retired
is the desk lead, unless a complete DeskOrder replaces that order outright.
ResponderMode::Lead picks the first effective active member. That is the
default and it is fully deterministic.
ResponderMode::Auto asks the runtime Selector when the desk has at least
two effective members, and falls back deterministically when the selector is
unavailable, disabled, or returns something that does not name exactly one
candidate. See Responder ladder.
The subtle part is the default desk, which has four spellings and one identity.
A console addresses its default thread as "main". An unaddressed message
stores None. Older events carry "". The desk's own id and display name are
"General".
assert!(is_general_chat(None));
assert!(is_general_chat(Some("")));
assert!(is_general_chat(Some("main")));
assert!(is_general_chat(Some("General")));
assert!(!is_general_chat(Some("engineering")));All four are one conversation, and same_conversation is the only place that
decides so. Everything else compares verbatim, case sensitively.
That single function matters more than it looks. When two surfaces decide "same
conversation?" by different rules, a thread root stored as None fails to
match the "General" it is rendered under, and a threaded continuation quietly
resumes in the channel instead of the thread. One transcript splits across
whichever id happened to write each message. Answering it in one place is the
fix.
tinyhivemind is GPL-3.0-only. Built by @senamakel.
Start here
The algebra
- Shared medium
- Desks and rosters
- Mentions
- Cross-desk referral
- Transcript projection
- Threads
- Recall
- Responder ladder
Hive mechanics
Working on it
Reference