1//! How lmjtfy decides what to do with a query: rules over facts, compiled 2//! into a Rete network that the page draws. 3//! 4//! A fact is something known about the query: whether Jev can answer it, 5//! which kinds of question it reads as, whether it is several questions. A rule is a 6//! list of tests on facts and what to do when they all hold. The network is 7//! those rules with their shared tests merged: one alpha node per distinct 8//! test, and a chain of join nodes per rule, shared where rules begin alike. 9//! 10//! Most facts are Jev's to give: each is one typed question about the input. 11//! A rules engine would ask for them one at a time, as each rule came to 12//! need one. This one does not. [`Network::next`] collects every Jev fact 13//! that any rule still alive is waiting on, and asks for them together, so 14//! they go out as one request with N questions (the user, 2026-10-02: "roll 15//! up multiple rules that all use jev and parallelize them by firing a 16//! single request with N questions to backfill"). Asking Jev more in one 17//! request is what keeps the LLM, which costs far more, for the queries 18//! that need it. 19//! 20//! Nothing here does I/O. The Worker asks [`Network::next`] what to do, does 21//! it, records what it learned in [`Known`], and asks again. 22#![forbid(unsafe_code)] 23 24use std::collections::BTreeMap; 25 26use serde::{Deserialize, Serialize}; 27 28/// A way to read an input as a question, which is also the Jev type that 29/// answers it. 30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 31pub enum Kind { 32 /// Yes or no, or how likely. 33 Noul, 34 /// The best among several possibilities. 35 Choice, 36 /// A degree, an amount, a rating. 37 Score, 38 /// The chance of something: how likely, what are the odds. Jev answers 39 /// it as a yes-or-no about the thing itself, and the probability of yes 40 /// is the answer. 41 Chance, 42} 43 44impl Kind { 45 pub const ALL: [Kind; 4] = [Kind::Noul, Kind::Choice, Kind::Score, Kind::Chance]; 46} 47 48/// Something known about a query. 49#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 50pub enum Fact { 51 /// Jev can judge the input at all. 52 Answerable, 53 /// It asks more than one separate question. 54 Several, 55 /// The input is to be read as this kind of question. Jev gives a 56 /// probability for each kind; the likeliest is read, and so is the 57 /// runner-up when Jev is split between them. So one, or two, hold. 58 Reads(Kind), 59 /// A how-much question that needs a scale written for it: a general 60 /// scale would answer it badly. 61 Scale, 62 /// The answer to the input, read as a yes-or-no question. 63 Yes, 64 /// The answer to the input, read as a how-much question, on a fixed 65 /// scale that needs nobody to write it. 66 Degree, 67 /// Whether the thing the input asks the chances of is so. The answer to 68 /// a how-likely question is this fact's probability. 69 Chance, 70 /// The input is fit to show to strangers on a public page. 71 Fit, 72 /// The LLM wrote at least one question Jev can take. 73 Drafted, 74 /// Jev answered at least one of the LLM's questions. 75 Judged, 76} 77 78/// Where a fact comes from. 79#[derive(Clone, Copy, Debug, PartialEq, Eq)] 80pub enum Source { 81 /// A Jev question about the input. Every such fact that is wanted is 82 /// asked for in the same request. 83 Jev, 84 /// The LLM, when a rule's effect asks it. 85 Llm, 86 /// Jev, when a rule's effect asks it what the LLM wrote. 87 JevAgain, 88} 89 90impl Fact { 91 pub const ALL: [Fact; 13] = [ 92 Fact::Answerable, 93 Fact::Several, 94 Fact::Reads(Kind::Noul), 95 Fact::Reads(Kind::Choice), 96 Fact::Reads(Kind::Score), 97 Fact::Reads(Kind::Chance), 98 Fact::Scale, 99 Fact::Yes, 100 Fact::Degree, 101 Fact::Chance, 102 Fact::Fit, 103 Fact::Drafted, 104 Fact::Judged, 105 ]; 106 107 pub fn source(self) -> Source { 108 match self { 109 Fact::Drafted => Source::Llm, 110 Fact::Judged => Source::JevAgain, 111 _ => Source::Jev, 112 } 113 } 114 115 /// Everything the fact can turn out to be. 116 pub fn values(self) -> &'static [Value] { 117 match self { 118 Fact::Yes | Fact::Degree | Fact::Chance => &[Value::Given], 119 _ => &[Value::Bool(true), Value::Bool(false)], 120 } 121 } 122 123 /// The fact's name on the diagram and in a playground link. 124 pub fn name(self) -> &'static str { 125 match self { 126 Fact::Answerable => "answerable", 127 Fact::Several => "several", 128 Fact::Reads(Kind::Noul) => "yes-or-no", 129 Fact::Reads(Kind::Choice) => "pick", 130 Fact::Reads(Kind::Score) => "how-much", 131 Fact::Reads(Kind::Chance) => "how-likely", 132 Fact::Chance => "chance", 133 Fact::Scale => "scale", 134 Fact::Yes => "yes", 135 Fact::Degree => "degree", 136 Fact::Fit => "fit", 137 Fact::Drafted => "drafted", 138 Fact::Judged => "judged", 139 } 140 } 141} 142 143/// What a fact turned out to be. 144#[derive(Clone, Copy, Debug, PartialEq, Eq)] 145pub enum Value { 146 Bool(bool), 147 /// Known, with a value no rule tests: the answer itself. 148 Given, 149} 150 151impl Value { 152 pub fn label(self) -> &'static str { 153 match self { 154 Value::Bool(true) => "yes", 155 Value::Bool(false) => "no", 156 Value::Given => "known", 157 } 158 } 159} 160 161/// One test on one fact: an alpha node. 162#[derive(Clone, Copy, Debug, PartialEq, Eq)] 163pub enum Test { 164 Is(Fact, Value), 165 /// The fact is known, whatever it is. 166 Known(Fact), 167} 168 169impl Test { 170 pub fn fact(self) -> Fact { 171 match self { 172 Test::Is(fact, _) | Test::Known(fact) => fact, 173 } 174 } 175 176 /// The test without its fact's name, as the diagram labels the node. 177 pub fn label(self) -> String { 178 match self { 179 Test::Is(_, value) => format!("= {}", value.label()), 180 Test::Known(_) => "is known".to_owned(), 181 } 182 } 183} 184 185/// What the LLM is asked to write. 186#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 187pub enum Want { 188 /// The options of a pick. 189 Options, 190 /// The levels of a how-much. 191 Scale, 192 /// Each of several questions, as whatever it is. 193 Split, 194} 195 196/// Something a rule does that costs a call and teaches a fact. 197#[derive(Clone, Copy, Debug, PartialEq, Eq)] 198pub enum Effect { 199 /// Ask the LLM to write Jev's questions. Teaches [`Fact::Drafted`]. 200 /// Every drafting rule that holds is served by the one call 201 /// ([`Network::wants`]). 202 Draft(Want), 203 /// Ask Jev the questions the LLM wrote. Teaches [`Fact::Judged`]. 204 Judge, 205} 206 207impl Effect { 208 pub fn teaches(self) -> Fact { 209 match self { 210 Effect::Draft(_) => Fact::Drafted, 211 Effect::Judge => Fact::Judged, 212 } 213 } 214} 215 216/// How a rule ends a query outright. 217#[derive(Clone, Copy, Debug, PartialEq, Eq)] 218pub enum End { 219 /// Jev cannot judge the input. 220 NotAQuestion, 221} 222 223/// Something that is so when a rule holds, and that decides no step: the 224/// Worker reads it once the query has ended. 225#[derive(Clone, Copy, Debug, PartialEq, Eq)] 226pub enum Note { 227 /// The question may go on the public feed. 228 List, 229 /// This fact is an answer to show: Jev's own (`Yes`, `Degree`, `Chance`), 230 /// or its answers to what the LLM wrote (`Judged`). 231 Show(Fact), 232} 233 234#[derive(Clone, Copy, Debug, PartialEq, Eq)] 235pub enum Then { 236 Do(Effect), 237 End(End), 238 Note(Note), 239} 240 241pub struct Rule { 242 pub name: &'static str, 243 pub when: &'static [Test], 244 pub then: Then, 245} 246 247const YES: Value = Value::Bool(true); 248const NO: Value = Value::Bool(false); 249const ONE: [Test; 2] = [Test::Is(Fact::Answerable, YES), Test::Is(Fact::Several, NO)]; 250 251/// lmjtfy's rules. Order is priority: of the rules that hold, the first with 252/// something left to do decides. When Jev is split between two readings, 253/// the rules of both hold, and both are answered. 254pub const RULES: &[Rule] = &[ 255 Rule { name: "refuse", when: &[Test::Is(Fact::Answerable, NO)], then: Then::End(End::NotAQuestion) }, 256 Rule { 257 name: "answer yes or no", 258 when: &[ONE[0], ONE[1], Test::Is(Fact::Reads(Kind::Noul), YES), Test::Known(Fact::Yes)], 259 then: Then::Note(Note::Show(Fact::Yes)), 260 }, 261 Rule { 262 name: "grade it", 263 when: &[ 264 ONE[0], 265 ONE[1], 266 Test::Is(Fact::Reads(Kind::Score), YES), 267 Test::Is(Fact::Scale, NO), 268 Test::Known(Fact::Degree), 269 ], 270 then: Then::Note(Note::Show(Fact::Degree)), 271 }, 272 Rule { 273 name: "give the odds", 274 when: &[ONE[0], ONE[1], Test::Is(Fact::Reads(Kind::Chance), YES), Test::Known(Fact::Chance)], 275 then: Then::Note(Note::Show(Fact::Chance)), 276 }, 277 Rule { 278 name: "draft a scale", 279 when: &[ONE[0], ONE[1], Test::Is(Fact::Reads(Kind::Score), YES), Test::Is(Fact::Scale, YES)], 280 then: Then::Do(Effect::Draft(Want::Scale)), 281 }, 282 Rule { 283 name: "draft options", 284 when: &[ONE[0], ONE[1], Test::Is(Fact::Reads(Kind::Choice), YES)], 285 then: Then::Do(Effect::Draft(Want::Options)), 286 }, 287 Rule { 288 name: "split it up", 289 when: &[Test::Is(Fact::Answerable, YES), Test::Is(Fact::Several, YES)], 290 then: Then::Do(Effect::Draft(Want::Split)), 291 }, 292 Rule { name: "judge the drafts", when: &[Test::Is(Fact::Drafted, YES)], then: Then::Do(Effect::Judge) }, 293 Rule { 294 name: "show the answers", 295 when: &[Test::Is(Fact::Judged, YES)], 296 then: Then::Note(Note::Show(Fact::Judged)), 297 }, 298 Rule { 299 name: "list it", 300 when: &[Test::Is(Fact::Answerable, YES), Test::Is(Fact::Fit, YES)], 301 then: Then::Note(Note::List), 302 }, 303]; 304 305/// What is known about a query so far. 306#[derive(Clone, Debug, Default, PartialEq)] 307pub struct Known(BTreeMap<Fact, Value>); 308 309impl Known { 310 pub fn learn(&mut self, fact: Fact, value: Value) { 311 self.0.insert(fact, value); 312 } 313 314 pub fn get(&self, fact: Fact) -> Option<Value> { 315 self.0.get(&fact).copied() 316 } 317 318 pub fn forget(&mut self, fact: Fact) { 319 self.0.remove(&fact); 320 } 321} 322 323/// Where a node stands, given what is known. 324#[derive(Clone, Copy, Debug, PartialEq, Eq)] 325pub enum State { 326 /// Every test up to here holds. 327 Holds, 328 /// A test up to here is known to fail. Nothing downstream can fire. 329 Fails, 330 /// No test has failed, and at least one is on a fact not yet known. 331 Waits, 332} 333 334impl State { 335 fn and(self, other: State) -> State { 336 match (self, other) { 337 (State::Fails, _) | (_, State::Fails) => State::Fails, 338 (State::Holds, State::Holds) => State::Holds, 339 _ => State::Waits, 340 } 341 } 342} 343 344/// A join node: everything `left` requires, and one more test. `left` is 345/// another join, or `None` when this is a rule's first test. 346#[derive(Clone, Copy, Debug, PartialEq, Eq)] 347pub struct Join { 348 pub left: Option<usize>, 349 /// Index into [`Network::alphas`]. 350 pub alpha: usize, 351 /// How many tests are joined here: 1 for a rule's first. 352 pub depth: usize, 353} 354 355/// A rule's place in the network: the join that holds when the rule does. 356#[derive(Clone, Copy, Debug)] 357pub struct Terminal { 358 pub name: &'static str, 359 pub join: usize, 360 pub then: Then, 361} 362 363/// The rules as a Rete network. 364#[derive(Clone, Debug)] 365pub struct Network { 366 /// One per distinct test, however many rules use it. 367 pub alphas: Vec<Test>, 368 /// Shared by every rule that begins with the same tests. 369 pub joins: Vec<Join>, 370 pub terminals: Vec<Terminal>, 371} 372 373/// What to do next about a query. 374#[derive(Clone, Debug, PartialEq, Eq)] 375pub enum Next { 376 /// Ask Jev for all of these, in one request. 377 Ask(Vec<Fact>), 378 Do(Effect), 379 /// A rule ends the query outright. 380 End(End), 381 /// No rule has anything left to do and nothing more can be learned: 382 /// the query is over, and what to show is in the notes. 383 Done, 384} 385 386impl Network { 387 pub fn compile(rules: &[Rule]) -> Self { 388 let mut network = Network { alphas: Vec::new(), joins: Vec::new(), terminals: Vec::new() }; 389 for rule in rules { 390 let mut left = None; 391 for (depth, test) in rule.when.iter().enumerate() { 392 let alpha = network.alphas.iter().position(|known| known == test).unwrap_or_else(|| { 393 network.alphas.push(*test); 394 network.alphas.len() - 1 395 }); 396 let join = Join { left, alpha, depth: depth + 1 }; 397 left = Some(network.joins.iter().position(|known| *known == join).unwrap_or_else(|| { 398 network.joins.push(join); 399 network.joins.len() - 1 400 })); 401 } 402 let join = left.expect("a rule has at least one test"); 403 network.terminals.push(Terminal { name: rule.name, join, then: rule.then }); 404 } 405 network 406 } 407 408 /// lmjtfy's own network. 409 pub fn lmjtfy() -> Self { 410 Network::compile(RULES) 411 } 412 413 pub fn alpha(&self, alpha: usize, known: &Known) -> State { 414 let test = self.alphas[alpha]; 415 match (test, known.get(test.fact())) { 416 (_, None) => State::Waits, 417 (Test::Known(_), Some(_)) => State::Holds, 418 (Test::Is(_, wanted), Some(value)) if wanted == value => State::Holds, 419 (Test::Is(..), Some(_)) => State::Fails, 420 } 421 } 422 423 pub fn join(&self, join: usize, known: &Known) -> State { 424 let Join { left, alpha, .. } = self.joins[join]; 425 let here = self.alpha(alpha, known); 426 left.map_or(here, |left| self.join(left, known).and(here)) 427 } 428 429 /// The facts a join's tests are on, first test first. 430 fn facts(&self, join: usize) -> Vec<Fact> { 431 let Join { left, alpha, .. } = self.joins[join]; 432 let mut facts = left.map_or_else(Vec::new, |left| self.facts(left)); 433 facts.push(self.alphas[alpha].fact()); 434 facts 435 } 436 437 /// What to do, given what is known, and the rule that says so (its index 438 /// in [`Network::terminals`]), when one does. 439 /// 440 /// A rule that holds decides, in rule order; an effect whose fact is 441 /// already known has been done and is passed over. If no rule decides, 442 /// every Jev fact that a rule not yet failed is waiting on is wanted, 443 /// and they are asked for together. 444 pub fn decide(&self, known: &Known) -> (Option<usize>, Next) { 445 for (index, terminal) in self.terminals.iter().enumerate() { 446 if self.join(terminal.join, known) != State::Holds { 447 continue; 448 } 449 match terminal.then { 450 Then::End(end) => return (Some(index), Next::End(end)), 451 Then::Do(effect) if known.get(effect.teaches()).is_none() => return (Some(index), Next::Do(effect)), 452 Then::Do(_) | Then::Note(_) => {} 453 } 454 } 455 let mut wanted: Vec<Fact> = self 456 .terminals 457 .iter() 458 .filter(|terminal| self.join(terminal.join, known) == State::Waits) 459 .flat_map(|terminal| self.facts(terminal.join)) 460 .filter(|fact| fact.source() == Source::Jev && known.get(*fact).is_none()) 461 .collect(); 462 wanted.sort(); 463 wanted.dedup(); 464 (None, if wanted.is_empty() { Next::Done } else { Next::Ask(wanted) }) 465 } 466 467 /// What to do, given what is known. 468 pub fn next(&self, known: &Known) -> Next { 469 self.decide(known).1 470 } 471 472 fn held<'a>(&'a self, known: &'a Known) -> impl Iterator<Item = &'a Terminal> { 473 self.terminals.iter().filter(move |terminal| self.join(terminal.join, known) == State::Holds) 474 } 475 476 /// The names of the rules that hold, in rule order. 477 pub fn holding(&self, known: &Known) -> Vec<&'static str> { 478 self.held(known).map(|terminal| terminal.name).collect() 479 } 480 481 /// Whether a rule that holds says `note`. 482 pub fn notes(&self, known: &Known, note: Note) -> bool { 483 self.held(known).any(|terminal| terminal.then == Then::Note(note)) 484 } 485 486 /// The answers to show, in rule order. 487 pub fn shows(&self, known: &Known) -> Vec<Fact> { 488 self.held(known) 489 .filter_map(|terminal| match terminal.then { 490 Then::Note(Note::Show(fact)) => Some(fact), 491 _ => None, 492 }) 493 .collect() 494 } 495 496 /// Everything the LLM is to write, for every drafting rule that holds: 497 /// one call serves them all. 498 pub fn wants(&self, known: &Known) -> Vec<Want> { 499 let mut wants: Vec<Want> = self 500 .held(known) 501 .filter_map(|terminal| match terminal.then { 502 Then::Do(Effect::Draft(want)) => Some(want), 503 _ => None, 504 }) 505 .collect(); 506 wants.sort(); 507 wants.dedup(); 508 wants 509 } 510 511 /// The joins and alphas that a rule that holds stands on: what, of 512 /// everything known, turned out to matter. 513 pub fn used(&self, known: &Known) -> (Vec<bool>, Vec<bool>) { 514 let mut joins = vec![false; self.joins.len()]; 515 let mut alphas = vec![false; self.alphas.len()]; 516 for terminal in &self.terminals { 517 if self.join(terminal.join, known) != State::Holds { 518 continue; 519 } 520 let mut at = Some(terminal.join); 521 while let Some(join) = at { 522 joins[join] = true; 523 alphas[self.joins[join].alpha] = true; 524 at = self.joins[join].left; 525 } 526 } 527 (joins, alphas) 528 } 529} 530 531#[cfg(test)] 532mod tests;