@@ -642,6 +642,16 @@ enum Commands {
642642 #[ arg( long, default_value_t = 5 ) ]
643643 k : usize ,
644644 } ,
645+ /// Record a durable user preference (Pillar C) — e.g. "prefer terse output",
646+ /// "respond in Russian", "always run the full test suite before tagging".
647+ /// Stored user-level (across all projects) and injected into every session
648+ /// so the agent remembers how you work without being re-told.
649+ Remember {
650+ /// The preference text to remember.
651+ text : String ,
652+ } ,
653+ /// List your stored user preferences.
654+ Preferences ,
645655 /// Render and print the resume pack for a task.
646656 Pack {
647657 /// Task id (e.g. tj-7f3a).
@@ -1250,6 +1260,30 @@ fn main() -> Result<()> {
12501260 }
12511261 }
12521262 }
1263+ Commands :: Remember { text } => {
1264+ let global = tj_core:: memory:: open ( tj_core:: paths:: memory_db ( ) ?) ?;
1265+ let now = chrono:: Utc :: now ( ) . to_rfc3339 ( ) ;
1266+ if tj_core:: memory:: add_preference ( & global, & text, & now) ? {
1267+ println ! ( "remembered: {}" , text. trim( ) ) ;
1268+ } else {
1269+ println ! ( "already remembered" ) ;
1270+ }
1271+ }
1272+ Commands :: Preferences => {
1273+ let path = tj_core:: paths:: memory_db ( ) ?;
1274+ let prefs = if path. exists ( ) {
1275+ tj_core:: memory:: list_preferences ( & tj_core:: memory:: open ( & path) ?) ?
1276+ } else {
1277+ Vec :: new ( )
1278+ } ;
1279+ if prefs. is_empty ( ) {
1280+ println ! ( "no preferences yet — add one with `task-journal remember \" ...\" `" ) ;
1281+ } else {
1282+ for p in prefs {
1283+ println ! ( "- {p}" ) ;
1284+ }
1285+ }
1286+ }
12531287 Commands :: Event {
12541288 task_id,
12551289 r#type,
@@ -1965,10 +1999,17 @@ fn main() -> Result<()> {
19651999 // manually each session. Empty stdout when no open tasks → no
19662000 // injection, keeps system prompt clean for fresh projects.
19672001 if kind == "SessionStart" {
2002+ // User preferences are global, so they surface even in a fresh
2003+ // project with no events of its own (Pillar C "remember me").
2004+ let prefs_block = session_preferences_block ( ) ;
19682005 // Skip early on a clean machine: nothing to surface, and we
19692006 // don't want SessionStart to spawn empty SQLite files in
1970- // every project Claude Code is opened in.
2007+ // every project Claude Code is opened in. Preferences still go
2008+ // out if there are any.
19712009 if !events_path. exists ( ) {
2010+ if !prefs_block. is_empty ( ) {
2011+ emit_session_context ( & prefs_block) ;
2012+ }
19722013 return Ok ( ( ) ) ;
19732014 }
19742015 let state_path =
@@ -1977,6 +2018,9 @@ fn main() -> Result<()> {
19772018 tj_core:: db:: ingest_new_events ( & conn, & events_path, & project_hash) ?;
19782019 let recent = recent_task_contexts ( & conn, 3 ) ?;
19792020 if recent. is_empty ( ) {
2021+ if !prefs_block. is_empty ( ) {
2022+ emit_session_context ( & prefs_block) ;
2023+ }
19802024 return Ok ( ( ) ) ;
19812025 }
19822026 // After a compaction (source=="compact"), re-inject the
@@ -1985,6 +2029,12 @@ fn main() -> Result<()> {
19852029 // any error → no reminder, never abort SessionStart.
19862030 let source = payload. get ( "source" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
19872031 let mut bundle = String :: new ( ) ;
2032+ // Preferences lead the bundle — they're the smallest, most
2033+ // durable signal about how the user wants to be worked with.
2034+ if !prefs_block. is_empty ( ) {
2035+ bundle. push_str ( & prefs_block) ;
2036+ bundle. push_str ( "\n \n " ) ;
2037+ }
19882038 if source == "compact" {
19892039 if let Ok ( Some ( reminder) ) = tj_core:: reminder:: active_task_reminder ( & conn) {
19902040 bundle. push_str ( & reminder) ;
@@ -3809,6 +3859,38 @@ fn run_recall_hook() -> anyhow::Result<()> {
38093859 Ok ( ( ) )
38103860}
38113861
3862+ /// Render the user's standing preferences as a SessionStart context block, or
3863+ /// "" when there are none. Capped so it never floods the system prompt.
3864+ fn session_preferences_block ( ) -> String {
3865+ let prefs = match tj_core:: paths:: memory_db ( )
3866+ . and_then ( tj_core:: memory:: open)
3867+ . and_then ( |c| tj_core:: memory:: list_preferences ( & c) )
3868+ {
3869+ Ok ( p) if !p. is_empty ( ) => p,
3870+ _ => return String :: new ( ) ,
3871+ } ;
3872+ let mut s = String :: from ( "## Your standing preferences (remember these across sessions):\n " ) ;
3873+ for p in prefs {
3874+ let line = format ! ( "- {p}\n " ) ;
3875+ if s. len ( ) + line. len ( ) > 800 {
3876+ break ;
3877+ }
3878+ s. push_str ( & line) ;
3879+ }
3880+ s. trim_end ( ) . to_string ( )
3881+ }
3882+
3883+ /// Emit a SessionStart `additionalContext` envelope and nothing else.
3884+ fn emit_session_context ( ctx : & str ) {
3885+ let env = serde_json:: json!( {
3886+ "hookSpecificOutput" : {
3887+ "hookEventName" : "SessionStart" ,
3888+ "additionalContext" : ctx. trim_end( ) ,
3889+ }
3890+ } ) ;
3891+ println ! ( "{env}" ) ;
3892+ }
3893+
38123894fn auto_open_task_from_prompt (
38133895 events_path : & std:: path:: Path ,
38143896 project_hash : & str ,
0 commit comments