- 153
pub epoch: u64, - 154
} - 155
- 156
/// One layer's contribution to the assembled system prompt. - 157
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 158
pub struct PromptLayerDescriptor { - 159
/// "identity" | "operating-rules" | "guardrails". - 160
pub block: String, - 161
/// "seed" | "shared" | "project" | "surface" | "bot" | "chat" | "agent". - 162
pub layer: String, - 163
#[serde(default, skip_serializing_if = "Option::is_none")] - 164
pub source: Option<String>, - 165
pub digest: String, - 166
pub bytes: usize, - 167
} - 168
- 169
#[derive(Debug, Clone, Serialize, Deserialize)] - 170
pub struct SessionHeader { - 171
/// Frozen user-facing owner. Absence denotes the built-in Vak agent. - 172
#[serde(default, skip_serializing_if = "Option::is_none")] - 173
pub agent: Option<AgentIdentity>, - 174
pub session_id: String, - 175
pub created_at: DateTime<Utc>, - 176
pub cwd: PathBuf, - 177
#[serde(default, skip_serializing_if = "Option::is_none")] - 178
pub parent_session_id: Option<String>, - 179
#[serde(default, skip_serializing_if = "Option::is_none")] - 180
pub contract_id: Option<String>, - 181
#[serde(default, skip_serializing_if = "Option::is_none")] - 182
pub work_item_id: Option<String>, - 183
/// Durable conversation ownership and ingress provenance. This is - 184
/// optional only while the baseline checker identifies pre-contract - 185
/// ledgers; every newly admitted session receives it. - 186
#[serde(default, skip_serializing_if = "Option::is_none")] - 187
pub conversation: Option<ConversationContext>, - 188
pub contract: FrozenContract, - 189
} - 190
- 191
/// The audience and conversation that are allowed to see a session. A - 192
/// transport address is not itself a person: `audience_id` is the verified - 193
/// principal/group identity, while `origin` records where this request arrived - 194
/// for delivery and audit purposes. - 195
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 196
pub struct ConversationContext { - 197
pub conversation_id: String, - 198
pub audience_id: String, - 199
#[serde(default, skip_serializing_if = "Option::is_none")] - 200
pub origin: Option<ConversationOrigin>, - 201
} - 202
- 203
impl ConversationContext { - 204
/// The private local default used when an embedding surface has not - 205
/// supplied a remote audience. The conversation id is intentionally the - 206
/// newly admitted session id, so independent CLI/background admissions do - 207
/// not accidentally share history. - 208
pub fn local(conversation_id: impl Into<String>, surface: impl Into<String>) -> Self { - 209
let surface = surface.into(); - 210
Self { - 211
conversation_id: conversation_id.into(), - 212
audience_id: "local".into(), - 213
origin: Some(ConversationOrigin { - 214
surface: if surface.trim().is_empty() { - 215
"local".into() - 216
} else { - 217
surface - 218
}, - 219
address: "local".into(), - 220
bot_id: None, - 221
}), - 222
} - 223
} - 224
} - 225
- 226
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 227
pub struct ConversationOrigin { - 228
pub surface: String, - 229
pub address: String, - 230
#[serde(default, skip_serializing_if = "Option::is_none")] - 231
pub bot_id: Option<String>, - 232
} - 233
- 234
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 235
pub struct AgentIdentity { - 236
pub id: String, - 237
pub revision: u64, - 238
pub name: String, - 239
// Older ledgers in the supported major line predate characters. An - 240
// empty historical snapshot means "use the Agent's current profile"; - 241
// new headers always write the selected character explicitly. - 242
#[serde(default)] - 243
pub character: String, - 244
pub personality: String, - 245
#[serde(default = "default_agent_animation")] - 246
pub animation: String, - 247
#[serde(default = "default_agent_voice")] - 248
pub voice: String, - 249
pub behaviour: String, - 250
#[serde(default)] - 251
pub responsibilities: String, - 252
/// User-authored instructions added to vak's universal foundation. - 253
#[serde(default)] - 254
pub instructions: String, - 255
} - 256
- 257
fn default_agent_animation() -> String { - 258
"subtle".into() - 259
} - 260
- 261
fn default_agent_voice() -> String { - 262
"default".into() - 263
} - 264
- 265
impl SessionHeader { - 266
pub fn contract_cwd(&self) -> PathBuf { - 267
self.cwd.clone() - 268
} - 269
} - 270
- 271
#[derive(Debug, Clone, Serialize, Deserialize)] - 272
pub struct MessageRecord { - 273
pub message: Message, - 274
#[serde(default, skip_serializing_if = "Option::is_none")] - 275
pub meta: Option<MessageMeta>, - 276
} - 277
- 278
/// One model-visible message with its ledger identity and class. - 279
#[derive(Debug, Clone)] - 280
pub struct TranscriptMessage { - 281
/// The ledger entry this message came from (stable, unique). - 282
pub entry_id: String, - 283
pub message: Message, - 284
/// Set when the runtime authored this user-role message (a nudge). - 285
pub control: Option<vak_intent::control::ControlKind>, - 286
/// Set when the runtime derived this message into the model's input - 287
/// (compaction summary, intent note, work contract, conversation thread). - 288
pub context: bool, - 289
pub author_id: Option<String>, - 290
pub author_name: Option<String>, - 291
/// Files attached to this message (`MessageMeta::attachments`). - 292
pub attachments: Vec<AttachedFile>, - 293
} - 294
- 295
impl MessageRecord { - 296
/// A user-role message the runtime authored. The body still begins with - 297
/// the kind's marker (the model reads it); the tag is what every other - 298
/// layer reads instead of guessing from the text. - 299
pub fn control(kind: vak_intent::control::ControlKind, body: impl Into<String>) -> Self { - 300
Self { - 301
message: Message::user_text(body), - 302
meta: Some(MessageMeta { - 303
control: Some(kind), - 304
..MessageMeta::default() - 305
}), - 306
} - 307
} - 308
- 309
/// Whether the runtime, not the user, authored this message: the - 310
/// structural tag, and nothing else. Text is never sniffed. - 311
pub fn control_kind(&self) -> Option<vak_intent::control::ControlKind> { - 312
self.meta.as_ref().and_then(|meta| meta.control) - 313
} - 314
} - 315
- 316
#[derive(Debug, Clone, Default, Serialize, Deserialize)] - 317
pub struct MessageMeta { - 318
#[serde(default, skip_serializing_if = "Option::is_none")] - 319
pub model: Option<String>, - 320
#[serde(default, skip_serializing_if = "Option::is_none")] - 321
pub stop_reason: Option<String>, - 322
#[serde(default, skip_serializing_if = "Option::is_none")] - 323
pub usage: Option<Usage>, - 324
/// Set on a user-role message the runtime authored (a repair nudge, a - 325
/// stop guard) rather than the user. See `vak_intent::control`. - 326
#[serde(default, skip_serializing_if = "Option::is_none")] - 327
pub control: Option<vak_intent::control::ControlKind>, - 328
/// Verified human principal for a shared-conversation message. - 329
#[serde(default, skip_serializing_if = "Option::is_none")] - 330
pub author_id: Option<String>, - 331
#[serde(default, skip_serializing_if = "Option::is_none")] - 332
pub author_name: Option<String>, - 333
/// Client idempotency key, scoped to `author_id`. - 334
#[serde(default, skip_serializing_if = "Option::is_none")] - 335
pub request_id: Option<String>, - 336
/// Files the person attached, saved in the workspace inbox. Each names - 337
/// the text block that tells the model where the file is, so a client - 338
/// draws the file there instead of that line. - 339
#[serde(default, skip_serializing_if = "Vec::is_empty")] - 340
pub attachments: Vec<AttachedFile>, - 341
} - 342
- 343
/// A file attached to a user message (docs/design/72, "File in"). Its bytes - 344
/// never enter the message; `block` is the index of the text block that - 345
/// names `path` to the model. - 346
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 347
pub struct AttachedFile { - 348
pub block: usize, - 349
/// Workspace-relative, under `inbox/`. - 350
pub path: String, - 351
/// The name the file had when it was attached. - 352
pub name: String, - 353
pub bytes: u64, - 354
} - 355
- 356
/// A compaction packet (docs/design/68-context-engine.md §4): the summary - 357
/// of one contiguous range of closed turns, keyed by that range. A packet - 358
/// is a cache of summariser work, never a boundary. The `WorkingSetPlanner` - 359
/// decides per request, from the bound model's measured profile, whether a - 360
/// packet is needed at all and over which range; the projection reuses a - 361
/// stored packet only when its range is exactly the one the plan asks for. - 362
/// So a packet written while a small model was bound never hides those - 363
/// turns from a larger model bound later: the ledger stays the one rich - 364
/// original, and every projection is a function of (ledger, bound model). - 365
/// - 366
/// `reset_all` is the one true boundary: the reset-with-handoff rescue - 367
/// (docs/design/42-managed-work-contracts.md) for a profile with no usable - 368
/// horizon replaces everything before the entry with `summary`; the range - 369
/// fields are empty on such an entry. - 370
#[derive(Debug, Clone, Serialize, Deserialize)] - 371
pub struct CompactionEntry { - 372
pub summary: String, - 373
/// Oldest covered turn's directive entry id (inclusive). - 374
pub first_turn_id: String, - 375
/// Newest covered turn's directive entry id (inclusive). - 376
pub last_turn_id: String, - 377
/// The model whose plan asked for this packet and whose summariser - 378
/// wrote it — provenance for forensics, not a lookup key. - 379
pub model: String, - 380
pub tokens_before: u64, - 381
#[serde(default)] - 382
pub reset_all: bool, - 383
} - 384
- 385
/// A durable objective with acceptance criteria (docs/design/42-managed-work-contracts.md). - 386
/// Status transitions append new entries — the ledger never rewrites. - 387
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - 388
pub struct GoalEntry { - 389
pub goal_id: String, - 390
pub objective: String, - 391
pub criteria: Vec<String>, - 392
pub status: GoalStatus, - 393
} - 394
- 395
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - 396
#[serde(rename_all = "snake_case")] - 397
pub enum GoalStatus { - 398
Active, - 399
/// Completed AND independently audited (deterministic checks and/or - 400
/// judge) — never self-reported alone. - 401
Done { - 402
audited: bool, - 403
}, - 404
/// Audit budget exhausted without verification; run proceeds so it - 405
/// can never trap the model. - 406
Unverified { - 407
reason: String, - 408
}, - 409
} - 410
- 411
/// Durable, projection-neutral lifecycle fact used to rebuild native output - 412
/// timelines. Activity never enters the model context and never replaces the - 413
/// message/tool records that remain the source of conversational truth. - 414
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 415
pub struct ActivityRecord { - 416
pub activity_id: String, - 417
#[serde(default, skip_serializing_if = "Option::is_none")] - 418
pub turn: Option<usize>, - 419
#[serde(rename = "activity_kind")] - 420
pub kind: ActivityKind, - 421
pub status: ActivityStatus, - 422
pub label: String, - 423
#[serde(default, skip_serializing_if = "Option::is_none")] - 424
pub detail: Option<String>, - 425
#[serde(default)] - 426
pub data: BTreeMap<String, String>, - 427
} - 428
- 429
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - 430
pub struct WorkContract { - 431
pub contract_id: String, - 432
pub revision: u32, - 433
pub source_entry_id: String, - 434
pub objective: String, - 435
#[serde(default)] - 436
pub constraints: Vec<WorkConstraint>, - 437
#[serde(default)] - 438
pub assumptions: Vec<WorkAssumption>, - 439
#[serde(default)] - 440
pub criteria: Vec<WorkCriterion>, - 441
pub items: Vec<WorkItemDefinition>, - 442
} - 443
- 444
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 445
pub struct WorkConstraint { - 446
pub constraint_id: String, - 447
pub text: String, - 448
} - 449
- 450
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 451
pub struct WorkAssumption { - 452
pub assumption_id: String, - 453
pub text: String, - 454
#[serde(default)] - 455
pub requires_confirmation: bool, - 456
#[serde(default, skip_serializing_if = "Option::is_none")] - 457
pub resolution: Option<String>, - 458
} - 459
- 460
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 461
pub struct WorkCriterion { - 462
pub criterion_id: String, - 463
pub statement: String, - 464
pub kind: CriterionKind, - 465
#[serde(default = "default_true")] - 466
pub required: bool, - 467
} - 468
- 469
fn default_true() -> bool { - 470
true - 471
} - 472
- 473
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 474
#[serde(tag = "kind", rename_all = "snake_case")] - 475
pub enum CriterionKind { - 476
Shell { command: String }, - 477
FileExists { path: PathBuf }, - 478
FileContains { path: PathBuf, pattern: String }, - 479
ToolSucceeded { tool: String }, - 480
FlowCompleted { flow: String }, - 481
ExternalReceipt { integration: String }, - 482
Semantic, - 483
} - 484
- 485
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 486
pub struct WorkItemDefinition { - 487
pub item_id: String, - 488
pub title: String, - 489
pub instructions: String, - 490
#[serde(default)] - 491
pub dependencies: Vec<String>, - 492
pub owner: WorkOwner, - 493
#[serde(default = "default_true")] - 494
pub required: bool, - 495
#[serde(default)] - 496
pub readonly: bool, - 497
#[serde(default)] - 498
pub path_claims: Vec<String>, - 499
#[serde(default)] - 500
pub criterion_ids: Vec<String>, - 501
} - 502
- 503
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 504
#[serde(rename_all = "snake_case")] - 505
pub enum WorkOwner { - 506
ParentAgent, - 507
/// A ledger entry written before the subagent->worker rename stored this - 508
/// as "subagent" — the alias keeps sessions logged before that upgrade - 509
/// from silently failing to parse (which would break the hash-chain - 510
/// integrity check on the next entry too). - 511
#[serde(alias = "subagent")] - 512
Worker, - 513
Flow { - 514
name: String, - 515
}, - 516
Tool { - 517
name: String, - 518
}, - 519
Human, - 520
} - 521
- 522
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 523
#[serde(rename_all = "snake_case")] - 524
pub enum WorkContractStatus { - 525
Draft, - 526
AwaitingInput, - 527
Active, - 528
Blocked, - 529
Verifying, - 530
Completed, - 531
Failed, - 532
Cancelled, - 533
Unverified, - 534
} - 535
- 536
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 537
#[serde(rename_all = "snake_case")] - 538
pub enum WorkItemStatus { - 539
Proposed, - 540
Ready, - 541
Running, - 542
WaitingApproval, - 543
Blocked, - 544
ReadyForVerification, - 545
Succeeded, - 546
Failed, - 547
Skipped, - 548
Cancelled, - 549
Interrupted, - 550
} - 551
- 552
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 553
pub struct WorkItemState { - 554
pub item_id: String, - 555
pub owner: WorkOwner, - 556
pub status: WorkItemStatus, - 557
pub attempt: u32, - 558
#[serde(default, skip_serializing_if = "Option::is_none")] - 559
pub child_session_id: Option<String>, - 560
#[serde(default, skip_serializing_if = "Option::is_none")] - 561
pub blocker: Option<String>, - 562
#[serde(default)] - 563
pub evidence: Vec<EvidenceRef>, - 564
} - 565
- 566
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 567
#[serde(tag = "kind", rename_all = "snake_case")] - 568
pub enum EvidenceRef { - 569
LedgerEntry { - 570
session_id: String, - 571
entry_id: String, - 572
}, - 573
ToolResult { - 574
session_id: String, - 575
tool_use_id: String, - 576
}, - 577
Receipt { - 578
session_id: String, - 579
entry_id: String, - 580
}, - 581
CheckpointDiff { - 582
session_id: String, - 583
from_seq: u32, - 584
to_seq: u32, - 585
}, - 586
FlowNode { - 587
flow: String, - 588
run_id: String, - 589
node_id: String, - 590
}, - 591
ChildSession { - 592
session_id: String, - 593
}, - 594
ExternalOperation { - 595
integration: String, - 596
operation_id: String, - 597
}, - 598
} - 599
- 600
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 601
#[serde(tag = "kind", rename_all = "snake_case")] - 602
pub enum CriterionResult { - 603
Passed { evidence: String }, - 604
Failed { reason: String }, - 605
Unknown { reason: String }, - 606
} - 607
- 608
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - 609
pub struct WorkEvent { - 610
pub contract_id: String, - 611
pub revision: u32, - 612
pub kind: WorkEventKind, - 613
} - 614
- 615
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - 616
#[serde(tag = "kind", rename_all = "snake_case")] - 617
pub enum WorkEventKind { - 618
ContractCreated { - 619
contract: WorkContract, - 620
}, - 621
ContractRevised { - 622
previous_revision: u32, - 623
contract: WorkContract, - 624
reason: String, - 625
}, - 626
ContractStatusChanged { - 627
from: WorkContractStatus, - 628
to: WorkContractStatus, - 629
reason: String, - 630
}, - 631
ItemStatusChanged { - 632
item_id: String, - 633
from: WorkItemStatus, - 634
to: WorkItemStatus, - 635
attempt: u32, - 636
reason: String, - 637
}, - 638
ItemVerified { - 639
item_id: String, - 640
attempt: u32, - 641
}, - 642
ItemAssigned { - 643
item_id: String, - 644
owner: WorkOwner, - 645
child_session_id: Option<String>, - 646
}, - 647
EvidenceAttached { - 648
item_id: String, - 649
evidence: EvidenceRef, - 650
}, - 651
AssumptionResolved { - 652
assumption_id: String, - 653
resolution: String, - 654
}, - 655
VerificationRecorded { - 656
criterion_id: String, - 657
result: CriterionResult, - 658
}, - 659
} - 660
- 661
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] - 662
#[serde(rename_all = "snake_case")] - 663
pub enum ActivityKind { - 664
Approval, - 665
Retry, - 666
RouteFallback, - 667
/// A ledger entry written before the subagent->worker rename stored this - 668
/// as "subagent" — the alias keeps sessions logged before that upgrade - 669
/// from silently failing to parse. - 670
#[serde(alias = "subagent")] - 671
Worker, - 672
Diagnostic, - 673
Run, - 674
/// A provisional or committed speech recognition segment. - 675
VoiceTranscript, - 676
/// Speech delivery and playback accounting for an assistant response. - 677
VoicePlayback, - 678
/// Which immutable presentation revision was selected for a result. - 679
PresentationSelection, - 680
/// A validated immutable presentation revision preview was proposed. - 681
PresentationProposal, - 682
/// User choice or feedback about a presentation projection. - 683
PresentationFeedback, - 684
/// Human feedback anchored to an immutable candidate result and file. - 685
CandidateComment, - 686
/// A human-requested isolated Agent revision of a saved candidate. - 687
CandidateRevision, - 688
/// A bind-time capacity probe ran and recorded a `CapacityProfile` - 689
/// (docs/design/68-context-engine.md §1). Never model-visible. - 690
CapacityProbe, - 691
/// A turn's usage or instruction-following outcome updated an existing - 692
/// `CapacityProfile` (§1 "Feedback"). Never model-visible. - 693
CapacityFeedback, - 694
} - 695
- 696
/// Where a validated presentation came from (docs/design/68-context-engine.md - 697
/// §10 "Presentations are ledger entries"). Both paths converge on the same - 698
/// `PresentationRecord` shape; only the provenance differs. - 699
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] - 700
#[serde(tag = "kind", rename_all = "snake_case")] - 701
pub enum PresentationSource { - 702
/// Emitted through an `emit_*_card` tool call. - 703
ToolCall { tool_use_id: String }, - 704
/// Emitted as an inline ```` ```vak ```` fence in assistant text (models - 705
/// without tool calling). - 706
Fence { message_entry_id: String }, - 707
/// Emitted by a worker this conversation delegated to through the - 708
/// `tool_use_id` call (`task`). The worker's own ledger holds the card's - 709
/// original call; this entry is how the delegating conversation shows it - 710
/// and recalls it. Unlike `ToolCall`, one call may carry several. - 711
Delegated { - 712
tool_use_id: String, - 713
worker_session_id: String, - 714
}, - 715
} - 716
- 717
/// A validated `emit_*_card` (or fence) presentation, written once at the - 718
/// moment it validates. Never model-visible raw (`derive_messages` skips it, - 719
/// like `Receipt`): the current turn already sees the card through the - 720
/// `tool_use` input it wrote; later turns see it through a `TurnCard` or a - 721
/// full-record rendering, both of which read this entry. This is the single - 722
/// source both the model-visible history and the display channel read — - 723
/// nothing is rebuilt from tool arguments after this is written. - 724
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] - 725
pub struct PresentationRecord { - 726
/// The entry id of the user directive this turn answers. - 727
pub turn_id: String, - 728
pub source: PresentationSource, - 729
pub semantic_type: String, - 730
pub skill_id: String, - 731
pub skill_version: String, - 732
pub schema_version: u32, - 733
/// Canonical (validated, key-sorted) form. See [`canonicalize_json`]. - 734
pub payload: Value, - 735
/// Hash of the canonical payload. See [`payload_digest`]. - 736
pub payload_digest: String, - 737
/// Evidence ids: the `tool_use_id`s of every non-card tool result that - 738
/// appears in the current turn before this card. - 739
pub derived_from: Vec<String>, - 740
pub title: String, - 741
/// Schema-driven summary of the fields that make this presentation - 742
/// distinguishable from another of the same `semantic_type`, used in - 743
/// `TurnCard` index lines. Never a character truncation. - 744
pub identity_digest: String, - 745
} - 746
- 747
/// Recursively sorts object keys so two payloads that differ only in field - 748
/// insertion order canonicalize to identical bytes. Array order is - 749
/// preserved — it is meaningful (e.g. chart series, table rows). - 750
/// - 751
/// Uses an explicit `BTreeMap` pass (rather than relying on `serde_json`'s - 752
/// own map ordering, which is only sorted when the `preserve_order` feature - 753
/// is off) so the canonical form is deterministic regardless of that - 754
/// feature flag. - 755
pub fn canonicalize_json(value: &Value) -> Value { - 756
match value { - 757
Value::Object(map) => { - 758
let sorted: BTreeMap<&String, &Value> = map.iter().collect(); - 759
let mut out = serde_json::Map::new(); - 760
for (key, val) in sorted { - 761
out.insert(key.clone(), canonicalize_json(val)); - 762
} - 763
Value::Object(out) - 764
} - 765
Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()), - 766
other => other.clone(), - 767
} - 768
} - 769
- 770
/// SHA-256 hex digest of a payload's canonical form (see - 771
/// [`canonicalize_json`]). Used to detect a repeated presentation — the - 772
/// same card validated twice in one turn (once via tool call, once via a - 773
/// duplicate fence) hashes identically regardless of key order. - 774
pub fn payload_digest(payload: &Value) -> String { - 775
use sha2::{Digest, Sha256}; - 776
let canonical = canonicalize_json(payload); - 777
let bytes = serde_json::to_vec(&canonical).unwrap_or_default(); - 778
let mut hasher = Sha256::new(); - 779
hasher.update(&bytes); - 780
hasher - 781
.finalize() - 782
.iter() - 783
.fold(String::with_capacity(64), |mut acc, byte| { - 784
use std::fmt::Write; - 785
let _ = write!(acc, "{byte:02x}"); - 786
acc - 787
}) - 788
} - 789
- 790
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] - 791
#[serde(rename_all = "snake_case")] - 792
pub enum ActivityStatus { - 793
Pending, - 794
Running, - 795
Succeeded, - 796
Failed, - 797
Denied, - 798
Cancelled, - 799
Partial, - 800
} - 801
- 802
/// One turn's resolved intent (docs/design/47-commitment-kernel.md). - 803
/// - 804
/// Model-visible **and** audit in one entry, deliberately. `model_visible` - 805
/// holds the exact text the engagement contributed to the model's context, so - 806
/// invariant 1 holds by construction: replaying the ledger reproduces the - 807
/// prompt byte-for-byte rather than regenerating it from a derivation that may - 808
/// have changed in the meantime. - 809
/// - 810
/// The `reading`/`engagement`/`provenance` fields alongside it are what make - 811
/// the decision auditable — including whether it was reproducible at all. - 812
#[derive(Debug, Clone, Serialize, Deserialize)] - 813
pub struct IntentRecord { - 814
/// The composite reading for the turn. - 815
pub reading: vak_intent::Reading, - 816
/// The parts of the request, each with its own reading, relation and - 817
/// thread lineage (docs/design/47-commitment-kernel.md, strands). - 818
#[serde(default)] - 819
pub strands: Vec<vak_intent::Strand>, - 820
pub engagement: vak_intent::Engagement, - 821
pub provenance: vak_intent::Provenance, - 822
/// The requested outcome captured for this turn, when the host could - 823
/// construct one without inventing requirements. - 824
#[serde(default, skip_serializing_if = "Option::is_none")] - 825
pub outcome: Option<vak_intent::OutcomeSpec>, - 826
/// Exactly what the model was told, if anything. `None` when the - 827
/// engagement had nothing worth spending tokens to say. - 828
#[serde(default, skip_serializing_if = "Option::is_none")] - 829
pub model_visible: Option<String>, - 830
/// The durable commitment this turn serves, when one is open: the - 831
/// primary strand's. - 832
#[serde(default, skip_serializing_if = "Option::is_none")] - 833
pub commitment_id: Option<String>, - 834
/// Every strand's commitment, by strand id, when more than one durable - 835
/// thread is served by this turn. - 836
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] - 837
pub strand_commitments: std::collections::BTreeMap<String, String>, - 838
} - 839
- 840
/// Durable terminal marker for a child-agent run. Presence of a child ledger - 841
/// alone does not prove that the child finished; recovery must consult this - 842
/// marker before attaching evidence or retrying work. - 843
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 844
#[serde(rename_all = "snake_case")] - 845
pub enum ChildRunStatus { - 846
Completed, - 847
Failed, - 848
Aborted, - 849
MaxTurns, - 850
} - 851
- 852
#[derive(Debug, Clone, Serialize, Deserialize)] - 853
#[serde(tag = "kind", rename_all = "snake_case")] - 854
// SessionHeader intentionally carries the frozen agent contract and remains - 855
// inline so the append-only JSONL representation and all existing pattern - 856
// matches stay unchanged. Keep this representation decision explicit as the - 857
// header grows; boxing it would be a wire/API refactor, not a lint-only fix. - 858
#[allow(clippy::large_enum_variant)] - 859
pub enum EntryPayload { - 860
Header(SessionHeader), - 861
Message(MessageRecord), - 862
Compaction(CompactionEntry), - 863
/// Audit record for one unit of provider work (docs/design/42-managed-work-contracts.md). - 864
/// Never model-visible: `derive_messages` skips it. - 865
Receipt(vak_llm::WorkReceipt), - 866
/// Goal lifecycle (docs/design/42-managed-work-contracts.md). Never model-visible. - 867
Goal(GoalEntry), - 868
/// Relationship between this request and the active collaborative goal. - 869
/// Never model-visible; the original request remains a Message entry. - 870
GoalUpdate(vak_intent::GoalUpdate), - 871
/// UI/audit lifecycle facts; never model-visible. - 872
Activity(ActivityRecord), - 873
/// Durable managed-work lifecycle event. The projector is the source of - 874
/// current work state; events are never rewritten. - 875
Work(WorkEvent), - 876
/// This turn's resolved intent. Model-visible via `model_visible`. - 877
Intent(Box<IntentRecord>), - 878
/// Terminal marker written by a child agent before its parent observes the - 879
/// result. Never model-visible. - 880
ChildRun { - 881
status: ChildRunStatus, - 882
#[serde(default, skip_serializing_if = "Option::is_none")] - 883
outcome: Option<vak_intent::OutcomeSpec>, - 884
}, - 885
/// Exact capability interface used by one provider turn. - 886
TurnCapabilitiesBound(TurnCapabilitiesBound), - 887
/// A turn bound the same interface as an earlier `TurnCapabilitiesBound` - 888
/// (same digest): recorded by reference instead of rewriting the whole - 889
/// system prompt and tool schemas every turn. - 890
TurnCapabilitiesRef(TurnCapabilitiesRef), - 891
/// A validated presentation (docs/design/68-context-engine.md §10). - 892
/// Never model-visible raw: `derive_messages` skips it like `Receipt`. - 893
Presentation(PresentationRecord), - 894
/// A turn's closing card (docs/design/68-context-engine.md §10), written - 895
/// once when the turn closes and never rewritten. Never model-visible - 896
/// raw: a follow-up turn sees it through `TurnCard::line` in the - 897
/// `<turns>` tail block or, promoted, through `Turn::full_record`, never - 898
/// through this entry directly. - 899
TurnCard(TurnCardRecord), - 900
/// The whole result of a tool call whose request carried only a window - 901
/// of it (docs/design/68-context-engine.md §3). Never model-visible raw: - 902
/// the `ToolResult` block holds what the request carried, and `recall` - 903
/// and the closed-turn digests read this. - 904
EvidenceBody(EvidenceBodyRecord), - 905
} - 906
- 907
/// The whole result behind a windowed `ToolResult` block. - 908
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - 909
pub struct EvidenceBodyRecord { - 910
pub tool_use_id: String, - 911
pub content: String, - 912
} - 913
- 914
/// The closing record for one turn (docs/design/68-context-engine.md §10). - 915
/// `turn_id` is the directive entry id the card answers — the same id - 916
/// `TurnIndex` uses to key turns and `recall({ turn })` resolves against. - 917
#[derive(Debug, Clone, Serialize, Deserialize)] - 918
pub struct TurnCardRecord { - 919
pub turn_id: String, - 920
pub card: crate::turns::TurnCard, - 921
} - 922
- 923
#[derive(Debug, Clone, Serialize, Deserialize)] - 924
pub struct Entry { - 925
pub id: String, - 926
#[serde(default)] - 927
pub parent_id: Option<String>, - 928
pub ts: DateTime<Utc>, - 929
/// SHA-256 of the previous entry's serialized line, hex-encoded. - 930
/// - 931
/// `parent_id` links entries but binds nothing: an interior entry could be - 932
/// rewritten and re-linked, and reconstruction would accept the result. - 933
/// This makes any such edit detectable — changing an entry changes its - 934
/// line digest, which no longer matches its successor's `prev_hash`. - 935
/// - 936
/// `None` on the first entry, and on every entry written before the chain - 937
/// existed. Ledgers are a frozen, append-only contract, so an unchained - 938
/// entry is reported as a warning and never a read failure. - 939
#[serde(default, skip_serializing_if = "Option::is_none")] - 940
pub prev_hash: Option<String>, - 941
#[serde(flatten)] - 942
pub payload: EntryPayload, - 943
} - 944
- 945
impl Entry { - 946
pub fn new(parent_id: Option<String>, payload: EntryPayload) -> Self { - 947
Entry { - 948
id: uuid::Uuid::now_v7().to_string(), - 949
parent_id, - 950
ts: Utc::now(), - 951
prev_hash: None, - 952
payload, - 953
} - 954
} - 955
} - 956
- 957
/// Chain digest of one serialized ledger line. - 958
/// - 959
/// Taken over the exact bytes written rather than a re-serialization, so - 960
/// verification cannot drift with serde field ordering or formatting. - 961
pub fn line_digest(line: &str) -> String { - 962
use sha2::{Digest, Sha256}; - 963
let mut hasher = Sha256::new(); - 964
hasher.update(line.as_bytes()); - 965
hasher - 966
.finalize() - 967
.iter() - 968
.fold(String::with_capacity(64), |mut acc, byte| { - 969
use std::fmt::Write; - 970
let _ = write!(acc, "{byte:02x}"); - 971
acc - 972
}) - 973
} - 974
- 975
#[derive(Debug, thiserror::Error)] - 976
pub enum SessionError { - 977
#[error("io error: {0}")] - 978
Io(#[from] std::io::Error), - 979
#[error("json error at line {line}: {message}")] - 980
Corrupt { line: usize, message: String }, - 981
#[error("session file already exists: {0}")] - 982
Exists(std::path::PathBuf), - 983
#[error("session is locked by another process: {0}")] - 984
Locked(std::path::PathBuf), - 985
} - 986
- 987
#[cfg(test)] - 988
#[allow(clippy::unwrap_used)] - 989
mod agent_identity_tests { - 990
#[test] - 991
fn character_absent_from_existing_ledger_has_neutral_default() { - 992
let value = serde_json::json!({ - 993
"id": "researcher", - 994
"revision": 1, - 995
"name": "Researcher", - 996
"personality": "curious", - 997
"behaviour": "careful" - 998
}); - 999
let identity = serde_json::from_value::<super::AgentIdentity>(value).unwrap(); - 1000
assert!(identity.character.is_empty()); - 1001
} - 1002
- 1003
#[test] - 1004
fn movement_and_voice_have_stable_defaults_for_existing_ledgers() { - 1005
let value = serde_json::json!({ - 1006
"id": "researcher", - 1007
"revision": 1, - 1008
"name": "Researcher", - 1009
"character": "moss", - 1010
"personality": "curious", - 1011
"behaviour": "careful" - 1012
}); - 1013
let identity = serde_json::from_value::<super::AgentIdentity>(value).unwrap(); - 1014
assert_eq!(identity.animation, "subtle"); - 1015
assert_eq!(identity.voice, "default"); - 1016
} - 1017
} - 1018
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.