@@ -2140,6 +2140,25 @@ fn main() -> Result<()> {
21402140 bundle. push_str ( & reminder) ;
21412141 bundle. push_str ( "\n \n " ) ;
21422142 }
2143+ // Advisory (the hook can't force it): suggest the main agent
2144+ // delegate the just-compacted segment to the in-session
2145+ // distiller subagent, which backfills missed reasoning from
2146+ // the transcript file (which survives compaction) for the
2147+ // active task(s). Background → never blocks. Gated off by
2148+ // TJ_DISTILLER_HINT=0 for users who don't want it.
2149+ if std:: env:: var ( "TJ_DISTILLER_HINT" ) . as_deref ( ) != Ok ( "0" ) {
2150+ let transcript_hint = payload
2151+ . get ( "transcript_path" )
2152+ . and_then ( |v| v. as_str ( ) )
2153+ . map ( |p| format ! ( " (transcript: {p})" ) )
2154+ . unwrap_or_default ( ) ;
2155+ bundle. push_str ( & format ! (
2156+ "[task-journal] A compaction just occurred. If decisions, rejections, \
2157+ or findings from before it are not yet in the journal for the active task(s) above, delegate to \
2158+ the `task-journal-distiller` subagent to capture them from the transcript{transcript_hint}. It \
2159+ runs in the background and won't block you; it only fills gaps and never closes tasks.\n \n "
2160+ ) ) ;
2161+ }
21432162 }
21442163 for tc in & recent {
21452164 let pack = tj_core:: pack:: assemble (
@@ -4149,31 +4168,34 @@ fn compute_savings(
41494168 } )
41504169}
41514170
4152- /// Format a token count compactly: 980 → "980", 3_240 → "3.2k", 88_000 → "88k".
4171+ /// Format a token count compactly: 980 → "980", 3_240 → "3.2k", 88_000 → "88k",
4172+ /// 2_760_000 → "2.8M".
41534173fn fmt_tokens ( n : u64 ) -> String {
41544174 if n < 1_000 {
41554175 n. to_string ( )
41564176 } else if n < 100_000 {
41574177 format ! ( "{:.1}k" , n as f64 / 1_000.0 )
4158- } else {
4178+ } else if n < 1_000_000 {
41594179 format ! ( "{}k" , n / 1_000 )
4180+ } else {
4181+ format ! ( "{:.1}M" , n as f64 / 1_000_000.0 )
41604182 }
41614183}
41624184
41634185/// Human spent/saved suffix for a finalize line, e.g.
41644186/// " | spent 3.2k tok ($0.0012) · saved ~88k→1.5k tok (59×)".
41654187fn stats_suffix ( spent : & tj_core:: llm:: LlmUsage , saved : & Option < Savings > ) -> String {
41664188 let mut parts = Vec :: new ( ) ;
4167- if spent . total_tokens ( ) > 0 {
4168- let cost = match spent . cost_usd {
4169- Some ( c ) if c > 0.0 => format ! ( " (${c:.4})" ) ,
4170- _ => String :: new ( ) ,
4171- } ;
4172- parts. push ( format ! (
4173- "spent {} tok{}" ,
4174- fmt_tokens( spent. total_tokens( ) ) ,
4175- cost
4176- ) ) ;
4189+ // claude -p reports a (notional) dollar cost but muddy token counts — its
4190+ // big prompt lands in `cache_creation`, not `input_tokens` — so lead with
4191+ // the cost there. API backends report no cost but clean tokens, so show
4192+ // those instead.
4193+ match spent . cost_usd {
4194+ Some ( c ) if c > 0.0 => parts. push ( format ! ( "cost ${c:.4}" ) ) ,
4195+ _ if spent . total_tokens ( ) > 0 => {
4196+ parts . push ( format ! ( "spent {} tok" , fmt_tokens( spent. total_tokens( ) ) ) )
4197+ }
4198+ _ => { }
41774199 }
41784200 if let Some ( s) = saved {
41794201 if s. pack_tokens > 0 && s. raw_tokens > s. pack_tokens {
@@ -4395,6 +4417,21 @@ fn finalize_one_task(
43954417 Ok ( out)
43964418}
43974419
4420+ /// A one-line nudge shown when a cost-reporting backend (claude -p) was used:
4421+ /// the same Haiku via a direct API skips Claude Code's harness overhead. Only
4422+ /// claude -p reports a non-zero `cost_usd`, so this fires for it alone.
4423+ fn backend_cost_tip ( cost : Option < f64 > ) -> Option < String > {
4424+ match cost {
4425+ Some ( c) if c > 0.0 => Some (
4426+ "tip: that cost is claude -p's Claude Code overhead (notional under a \
4427+ subscription). For ~50× cheaper per task, use --backend anthropic (direct Haiku API, \
4428+ needs ANTHROPIC_API_KEY) — or --backend ollama for free, local."
4429+ . to_string ( ) ,
4430+ ) ,
4431+ _ => None ,
4432+ }
4433+ }
4434+
43984435/// Human-readable one-liner for a finalize result.
43994436fn print_finalize_outcome ( task_id : & str , out : & FinalizeOutcome ) {
44004437 if out. skipped_no_backend {
@@ -4458,6 +4495,9 @@ fn run_complete_single(
44584495 } ;
44594496 let out = finalize_one_task ( & ctx, task_id, enrich, dry_run, backend) ?;
44604497 print_finalize_outcome ( task_id, & out) ;
4498+ if let Some ( tip) = backend_cost_tip ( out. spent . cost_usd ) {
4499+ eprintln ! ( "{tip}" ) ;
4500+ }
44614501 Ok ( ( ) )
44624502}
44634503
@@ -4604,6 +4644,9 @@ fn run_complete_batch(
46044644 totals. trim_start_matches( " | " )
46054645 ) ;
46064646 }
4647+ if let Some ( tip) = backend_cost_tip ( total_spent. cost_usd ) {
4648+ eprintln ! ( "{tip}" ) ;
4649+ }
46074650
46084651 if !left_open. is_empty ( ) {
46094652 println ! ( "\n Left open ({}):" , left_open. len( ) ) ;
@@ -5682,10 +5725,26 @@ mod inline_tests {
56825725 pack_tokens : 1_500 ,
56835726 } ) ;
56845727 let s = stats_suffix ( & spent, & saved) ;
5685- assert ! ( s. contains( "spent 1.5k tok ($0.0012)" ) , "{s}" ) ;
5728+ // Cost-reporting backend (claude -p) → lead with cost, not muddy tokens.
5729+ assert ! ( s. contains( "cost $0.0012" ) , "{s}" ) ;
56865730 assert ! ( s. contains( "saved ~90.0k→1.5k tok (60×)" ) , "{s}" ) ;
56875731 }
56885732
5733+ #[ test]
5734+ fn stats_suffix_shows_tokens_for_costless_backend ( ) {
5735+ // API backend reports clean tokens, no cost → show the token count.
5736+ let spent = tj_core:: llm:: LlmUsage {
5737+ input_tokens : 1800 ,
5738+ output_tokens : 200 ,
5739+ cost_usd : None ,
5740+ } ;
5741+ assert_eq ! (
5742+ stats_suffix( & spent, & None ) ,
5743+ " | spent 2.0k tok" ,
5744+ "API backend should show tokens"
5745+ ) ;
5746+ }
5747+
56895748 #[ test]
56905749 fn stats_suffix_empty_when_nothing_to_report ( ) {
56915750 let spent = tj_core:: llm:: LlmUsage :: default ( ) ;
0 commit comments