1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! BP-7 (catalog §4a "Goals (persistent objective across turns)", cc's
//! `/goal`, cx's `/goal` + `goals_1.sqlite`): the session's standing
//! objective.
//!
//! **Not the plan.** `update_plan` (the `todos` module's tool) holds a
//! steps/status array for the CURRENT stretch of work and is deliberately
//! ephemeral. A goal is the condition the whole session is working toward:
//! one sentence, set once, restated to the model on every request until it
//! is changed or cleared, and persisted beside the session so it survives a
//! resume. Design §2 module 7 homes goals with `todos` for exactly this
//! reason — same module, different lifetime.
//!
//! **Where it lands.** `<session>.goal.json`, a single typed record in the
//! sidecar family next to `<session>.git.json` (also a single record, not a
//! log) — never inside the provider-visible transcript, so translating a
//! session to another harness never has to invent a message for it.
use serde::{Deserialize, Serialize};
/// The session's persistent objective.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GoalRecord {
/// The objective, as the user stated it.
pub objective: String,
/// Unix-ms wall-clock time the goal was first set.
pub created_at_ms: i64,
/// Unix-ms wall-clock time it was last changed.
pub updated_at_ms: i64,
}
impl GoalRecord {
/// A goal set now.
pub fn new(objective: impl Into<String>, now_ms: i64) -> GoalRecord {
GoalRecord {
objective: objective.into(),
created_at_ms: now_ms,
updated_at_ms: now_ms,
}
}
/// Replace the objective, keeping `created_at_ms` — the goal's identity
/// is the session's, not the sentence's, so a reworded goal is the same
/// goal refined, not a new one.
pub fn revise(&mut self, objective: impl Into<String>, now_ms: i64) {
self.objective = objective.into();
self.updated_at_ms = now_ms;
}
/// The block spliced into the tail of every request while this goal
/// stands. Deliberately at the TAIL, not the system prompt: a standing
/// objective is only useful if it is the last thing the model reads
/// before the current turn, and appending never disturbs the cached
/// prefix.
pub fn reminder(&self) -> String {
format!(
"<goal>\nThe standing objective for this session is:\n{}\n\
Keep working toward it; say so plainly if it is already met or \
if it cannot be.\n</goal>",
self.objective.trim()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_revision_keeps_the_creation_stamp_and_moves_the_update_stamp() {
let mut goal = GoalRecord::new("ship BP-7", 1_000);
goal.revise("ship BP-7 with proof", 2_000);
assert_eq!(goal.objective, "ship BP-7 with proof");
assert_eq!(goal.created_at_ms, 1_000);
assert_eq!(goal.updated_at_ms, 2_000);
}
#[test]
fn the_reminder_carries_the_objective_in_a_tagged_block() {
let goal = GoalRecord::new(" ship BP-7 ", 0);
let reminder = goal.reminder();
assert!(reminder.starts_with("<goal>"));
assert!(reminder.ends_with("</goal>"));
assert!(reminder.contains("ship BP-7"));
assert!(
!reminder.contains(" ship BP-7 "),
"the objective is trimmed"
);
}
#[test]
fn json_round_trip_is_lossless() {
let goal = GoalRecord::new("ship BP-7", 1_700_000_000_000);
let json = serde_json::to_string(&goal).unwrap();
assert_eq!(serde_json::from_str::<GoalRecord>(&json).unwrap(), goal);
}
}