Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ Learning here is a capped nudge to a few weights per trait, credited to the leve
trait also pushes the others' levels); rule immunity is architectural, not trained. The proofs and counts cover the stance; whether
a model writes in it is measured per model.

### Drives: which goal gets the next unit of effort
### Drives: which goal gets the next unit of effort (0.8.0, unreleased)

A `drives:` block gives a persona 2-7 goals, each with wanting, afterglow and an expectation. A win moves the individual by its
prediction error (the win's size minus what it expected), so repeated equal wins move it less and less; a new output, `pursue`,
Expand All @@ -518,7 +518,7 @@ A persona without the block gives the same documents as before, byte for byte. M
cues, rewards and praises fun on this persona, 100 individuals x 10,000 turns: 0 habit breaks, all 6,743 must-do turns pursued
the due chore, and the safety floor (0.1) held on every turn its habits allowed (least odds 0.141), while fun took 85 % of turns.

### Watch it live: `probbit monitor`
### Watch it live: `probbit monitor` (0.8.0, unreleased)

`probbit monitor pip.strand` replays a strand with the rules of `live verify` and draws the individual's inner state as live bars:
each trait's levels with their odds, the moods, the event's inputs, the habits that bound, the learned weights and the line, in the
Expand Down
4 changes: 3 additions & 1 deletion docs/persona.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,9 @@ a cue, a deadline on each `--hours` value. `prove` reads the drives as boxes: th
range (wanting and afterglow 0 to their caps, the prediction error 0 to `pe_cap` each way), the goal conditions both ways, and,
when `pursue` is in the rule's component, its drive terms (wanting, afterglow, deadline urgency, the floor lift at its most over
that box) widen the bound. A goal floor is reported held by construction, with the habits that can exclude it (`floors` in the
JSON). On the drives fixture of the tests: "a chore due within 24 h -> pursue chores" is held by construction (its habit);
JSON). If a checked individual's goal-field bounds (including couplings) leave the normal exponential range, its floors are
reported `unknown`: the log-space lift still runs, but the structural proof does not certify floating-point rounding over
every turn in that range. On the drives fixture of the tests: "a chore due within 24 h -> pursue chores" is held by construction (its habit);
"`since_pursued.chores` >= 12 -> pursue chores" is unknown, rightly, since `starve_safety` (priority 1) wins over `starve_chores`
(priority 0) on a turn where both are due.

Expand Down
7 changes: 4 additions & 3 deletions probbit-cli/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ pub fn prove_doc(p: &Persona, props: &[Prop], seeds: &[u64], out: &[Verdict]) ->
Json::Obj(f) }).collect();
let mut v = vec![("probbit_persona_prove".into(), n(1.0)), ("persona".into(), Json::Obj(vec![("name".into(), st(&p.name)), ("version".into(), st(&p.version)), ("digest".into(), st(&p.digest))])),
("seeds".into(), Json::Arr(seeds.iter().map(|x| n(*x as f64)).collect())), ("properties".into(), Json::Arr(props_j))];
if let Some(f) = persona::floor_verdicts(p) { v.push(("floors".into(), Json::Arr(f))); } // drives (§2.9): goal floors
if let Some(f) = persona::floor_verdicts(p, seeds) { v.push(("floors".into(), Json::Arr(f))); } // drives (§2.9): goal floors
Json::Obj(v)
}
/// The human report of `prove`
Expand All @@ -156,10 +156,11 @@ pub fn prove_human(p: &Persona, path: &str, props: &[Prop], seeds: &[u64], out:
o.push(format!(" seed {sd}: {why}; cell {}", persona::canon(cell)));
o.push(format!(" search it: probbit persona fuzz {} --seeds {} --never {}", word(path), ranges(seeds), word(&flow(&pr.rule)))); } }
}
for f in persona::floor_verdicts(p).unwrap_or_default() { let g = |k: &str| f.get(k).cloned().unwrap_or(Json::Null);
for f in persona::floor_verdicts(p, seeds).unwrap_or_default() { let g = |k: &str| f.get(k).cloned().unwrap_or(Json::Null);
let by: Vec<String> = g("excluded_by").as_arr().unwrap_or(&[]).iter().filter_map(|x| x.as_str().map(str::to_string)).collect();
o.push(String::new()); o.push(format!("floor {} odds >= {}", g("goal").as_str().unwrap_or(""), persona::canon(&g("floor"))));
o.push(format!(" held by construction (the odds lift; on turns whose habits allow it{})", if by.is_empty() { String::new() } else { format!("; can be excluded by {}", by.join(", ")) })); }
if g("verdict").as_str() == Some("unknown") { o.push(format!(" unknown {}", g("reason").as_str().unwrap_or("numeric floor guarantee not certified"))); }
else { o.push(format!(" held by construction (the odds lift; on turns whose habits allow it{})", if by.is_empty() { String::new() } else { format!("; can be excluded by {}", by.join(", ")) })); } }
o.join("\n")
}

