@@ -116,8 +116,8 @@ const SECTION_ICONS: Record<string, string> = {
116116 "Thresholds" : "⚡" ,
117117 "Tags & Cleanup" : "🏷️" ,
118118 "Historian" : "📜" ,
119-
120119 "Memory" : "🧠" ,
120+ "Experimental" : "🧪" ,
121121} ;
122122
123123// Fields that should use range sliders (percentage or threshold values)
@@ -849,6 +849,117 @@ function ConfigForm(props: {
849849 </ div >
850850 ) ;
851851 } ) ( ) }
852+
853+ { /* Experimental Features */ }
854+ { ( ( ) => {
855+ const exp = ( ) => ( getNestedValue ( formData ( ) , "experimental" ) as {
856+ compaction_markers ?: boolean ;
857+ user_memories ?: { enabled ?: boolean ; promotion_threshold ?: number } ;
858+ pin_key_files ?: { enabled ?: boolean ; token_budget ?: number ; min_reads ?: number } ;
859+ } | undefined ) ?? { } ;
860+
861+ const compactionMarkers = ( ) => exp ( ) . compaction_markers ?? false ;
862+ const userMemEnabled = ( ) => exp ( ) . user_memories ?. enabled ?? false ;
863+ const userMemThreshold = ( ) => exp ( ) . user_memories ?. promotion_threshold ?? 3 ;
864+ const pinEnabled = ( ) => exp ( ) . pin_key_files ?. enabled ?? false ;
865+ const pinBudget = ( ) => exp ( ) . pin_key_files ?. token_budget ?? 10000 ;
866+ const pinMinReads = ( ) => exp ( ) . pin_key_files ?. min_reads ?? 4 ;
867+
868+ const updateExp = ( path : string , value : unknown ) => {
869+ const current = exp ( ) ;
870+ if ( path === "compaction_markers" ) {
871+ handleFieldChange ( "experimental" , { ...current , compaction_markers : value } ) ;
872+ } else if ( path . startsWith ( "user_memories." ) ) {
873+ const um = current . user_memories ?? { enabled : false , promotion_threshold : 3 } ;
874+ const field = path . split ( "." ) [ 1 ] ;
875+ handleFieldChange ( "experimental" , { ...current , user_memories : { ...um , [ field ] : value } } ) ;
876+ } else if ( path . startsWith ( "pin_key_files." ) ) {
877+ const pk = current . pin_key_files ?? { enabled : false , token_budget : 10000 , min_reads : 4 } ;
878+ const field = path . split ( "." ) [ 1 ] ;
879+ handleFieldChange ( "experimental" , { ...current , pin_key_files : { ...pk , [ field ] : value } } ) ;
880+ }
881+ } ;
882+
883+ return (
884+ < div class = "config-card full-width" >
885+ < div class = "config-card-header" >
886+ < span class = "config-card-icon" > 🧪</ span >
887+ < span class = "config-card-title" > Experimental</ span >
888+ </ div >
889+ < div class = "config-card-two-col" >
890+ { /* Left column: Compaction Markers + User Memories */ }
891+ < div class = "config-card-content" >
892+ < div class = "config-field" >
893+ < label class = "field-label" >
894+ < span > Compaction Markers</ span >
895+ < span class = "field-hint" > Inject boundary into OpenCode's DB so transform only processes the live tail</ span >
896+ </ label >
897+ < label class = "toggle" >
898+ < input type = "checkbox" checked = { compactionMarkers ( ) } onChange = { ( e ) => updateExp ( "compaction_markers" , e . currentTarget . checked ) } />
899+ < span class = "toggle-slider" />
900+ < span class = "toggle-label" > { compactionMarkers ( ) ? "Enabled" : "Disabled" } </ span >
901+ </ label >
902+ </ div >
903+
904+ < div class = "config-field" style = { { "margin-top" : "16px" } } >
905+ < label class = "field-label" >
906+ < span > User Memories</ span >
907+ < span class = "field-hint" > Extract behavioral observations from historian runs, promote recurring patterns to stable user memories. Requires dreamer.</ span >
908+ </ label >
909+ < label class = "toggle" >
910+ < input type = "checkbox" checked = { userMemEnabled ( ) } onChange = { ( e ) => updateExp ( "user_memories.enabled" , e . currentTarget . checked ) } />
911+ < span class = "toggle-slider" />
912+ < span class = "toggle-label" > { userMemEnabled ( ) ? "Enabled" : "Disabled" } </ span >
913+ </ label >
914+ </ div >
915+
916+ < Show when = { userMemEnabled ( ) } >
917+ < div class = "config-field" style = { { "margin-top" : "8px" , "padding-left" : "12px" } } >
918+ < label class = "field-label" >
919+ < span > Promotion Threshold</ span >
920+ < span class = "field-hint" > Minimum candidate observations before dreamer promotes to stable (2–20)</ span >
921+ </ label >
922+ < input type = "number" class = "field-input" min = { 2 } max = { 20 } value = { userMemThreshold ( ) } onInput = { ( e ) => updateExp ( "user_memories.promotion_threshold" , Number ( e . currentTarget . value ) ) } style = { { width : "80px" } } />
923+ </ div >
924+ </ Show >
925+ </ div >
926+
927+ { /* Right column: Key File Pinning */ }
928+ < div class = "config-card-content" >
929+ < div class = "config-field" >
930+ < label class = "field-label" >
931+ < span > Key File Pinning</ span >
932+ < span class = "field-hint" > Pin frequently-read files into the system prompt so the agent doesn't need to re-read them after drops. Requires dreamer.</ span >
933+ </ label >
934+ < label class = "toggle" >
935+ < input type = "checkbox" checked = { pinEnabled ( ) } onChange = { ( e ) => updateExp ( "pin_key_files.enabled" , e . currentTarget . checked ) } />
936+ < span class = "toggle-slider" />
937+ < span class = "toggle-label" > { pinEnabled ( ) ? "Enabled" : "Disabled" } </ span >
938+ </ label >
939+ </ div >
940+
941+ < Show when = { pinEnabled ( ) } >
942+ < div class = "config-field" style = { { "margin-top" : "8px" , "padding-left" : "12px" } } >
943+ < label class = "field-label" >
944+ < span > Token Budget</ span >
945+ < span class = "field-hint" > Total tokens for all pinned key files (2,000–30,000)</ span >
946+ </ label >
947+ < input type = "number" class = "field-input" min = { 2000 } max = { 30000 } step = { 1000 } value = { pinBudget ( ) } onInput = { ( e ) => updateExp ( "pin_key_files.token_budget" , Number ( e . currentTarget . value ) ) } style = { { width : "100px" } } />
948+ </ div >
949+
950+ < div class = "config-field" style = { { "margin-top" : "8px" , "padding-left" : "12px" } } >
951+ < label class = "field-label" >
952+ < span > Min Reads</ span >
953+ < span class = "field-hint" > Minimum full-read count before a file is eligible for pinning (2–20)</ span >
954+ </ label >
955+ < input type = "number" class = "field-input" min = { 2 } max = { 20 } value = { pinMinReads ( ) } onInput = { ( e ) => updateExp ( "pin_key_files.min_reads" , Number ( e . currentTarget . value ) ) } style = { { width : "80px" } } />
956+ </ div >
957+ </ Show >
958+ </ div >
959+ </ div >
960+ </ div >
961+ ) ;
962+ } ) ( ) }
852963 </ div >
853964 </ Show >
854965 </ div >
0 commit comments