99// markers, files/commands, verification, dead ends, next actions, plus a
1010// verbatim exact-facts appendix), persisted as a context-store blob by the
1111// reactor under one STABLE key that every fold overwrites;
12- // - a thin spine that stays in the live prompt: goal one-liner, top
13- // constraints/decisions, a cumulative evidence echo, activated tools, and
14- // an explicit pointer (tool-output:/// URI) so the agent can re-read the
15- // full file when a detail is missing.
12+ // - a thin spine that stays in the live prompt: goal one-liner (cut at a
13+ // token boundary, never mid-token), standing output-format tokens uncut,
14+ // top constraints/decisions, a cumulative evidence echo, activated tools,
15+ // and an explicit pointer (tool-output:/// URI) so the agent can re-read
16+ // the full operator ask and output contract when a detail is missing.
1617//
1718// Everything the file carries is copied verbatim out of the folded turns —
1819// never paraphrased — so exact-required facts (paths, commands, counts, user
@@ -44,6 +45,7 @@ export const HANDOFF_LATEST_KEY = "compaction-handoff-latest.md";
4445
4546const HANDOFF_TOOLS_LINE_PREFIX =
4647 "Tools still activated and callable directly (no tool_search needed): " ;
48+ const HANDOFF_OUTPUT_LINE_PREFIX = "Output: " ;
4749
4850// Structured handoff artifact: the fat file's sections. Every entry is a
4951// verbatim excerpt from the folded turns (or carried verbatim from a prior
@@ -94,10 +96,32 @@ const VERIFICATION_SIGNAL = /test|check|lint|build|typecheck|verify/i;
9496// the lines between.
9597const EVIDENCE_TOKEN = / \[ \[ e v i d e n c e : [ ^ [ \] \r \n ] + \] \] / g;
9698
99+ // Standing reply-contract tokens (FILES_DONE=..., SUMMARY=...). The spine
100+ // must keep these whole; a char slice is how they become FILES_DO.
101+ const OUTPUT_FORMAT_TOKEN = / \b [ A - Z ] [ A - Z 0 - 9 _ ] + = \S + / g;
102+
97103function oneLine ( text : string , maxChars : number ) : string {
98104 return text . replace ( / \s + / g, " " ) . trim ( ) . slice ( 0 , maxChars ) ;
99105}
100106
107+ function outputFormatTokens ( text : string ) : string [ ] {
108+ const found : string [ ] = [ ] ;
109+ for ( const match of text . matchAll ( OUTPUT_FORMAT_TOKEN ) ) {
110+ const token = match [ 0 ] ;
111+ if ( token !== undefined && ! found . includes ( token ) ) found . push ( token ) ;
112+ }
113+ return found ;
114+ }
115+
116+ function cutSpineGoal ( text : string ) : string {
117+ const line = text . replace ( / \s + / g, " " ) . trim ( ) ;
118+ if ( line . length <= SPINE_GOAL_CHARS ) return line ;
119+ const head = line . slice ( 0 , SPINE_GOAL_CHARS ) ;
120+ const lastSpace = head . lastIndexOf ( " " ) ;
121+ const kept = lastSpace > 0 ? head . slice ( 0 , lastSpace ) : "" ;
122+ return kept . length > 0 ? `${ kept } ${ SPINE_CUT_SENTINEL } ` : SPINE_CUT_SENTINEL ;
123+ }
124+
101125function cutSpineItem ( text : string ) : string {
102126 const line = text . replace ( / \s + / g, " " ) . trim ( ) ;
103127 if ( line . length <= MAX_SPINE_ITEM_CHARS ) return line ;
@@ -213,6 +237,14 @@ function parseSpineText(text: string): CarriedFacts {
213237 if ( trimmed . startsWith ( "Goal: " ) ) {
214238 const goal = trimmed . slice ( "Goal: " . length ) . trim ( ) ;
215239 if ( goal . length > 0 ) carried . goal = goal ;
240+ } else if ( trimmed . startsWith ( HANDOFF_OUTPUT_LINE_PREFIX ) ) {
241+ for ( const token of outputFormatTokens (
242+ trimmed . slice ( HANDOFF_OUTPUT_LINE_PREFIX . length ) ,
243+ ) ) {
244+ if ( carried . goal === undefined ) carried . goal = token ;
245+ else if ( ! carried . goal . includes ( token ) )
246+ carried . goal = `${ carried . goal } ${ token } ` ;
247+ }
216248 } else if ( trimmed . startsWith ( "Constraints: " ) ) {
217249 for ( const constraint of trimmed
218250 . slice ( "Constraints: " . length )
@@ -691,7 +723,7 @@ export function extractHandoffArtifact(
691723 return {
692724 artifact : checked ,
693725 spine : {
694- goal : extractedGoal ,
726+ goal : fileGoal ,
695727 constraints : mergeUnique (
696728 carried . constraints ,
697729 freshConstraints ,
@@ -739,19 +771,20 @@ export function renderHandoffFile(
739771}
740772
741773/**
742- * Render the thin live spine. Goal, constraints/decisions, the cumulative
743- * evidence echo, activated tools, and the explicit file pointer. Starts with
744- * COMPACTED_PREFIX so the next fold treats it as a foldable handoff turn.
745- * Counts, file lists, and next actions stay in the fat file.
774+ * Render the thin live spine. Goal (token-boundary cut), standing output
775+ * contract tokens uncut, constraints/decisions, the cumulative evidence echo,
776+ * activated tools, and the explicit file pointer. Starts with COMPACTED_PREFIX
777+ * so the next fold treats it as a foldable handoff turn. Counts, file lists,
778+ * and next actions stay in the fat file.
746779 */
747780export function renderHandoffSpine (
748781 spine : SpineFacts ,
749782 pointerUri : string ,
750783) : string {
751- const lines = [
752- COMPACTED_PREFIX ,
753- `Goal: ${ oneLine ( spine . goal , SPINE_GOAL_CHARS ) } ` ,
754- ] ;
784+ const lines = [ COMPACTED_PREFIX , `Goal: ${ cutSpineGoal ( spine . goal ) } ` ] ;
785+ const output = outputFormatTokens ( spine . goal ) ;
786+ if ( output . length > 0 )
787+ lines . push ( ` ${ HANDOFF_OUTPUT_LINE_PREFIX } ${ output . join ( " " ) } ` ) ;
755788 if ( spine . constraints . length > 0 )
756789 lines . push (
757790 `Constraints: ${ spine . constraints
@@ -774,7 +807,7 @@ export function renderHandoffSpine(
774807 `${ HANDOFF_TOOLS_LINE_PREFIX } ${ spine . activatedTools . join ( ", " ) } ` ,
775808 ) ;
776809 lines . push (
777- `Handoff: ${ pointerUri } — re-read with read_file (offset/limit) for full detail: decisions, verification, dead ends, next actions.` ,
810+ `Handoff: ${ pointerUri } — re-read with read_file (offset/limit) for the full operator ask, output contract, decisions, verification, dead ends, next actions.` ,
778811 ) ;
779812 return lines . join ( "\n" ) ;
780813}
0 commit comments