- 1
//! Intent entries in the ledger and the projection. - 2
//! - 3
//! The property under test is `AGENTS.md` invariant 1 applied to intent: - 4
//! whatever the engagement tells the model must be reconstructable from the - 5
//! JSONL, and reconstructing it must produce the same prompt — not a fresh - 6
//! derivation that today's rules happen to agree with. - 7
- 8
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 9
- 10
use std::path::PathBuf; - 11
- 12
use tempfile::tempdir; - 13
- 14
use vak_llm::Message; - 15
use vak_session::types::{FrozenContract, IntentRecord, MessageRecord, SessionHeader}; - 16
use vak_session::{SessionLog, SessionPath}; - 17
- 18
fn header() -> SessionHeader { - 19
SessionHeader { - 20
agent: None, - 21
session_id: "s-intent".into(), - 22
created_at: chrono::Utc::now(), - 23
cwd: PathBuf::from("/tmp/proj"), - 24
parent_session_id: None, - 25
contract_id: None, - 26
work_item_id: None, - 27
conversation: None, - 28
contract: FrozenContract { - 29
app_version: "2.0.1".into(), - 30
provider: "anthropic".into(), - 31
model: "test-model".into(), - 32
route_ladder: Vec::new(), - 33
route_objective: String::new(), - 34
route_annotations: Vec::new(), - 35
system_prompt: "system".into(), - 36
permission_mode: "workspace-write".into(), - 37
capabilities: Vec::new(), - 38
prompt_layers: Vec::new(), - 39
}, - 40
} - 41
} - 42
- 43
fn record(note: Option<&str>) -> IntentRecord { - 44
IntentRecord { - 45
reading: vak_intent::Reading::general(), - 46
engagement: vak_intent::Engagement::general(), - 47
provenance: vak_intent::Provenance::new( - 48
vak_intent::Tier::Signals, - 49
vak_intent::RESOLVER_VERSION, - 50
Vec::new(), - 51
), - 52
outcome: None, - 53
model_visible: note.map(str::to_string), - 54
commitment_id: None, - 55
strands: Vec::new(), - 56
strand_commitments: Default::default(), - 57
} - 58
} - 59
- 60
fn open(dir: &std::path::Path) -> SessionLog { - 61
SessionLog::create( - 62
vak_session::SessionPath::new_session_file(dir, &PathBuf::from("/tmp/proj"), "s-intent"), - 63
header(), - 64
) - 65
.unwrap() - 66
} - 67
- 68
fn user(text: &str) -> MessageRecord { - 69
MessageRecord { - 70
message: Message::user_text(text), - 71
meta: None, - 72
} - 73
} - 74
- 75
/// The intent note reaches the model through the request tail - 76
/// (docs/design/68-context-engine.md §6/§10), not spliced into the - 77
/// projection: `derive_messages()` carries only the conversation, and - 78
/// `tail_sections()` carries the note the assembler renders alongside it. - 79
#[test] - 80
fn an_intent_note_reaches_the_model_through_the_tail_not_the_projection() { - 81
let dir = tempdir().unwrap(); - 82
let path = SessionPath::new_session_file(dir.path(), &PathBuf::from("/tmp/proj"), "s-intent"); - 83
let mut log = SessionLog::create(path.clone(), header()).unwrap(); - 84
log.append_intent(record(Some("Cite your sources."))) - 85
.unwrap(); - 86
log.append_message(user("what changed in the parser?")) - 87
.unwrap(); - 88
- 89
let messages = log.derive_messages(); - 90
let texts: Vec<String> = messages.iter().map(|m| m.text_content()).collect(); - 91
assert!( - 92
!texts.iter().any(|t| t.contains("Cite your sources.")), - 93
"the intent note must not be spliced into the projection: {texts:?}" - 94
); - 95
assert!( - 96
texts - 97
.iter() - 98
.any(|t| t.contains("what changed in the parser?")), - 99
"user turn present" - 100
); - 101
- 102
let tail = log.tail_sections(None); - 103
let intent = tail.intent.expect("intent tail section present"); - 104
assert!(intent.contains("Cite your sources.")); - 105
assert!(intent.starts_with("<intent>")); - 106
} - 107
- 108
/// A long note in a non-Latin script reaches the tail whole. The tail used - 109
/// to be cut at a byte offset, which panics inside a multi-byte character - 110
/// and silently drops the rest (AGENTS.md invariants 3 and 36). - 111
#[test] - 112
fn a_long_non_ascii_intent_note_reaches_the_tail_whole() { - 113
let dir = tempdir().unwrap(); - 114
let mut log = open(dir.path()); - 115
let note = "कृपया हर भाग का उत्तर दें। ".repeat(300); - 116
assert!(note.len() > 8_000, "longer than any old cut"); - 117
log.append_intent(record(Some(¬e))).unwrap(); - 118
let intent = log.tail_sections(None).intent.expect("intent tail section"); - 119
assert!(intent.contains(note.trim_end())); - 120
} - 121
- 122
/// `ContextProfile::Working` / `Full`: the workspace delta the host recorded - 123
/// for this turn reaches the model through the tail, from the ledger bytes, - 124
/// and only when the turn's reading asked for it. - 125
#[test] - 126
fn a_workspace_delta_reaches_the_tail_only_for_a_working_reading() { - 127
let dir = tempdir().unwrap(); - 128
let mut log = open(dir.path()); - 129
let delta = |id: &str| vak_session::ActivityRecord { - 130
activity_id: id.into(), - 131
turn: None, - 132
kind: vak_session::ActivityKind::Diagnostic, - 133
status: vak_session::ActivityStatus::Succeeded, - 134
label: "Workspace changes since the session began".into(), - 135
detail: Some("M src/parser.rs (+12 -3)".into()), - 136
data: std::collections::BTreeMap::from([( - 137
"section".to_string(), - 138
SessionLog::WORKSPACE_DELTA_SECTION.to_string(), - 139
)]), - 140
}; - 141
- 142
// A recall reading: the delta is in the ledger but not in the tail. - 143
log.append_intent(record(None)).unwrap(); - 144
log.append_activity(delta("wd-1")).unwrap(); - 145
log.append_message(user("explain the parser")).unwrap(); - 146
assert!(log.tail_sections(None).workspace.is_none()); - 147
- 148
// A working reading on the next turn, with its own delta activity. - 149
let mut working = record(None); - 150
working.engagement.posture.context = vak_intent::ContextProfile::Working; - 151
log.append_intent(working).unwrap(); - 152
log.append_activity(delta("wd-2")).unwrap(); - 153
log.append_message(user("now fix the failing test")) - 154
.unwrap(); - 155
let tail = log.tail_sections(None); - 156
let workspace = tail.workspace.expect("workspace section present"); - 157
assert!(workspace.starts_with("<workspace_delta>"), "{workspace}"); - 158
assert!(workspace.contains("src/parser.rs")); - 159
- 160
// A working reading whose turn recorded no delta shows nothing — a - 161
// previous turn's delta is never presented as current. - 162
let mut working = record(None); - 163
working.engagement.posture.context = vak_intent::ContextProfile::Working; - 164
log.append_intent(working).unwrap(); - 165
log.append_message(user("and run it")).unwrap(); - 166
assert!(log.tail_sections(None).workspace.is_none()); - 167
} - 168
- 169
#[test] - 170
fn goal_projection_ignores_control_updates_when_finding_active_work() { - 171
let dir = tempdir().unwrap(); - 172
let path = SessionPath::new_session_file(dir.path(), &PathBuf::from("/tmp/proj"), "s-intent"); - 173
let mut log = SessionLog::create(path.clone(), header()).unwrap(); - 174
log.append_goal_update(vak_intent::GoalUpdate { - 175
revision: 1, - 176
relation: vak_intent::GoalRelation::New, - 177
request: "build the report".into(), - 178
supersedes_revision: None, - 179
explicit: false, - 180
}) - 181
.unwrap(); - 182
log.append_goal_update(vak_intent::GoalUpdate { - 183
revision: 2, - 184
relation: vak_intent::GoalRelation::Status, - 185
request: "what is the status?".into(), - 186
supersedes_revision: None, - 187
explicit: false, - 188
}) - 189
.unwrap(); - 190
let state = log.goal_state().expect("goal state exists"); - 191
assert_eq!(state.objective, "build the report"); - 192
assert_eq!(state.revision, 2); - 193
// A status check is not work: the next message adds to the goal, and its - 194
// revision follows the status update rather than reusing it. - 195
let next = log.next_goal_update("include the regional split"); - 196
assert_eq!(next.relation, vak_intent::GoalRelation::AddsTo); - 197
assert_eq!(next.revision, 3); - 198
drop(log); - 199
let reopened = SessionLog::open(path).unwrap(); - 200
assert_eq!(reopened.goal_state(), Some(state)); - 201
} - 202
- 203
/// Per-turn guidance must not accumulate. Five stale notes waste context and - 204
/// let an old instruction argue with the current one. - 205
#[test] - 206
fn only_the_newest_intent_note_is_projected() { - 207
let dir = tempdir().unwrap(); - 208
let mut log = open(dir.path()); - 209
log.append_intent(record(Some("FIRST directive"))).unwrap(); - 210
log.append_message(user("one")).unwrap(); - 211
log.append_intent(record(Some("SECOND directive"))).unwrap(); - 212
log.append_message(user("two")).unwrap(); - 213
log.append_intent(record(Some("THIRD directive"))).unwrap(); - 214
log.append_message(user("three")).unwrap(); - 215
- 216
let joined = log - 217
.derive_messages() - 218
.iter() - 219
.map(|m| m.text_content()) - 220
.collect::<Vec<_>>() - 221
.join("\n"); - 222
// None of the notes are spliced into the projection any more — they - 223
// reach the model through the tail instead. - 224
assert!(!joined.contains("FIRST directive")); - 225
assert!(!joined.contains("SECOND directive")); - 226
assert!(!joined.contains("THIRD directive")); - 227
// The turns themselves all survive; only the guidance moved. - 228
for turn in ["one", "two", "three"] { - 229
assert!(joined.contains(turn), "lost turn `{turn}`"); - 230
} - 231
- 232
let intent = log.tail_sections(None).intent.expect("newest note present"); - 233
assert!(intent.contains("THIRD directive")); - 234
assert!(!intent.contains("FIRST directive")); - 235
assert!(!intent.contains("SECOND directive")); - 236
} - 237
- 238
#[test] - 239
fn an_engagement_with_nothing_to_say_contributes_no_tokens() { - 240
let dir = tempdir().unwrap(); - 241
let mut log = open(dir.path()); - 242
log.append_intent(record(None)).unwrap(); - 243
log.append_message(user("hi")).unwrap(); - 244
- 245
let messages = log.derive_messages(); - 246
assert_eq!(messages.len(), 1, "a silent engagement added a message"); - 247
assert!(!messages[0].text_content().contains("<intent>")); - 248
} - 249
- 250
/// The prompt is reconstructed from the recorded bytes, not re-derived. A - 251
/// ledger written by an older build must replay to the same prompt even if the - 252
/// derivation rules have since changed. - 253
#[test] - 254
fn replaying_a_ledger_reproduces_the_recorded_note_verbatim() { - 255
let dir = tempdir().unwrap(); - 256
let path = { - 257
let mut log = open(dir.path()); - 258
log.append_intent(record(Some("a rule this build would never generate"))) - 259
.unwrap(); - 260
log.append_message(user("go")).unwrap(); - 261
log.path().to_path_buf() - 262
}; - 263
- 264
let reopened = SessionLog::open(path.clone()).unwrap(); - 265
let intent = reopened - 266
.tail_sections(None) - 267
.intent - 268
.expect("intent tail section present after reopening"); - 269
assert!(intent.contains("a rule this build would never generate")); - 270
} - 271
- 272
/// An intent note is runtime guidance for the turn in flight, not - 273
/// conversation. It must not be swept into a compaction packet's - 274
/// summariser input as though the user had said it. - 275
#[test] - 276
fn intent_notes_are_control_and_stay_out_of_the_compaction_packet() { - 277
let dir = tempdir().unwrap(); - 278
let mut log = open(dir.path()); - 279
let mut ids = Vec::new(); - 280
for index in 0..6 { - 281
log.append_intent(record(Some(&format!("directive {index}")))) - 282
.unwrap(); - 283
ids.push( - 284
log.append_message(user(&format!("turn {index}"))) - 285
.unwrap() - 286
.id, - 287
); - 288
log.append_message(vak_session::types::MessageRecord { - 289
message: Message::assistant(vec![vak_llm::ContentBlock::text(format!( - 290
"answer {index}" - 291
))]), - 292
meta: None, - 293
}) - 294
.unwrap(); - 295
} - 296
let (older, _) = log.packet_transcript(&ids[0], &ids[3]); - 297
assert!( - 298
!older.contains("<intent>"), - 299
"an intent note was queued for summarization: {older}" - 300
); - 301
assert!(older.contains("turn 0"), "{older}"); - 302
assert!(!older.contains("turn 4"), "{older}"); - 303
} - 304
- 305
#[test] - 306
fn intent_entries_are_append_only_and_keep_the_hash_chain_intact() { - 307
let dir = tempdir().unwrap(); - 308
let path = { - 309
let mut log = open(dir.path()); - 310
log.append_intent(record(Some("note"))).unwrap(); - 311
log.append_message(user("go")).unwrap(); - 312
log.path().to_path_buf() - 313
}; - 314
let reopened = SessionLog::open(path.clone()).unwrap(); - 315
assert!( - 316
reopened.warnings().is_empty(), - 317
"intent entries broke the ledger hash chain: {:?}", - 318
reopened.warnings() - 319
); - 320
} - 321
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.