- 1
//! What the runtime concluded the request is, and how it concluded it. - 2
- 3
use std::collections::BTreeSet; - 4
- 5
use serde::{Deserialize, Serialize}; - 6
- 7
use crate::axes::{Act, Attendance, Clarity, Evidence, Horizon, Modality, Stakes}; - 8
use crate::signals::Signal; - 9
- 10
/// Which resolver tier produced a reading. - 11
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] - 12
#[serde(rename_all = "kebab-case")] - 13
pub enum Tier { - 14
/// Stated outright by a caller. Free and absolute. - 15
Declared, - 16
/// Deterministic signal extraction. Free and reproducible. - 17
Signals, - 18
/// A local model classified it. Cheap and private, not reproducible. - 19
LocalModel, - 20
/// A metered provider call classified it. Not reproducible. - 21
CloudModel, - 22
/// Nothing reached threshold; the general engagement applies. This is - 23
/// vak's pre-kernel behaviour and is never a failure. - 24
General, - 25
} - 26
- 27
impl Tier { - 28
pub fn as_str(self) -> &'static str { - 29
match self { - 30
Tier::Declared => "declared", - 31
Tier::Signals => "signals", - 32
Tier::LocalModel => "local-model", - 33
Tier::CloudModel => "cloud-model", - 34
Tier::General => "general", - 35
} - 36
} - 37
- 38
/// Whether replaying the recorded inputs is guaranteed to reproduce this - 39
/// decision. Model tiers say no, and say so rather than pretending — - 40
/// the same honesty the routing ledger applies to `Settlement::Unknown`. - 41
pub fn is_reproducible(self) -> bool { - 42
matches!(self, Tier::Declared | Tier::Signals | Tier::General) - 43
} - 44
} - 45
- 46
/// How a reading was reached, in enough detail to reconstruct or dispute it. - 47
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] - 48
pub struct Provenance { - 49
pub tier: Tier, - 50
/// Bumped whenever the lexicon or scoring changes, so a ledger entry can - 51
/// be interpreted against the rules that actually produced it. - 52
pub resolver_version: u32, - 53
#[serde(default)] - 54
pub signals: Vec<Signal>, - 55
/// Model that classified, when a model tier ran. - 56
#[serde(default, skip_serializing_if = "Option::is_none")] - 57
pub model: Option<String>, - 58
/// Digest of the classification prompt, so a later change to it is - 59
/// visible rather than silent. - 60
#[serde(default, skip_serializing_if = "Option::is_none")] - 61
pub prompt_digest: Option<String>, - 62
/// Mirrors [`Tier::is_reproducible`], recorded explicitly so a reader of - 63
/// old JSONL need not know today's tier semantics. - 64
pub reproducible: bool, - 65
/// Why a paid tier was or was not reached. Present whenever escalation - 66
/// was considered, so cost is explainable after the fact. - 67
#[serde(default, skip_serializing_if = "Option::is_none")] - 68
pub escalation_note: Option<String>, - 69
} - 70
- 71
impl Provenance { - 72
pub fn new(tier: Tier, resolver_version: u32, signals: Vec<Signal>) -> Self { - 73
Provenance { - 74
tier, - 75
resolver_version, - 76
signals, - 77
model: None, - 78
prompt_digest: None, - 79
reproducible: tier.is_reproducible(), - 80
escalation_note: None, - 81
} - 82
} - 83
} - 84
- 85
/// Per-axis confidence. - 86
/// - 87
/// A single scalar conflates independent questions. Capability slicing depends - 88
/// only on `act`; whether to open a commitment depends only on `horizon`; the - 89
/// approval floor depends only on `stakes`. Gating all three on the weakest - 90
/// axis meant an unsignalled horizon — which is most requests, since few say - 91
/// how long they will take — held back tool slicing that the act reading was - 92
/// perfectly confident about. - 93
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)] - 94
pub struct Confidences { - 95
pub act: f64, - 96
pub horizon: f64, - 97
pub stakes: f64, - 98
pub evidence: f64, - 99
} - 100
- 101
impl Confidences { - 102
/// The weakest axis. Used for the all-or-nothing general fallback, where - 103
/// the question really is "did we understand this request at all". - 104
pub fn overall(&self) -> f64 { - 105
self.act - 106
.min(self.horizon) - 107
.min(self.stakes) - 108
.min(self.evidence) - 109
} - 110
} - 111
- 112
/// What the request is, on every axis. - 113
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] - 114
pub struct Reading { - 115
pub act: Act, - 116
pub horizon: Horizon, - 117
pub stakes: Stakes, - 118
pub evidence: Evidence, - 119
pub clarity: Clarity, - 120
#[serde(default)] - 121
pub input_modalities: BTreeSet<Modality>, - 122
#[serde(default)] - 123
pub output_modalities: BTreeSet<Modality>, - 124
pub attendance: Attendance, - 125
/// Acts that scored close enough to the winner to be part of the same - 126
/// request rather than rivals to it. The capability slice covers all of - 127
/// them; everything else (output shape, stop profile) follows `act`. - 128
#[serde(default)] - 129
pub alternate_acts: BTreeSet<Act>, - 130
/// Capability domains the request needs (the shared vocabulary in - 131
/// [`crate::engage::DOMAIN_VOCABULARY`]) plus a few environment tags. - 132
/// They decide which admitted tools are loaded, and `live-data` asks the - 133
/// agent loop to observe a current value this turn before answering. - 134
/// They never change what the runtime is *allowed* to do: admission and - 135
/// permission ignore them. - 136
#[serde(default)] - 137
pub domains: BTreeSet<String>, - 138
/// [0,1]. Below the configured floor the orienting engagement applies. - 139
/// Equal to `axis_confidence.overall()`. - 140
pub confidence: f64, - 141
/// Per-axis confidence, so each projection can gate on the axis it - 142
/// actually depends on. - 143
#[serde(default)] - 144
pub axis_confidence: Confidences, - 145
} - 146
- 147
impl Default for Reading { - 148
fn default() -> Self { - 149
Reading::general() - 150
} - 151
} - 152
- 153
impl Reading { - 154
/// The reading that assumes nothing. - 155
/// - 156
/// Deliberately the *widest* capability posture paired with the *ordinary* - 157
/// approval posture, because uncertainty must never silently narrow what - 158
/// the agent can do — a wrongly-sliced turn fails in a way the user - 159
/// experiences as the agent being broken. - 160
pub fn general() -> Self { - 161
Reading { - 162
act: Act::Answer, - 163
horizon: Horizon::Turn, - 164
stakes: Stakes::Reversible, - 165
evidence: Evidence::None, - 166
clarity: Clarity::Clear, - 167
input_modalities: BTreeSet::from([Modality::Text]), - 168
output_modalities: BTreeSet::from([Modality::Text]), - 169
attendance: Attendance::Interactive, - 170
alternate_acts: BTreeSet::new(), - 171
domains: BTreeSet::new(), - 172
confidence: 0.0, - 173
axis_confidence: Confidences::default(), - 174
} - 175
} - 176
- 177
/// Whether this reading is confident enough to narrow capability. - 178
/// - 179
/// Gated on the `act` axis alone, because that is the only axis the - 180
/// capability slice is derived from. Two thresholds rather than one: a - 181
/// provisional reading may still raise an approval floor (getting that - 182
/// wrong is merely annoying) but may not remove a tool (getting that wrong - 183
/// breaks the task). - 184
pub fn may_slice_capabilities(&self, floor: f64) -> bool { - 185
self.axis_confidence.act >= floor - 186
} - 187
- 188
/// Whether this reading is confident enough to promote work to a durable - 189
/// commitment. Gated on `horizon` alone. - 190
pub fn may_open_commitment(&self, floor: f64) -> bool { - 191
self.axis_confidence.horizon >= floor - 192
} - 193
- 194
/// Whether the reading is usable at all. - 195
pub fn is_actionable(&self, floor: f64) -> bool { - 196
self.confidence >= floor - 197
} - 198
- 199
/// One-line summary for logs, chips, and explain views. - 200
pub fn summary(&self) -> String { - 201
format!( - 202
"{}/{}/{}/{} ({}, {:.0}% confident)", - 203
self.act.as_str(), - 204
self.horizon.as_str(), - 205
self.stakes.as_str(), - 206
self.evidence.as_str(), - 207
self.attendance.as_str(), - 208
self.confidence * 100.0 - 209
) - 210
} - 211
- 212
/// Every act this reading covers: the primary plus any close contenders. - 213
pub fn acts(&self) -> BTreeSet<Act> { - 214
let mut acts = self.alternate_acts.clone(); - 215
acts.insert(self.act); - 216
acts - 217
} - 218
- 219
/// Modalities a serving model must support to handle this turn at all. - 220
/// - 221
/// Plain text and structured data ride the ordinary text interface; the - 222
/// rest constrain which ladder legs may serve, and an unsatisfiable - 223
/// requirement is a typed failure rather than a silent degradation. - 224
pub fn required_modalities(&self) -> BTreeSet<Modality> { - 225
self.input_modalities - 226
.union(&self.output_modalities) - 227
.copied() - 228
.filter(|m| m.needs_declared_support()) - 229
.collect() - 230
} - 231
} - 232
- 233
/// A reading paired with everything the runtime derived from it. - 234
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] - 235
pub struct Intent { - 236
/// The composite reading: the most consequential strand, widened by the - 237
/// others (every act they cover, the highest level on each ordered axis, - 238
/// the union of modalities and domains). Everything that wants one - 239
/// answer for the turn — the misread ledger, the session index, a - 240
/// commitment's spec — reads this; everything that cares which part of - 241
/// the request said what reads `strands`. - 242
pub reading: Reading, - 243
/// The parts of the request, in textual order. A resolved request has at - 244
/// least one; a request that is one thing has one strand whose reading - 245
/// equals `reading`. Only a disabled kernel's [`Intent::general`] has - 246
/// none. - 247
#[serde(default)] - 248
pub strands: Vec<crate::strand::Strand>, - 249
/// `Engagement::compose` over the strands. - 250
pub engagement: crate::Engagement, - 251
pub provenance: Provenance, - 252
} - 253
- 254
impl Intent { - 255
/// The intent that changes nothing — vak's behaviour before this kernel, - 256
/// and what a disabled kernel produces. It has no strands: nothing was - 257
/// read, so nothing can open a thread or a commitment. - 258
pub fn general(resolver_version: u32) -> Self { - 259
Intent { - 260
reading: Reading::general(), - 261
strands: Vec::new(), - 262
engagement: crate::Engagement::general(), - 263
provenance: Provenance::new(Tier::General, resolver_version, Vec::new()), - 264
} - 265
} - 266
- 267
/// The block of text this intent contributes to the model's context, if - 268
/// any. - 269
/// - 270
/// Model-visible means logged (`AGENTS.md` invariant 1), so whatever this - 271
/// returns is written into the session ledger verbatim as part of the - 272
/// intent entry rather than being regenerated at replay time. - 273
pub fn model_visible(&self) -> Option<String> { - 274
self.engagement.posture.note.clone() - 275
} - 276
} - 277
- 278
#[cfg(test)] - 279
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 280
mod tests { - 281
use super::*; - 282
- 283
#[test] - 284
fn model_tiers_are_never_marked_reproducible() { - 285
for tier in [Tier::LocalModel, Tier::CloudModel] { - 286
assert!(!tier.is_reproducible()); - 287
assert!(!Provenance::new(tier, 1, Vec::new()).reproducible); - 288
} - 289
for tier in [Tier::Declared, Tier::Signals, Tier::General] { - 290
assert!(tier.is_reproducible()); - 291
assert!(Provenance::new(tier, 1, Vec::new()).reproducible); - 292
} - 293
} - 294
- 295
#[test] - 296
fn the_general_reading_restricts_nothing() { - 297
let intent = Intent::general(1); - 298
assert!( - 299
intent - 300
.engagement - 301
.limits - 302
.is_at_most(&crate::Limits::unrestricted()) - 303
); - 304
assert_eq!(intent.engagement.limits, crate::Limits::unrestricted()); - 305
} - 306
- 307
#[test] - 308
fn only_modalities_needing_support_constrain_the_ladder() { - 309
let mut reading = Reading::general(); - 310
reading.input_modalities.insert(Modality::Data); - 311
// Text and data both ride the ordinary interface. - 312
assert!(reading.required_modalities().is_empty()); - 313
- 314
reading.input_modalities.insert(Modality::Image); - 315
assert_eq!( - 316
reading.required_modalities(), - 317
BTreeSet::from([Modality::Image]) - 318
); - 319
} - 320
} - 321
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.