Expand Down
2 changes: 1 addition & 1 deletion probbit-cli/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ fn tools() -> Json {
for (k, x) in v.iter_mut() { if k == "description" { *x = jstr("A probbit-ir v1 program (docs/probbit-ir-json.md): variables over named values, scores, pairs, caps and the v2 constructs, plus optional flags."); }
if k == "properties" { if let Json::Obj(pr) = x { pr.push(("flags".into(), p(&run_flags))); } } } }
let tool = |name: &str, title: &str, desc: &str, schema: Json| obj(vec![("name", jstr(name)), ("title", jstr(title)), ("description", jstr(desc)), ("inputSchema", schema),
("annotations", obj(vec![("readOnlyHint", Json::Bool(true)), ("openWorldHint", Json::Bool(false))]))]);
("annotations", obj(vec![("readOnlyHint", Json::Bool(name != "probbit_live_event")), ("destructiveHint", Json::Bool(false)), ("openWorldHint", Json::Bool(false))]))]);
Json::Arr(vec![
tool("probbit_decide", "Route tasks to workers", "probbit decide: assign every task to one worker under hard rules (allowed sets, quotas, clamps) with workflow affinity. Returns the same JSON as the CLI: a plan that obeys every rule, odds per task, verdict exact | diagnostics_passed | partial (act on `released`, escalate `escalated`) | refused | infeasible, gate diagnostics and telemetry.", p(&router)),
tool("probbit_run", "Run a probbit-ir program", "probbit run: a general constrained categorical program (variables, values, scores, pairs, caps, all_different, implies, tables, precedes, linear). Same answer shape as probbit_decide with `marginals` instead of `odds`.", ir),
Expand Down
8 changes: 5 additions & 3 deletions probbit-cli/src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ fn short(d: &str) -> &str { let h = d.strip_prefix("sha256:").unwrap_or(d); &h[.
/// One frame: the lines to draw (without line endings). `note`: a warning to show (a truncated or rotated strand, an
/// incomplete last line).
fn frame(w: Option<&Watch>, bad_head: Option<&(usize, String)>, path: &str, note: Option<&str>, s: &Style) -> Vec<String> {
// Strip data's C0, DEL and C1 controls per plain line; the caller adds the frame's line endings.
let finish = |mut lines: Vec<String>| { if s.ascii { for line in &mut lines { line.retain(|c| !c.is_control()); } } lines };
let dot = s.paint(DIM, s.g("·", "|"));
let lab = |t: &str| s.bold(MAGENTA, &format!("{t:<8}"));
let mut out = vec![];
Expand All @@ -171,7 +173,7 @@ fn frame(w: Option<&Watch>, bad_head: Option<&(usize, String)>, path: &str, note
out.push(format!("{} {dot} {badge}", s.bold(CYAN, "probbit monitor")));
if let Some(t) = note { out.push(s.paint(YELLOW, t)); }
out.push(footer(path, "", s));
return out;
return finish(out);
};
let m = &w.meta;
let f = w.last.as_ref();
Expand All @@ -182,7 +184,7 @@ fn frame(w: Option<&Watch>, bad_head: Option<&(usize, String)>, path: &str, note
let Some(f) = f else {
out.push(s.paint(GREY, "no events yet: the strand holds its header and no event lines"));
out.push(footer(path, &m.engine, s));
return out;
return finish(out);
};
let doc = &f.doc;
let idw = m.traits.iter().chain(&m.moods).map(|v| v.id.chars().count()).chain(m.learning.iter().flat_map(|l| l.0.iter().map(|t| t.chars().count()))).max().unwrap_or(4).clamp(4, 14);
Expand Down Expand Up @@ -253,7 +255,7 @@ fn frame(w: Option<&Watch>, bad_head: Option<&(usize, String)>, path: &str, note
if let Some(y) = doc.get("why").and_then(Json::as_str).filter(|y| !y.is_empty()) { out.push(format!("{}{}", lab(""), s.paint(GREY, &format!("why: {y}")))); }
// 8. the footer
out.push(footer(path, &m.engine, s));
out
finish(out)
}
/// The footer: the strand, the engine that wrote it and the command that replays it; the demo's week (path "") is in memory, so
/// the command that writes the same week instead
Expand Down
Loading
Loading