2222
2323from proteus .core .adapter import ActionEvent
2424
25- PROTOCOL_VERSION = 1
25+ PROTOCOL_VERSION = 2
2626MODES = frozenset ({"native" , "framework" , "none" })
2727# Containerized coding harnesses commonly restrict writes to /workspace. The host source
2828# is still `<run>/harness`, while adapters bind this external directory over the nested
3131CONTAINER_HANDOFF = f"{ CONTAINER_ROOT } /handoff.md"
3232MAX_CONTENT_CHARS = 12_000
3333MAX_PRIOR_CHARS = 6_000
34+ MAX_CONTROLLER_NOTICE_CHARS = 3_000
35+ CONTROLLER_NOTICE_START = "<!-- proteus-controller-notice:start -->"
36+ CONTROLLER_NOTICE_END = "<!-- proteus-controller-notice:end -->"
3437
3538_SECRET_PATTERNS = (
3639 re .compile (r"\bsk-[A-Za-z0-9_-]{12,}\b" ),
@@ -50,10 +53,15 @@ def validate_mode(mode: str) -> str:
5053 return mode
5154
5255
53- def framework_prompt (phase : str ) -> str :
56+ def framework_prompt (phase : str , * , goal_present : bool = True ) -> str :
5457 """The portable protocol text appended to a framework-continuity phase prompt."""
58+ observe_action = (
59+ "Record objective-relevant findings and evidence for propose."
60+ if goal_present else
61+ "Record findings, evidence, and uncertainties for propose."
62+ )
5563 action = {
56- "observe" : "Record objective-relevant findings and evidence for propose." ,
64+ "observe" : observe_action ,
5765 "propose" : "Replace it with one scoped file-and-test plan for act." ,
5866 "act" : "Replace it with edits attempted, files changed, and verification still needed." ,
5967 "reflect" : "Replace it with validation results, unresolved risks, and the next step." ,
@@ -83,6 +91,39 @@ def _clip(text: str, limit: int = MAX_CONTENT_CHARS) -> str:
8391 return "[earlier handoff content omitted]\n \n " + text [- limit :]
8492
8593
94+ def _strip_controller_notice (text : str ) -> str :
95+ """Remove framework-owned notices before replacing or clearing one."""
96+ pattern = re .compile (
97+ rf"{ re .escape (CONTROLLER_NOTICE_START )} .*?"
98+ rf"{ re .escape (CONTROLLER_NOTICE_END )} " ,
99+ re .DOTALL ,
100+ )
101+ return pattern .sub ("" , text ).strip ()
102+
103+
104+ def _with_controller_notice (text : str , notice : str ) -> str :
105+ """Compose one durable controller notice with an agent/fallback handoff.
106+
107+ The notice is kept in a separately owned file, so an agent that replaces
108+ ``handoff.md`` cannot accidentally discard a still-active boundary failure. Markers
109+ make repeated phase transitions idempotent and let a successful repair remove stale
110+ notices from the live handoff without rewriting archived history.
111+ """
112+ base = _strip_controller_notice (text )
113+ clean_notice = _clip (notice , MAX_CONTROLLER_NOTICE_CHARS ) if notice .strip () else ""
114+ if not clean_notice :
115+ return _clip (base )
116+ block = (
117+ f"{ CONTROLLER_NOTICE_START } \n "
118+ "# Proteus controller notice\n \n "
119+ f"{ clean_notice } \n "
120+ f"{ CONTROLLER_NOTICE_END } "
121+ )
122+ remaining = max (1_000 , MAX_CONTENT_CHARS - len (block ) - 2 )
123+ clipped_base = _clip (base , remaining ) if base else ""
124+ return f"{ block } \n \n { clipped_base } " if clipped_base else block
125+
126+
86127def _event_detail (event : ActionEvent ) -> str :
87128 preferred = ("file_path" , "path" , "pattern" , "query" , "command" )
88129 detail = next ((str (event .params [k ]) for k in preferred if event .params .get (k )), "" )
@@ -124,18 +165,58 @@ def __init__(self, run_root: Path):
124165 self .history = self .root / "handoffs"
125166 self .current = self .root / "handoff.md"
126167 self .latest = self .root / "latest.md"
168+ self .controller_notice = self .root / "controller-notice.md"
127169
128170 def initialise (self ) -> None :
129171 self .history .mkdir (parents = True , exist_ok = True )
130172 meta = self .root / "continuity.json"
131- if not meta .exists ():
132- self ._atomic_text (meta , json .dumps ({
133- "protocol" : "proteus-phase-continuity" ,
134- "version" : PROTOCOL_VERSION ,
135- "mode" : "framework" ,
136- "container_handoff" : CONTAINER_HANDOFF ,
137- "persists_raw_reasoning" : False ,
138- }, indent = 2 ) + "\n " )
173+ desired = {
174+ "protocol" : "proteus-phase-continuity" ,
175+ "version" : PROTOCOL_VERSION ,
176+ "mode" : "framework" ,
177+ "container_handoff" : CONTAINER_HANDOFF ,
178+ "persists_raw_reasoning" : False ,
179+ "persistent_controller_notices" : True ,
180+ }
181+ try :
182+ current = json .loads (meta .read_text (encoding = "utf-8" ))
183+ except (OSError , json .JSONDecodeError ):
184+ current = None
185+ if current != desired :
186+ self ._atomic_text (meta , json .dumps (desired , indent = 2 ) + "\n " )
187+
188+ def set_controller_notice (self , notice : str ) -> None :
189+ """Persist a redacted framework fact across phases until explicitly cleared."""
190+ self .initialise ()
191+ clean = _clip (notice , MAX_CONTROLLER_NOTICE_CHARS )
192+ if not clean :
193+ self .clear_controller_notice ()
194+ return
195+ self ._atomic_text (self .controller_notice , clean + "\n " )
196+ self ._sync_live_notice (clean )
197+
198+ def clear_controller_notice (self ) -> None :
199+ """Clear a resolved notice from live continuity while preserving history."""
200+ self .initialise ()
201+ self .controller_notice .unlink (missing_ok = True )
202+ self ._sync_live_notice ("" )
203+
204+ def _read_controller_notice (self ) -> str :
205+ try :
206+ return _clip (
207+ self .controller_notice .read_text (encoding = "utf-8" ),
208+ MAX_CONTROLLER_NOTICE_CHARS ,
209+ )
210+ except OSError :
211+ return ""
212+
213+ def _sync_live_notice (self , notice : str ) -> None :
214+ for path in (self .latest , self .current ):
215+ try :
216+ content = path .read_text (encoding = "utf-8" )
217+ except OSError :
218+ continue
219+ self ._atomic_text (path , _with_controller_notice (content , notice ) + "\n " )
139220
140221 def begin (self , episode : int , phase : str ) -> HandoffStart :
141222 """Expose the latest archived handoff and return a modification baseline."""
@@ -147,7 +228,7 @@ def begin(self, episode: int, phase: str) -> HandoffStart:
147228 "No prior phase has run. Inspect the current harness and write the first "
148229 "handoff before this phase ends.\n "
149230 )
150- previous = _clip (previous )
231+ previous = _with_controller_notice (previous , self . _read_controller_notice () )
151232 self ._atomic_text (self .current , previous + ("\n " if previous else "" ))
152233 digest = hashlib .sha256 (self .current .read_bytes ()).hexdigest ()
153234 return HandoffStart (episode , phase , previous , digest )
@@ -189,6 +270,8 @@ def finish(self, start: HandoffStart, events: Sequence[ActionEvent] = (),
189270 content = _clip (current ) if explicit else fallback_handoff (
190271 start .previous , events , interrupted
191272 )
273+ notice = self ._read_controller_notice ()
274+ content = _with_controller_notice (content , notice )
192275 calls = [_event_detail (event ) for event in events if event .tool ][- 30 :]
193276 phase_dir = self .history / f"ep{ start .episode :03d} "
194277 phase_dir .mkdir (parents = True , exist_ok = True )
@@ -203,6 +286,7 @@ def finish(self, start: HandoffStart, events: Sequence[ActionEvent] = (),
203286 "attempt" : attempt ,
204287 "source" : "agent" if explicit else "framework-fallback" ,
205288 "interrupted" : bool (interrupted ),
289+ "controller_notice" : bool (notice ),
206290 "content" : content ,
207291 "tool_calls" : calls ,
208292 }
0 commit comments