diff --git a/openzero/brain/app.py b/openzero/brain/app.py
index c8343de..7810c62 100644
--- a/openzero/brain/app.py
+++ b/openzero/brain/app.py
@@ -4911,6 +4911,33 @@ def voice_transcribe():
return jsonify(result)
+def deterministic_browser_inspection_reply(state: Dict[str, object]) -> str:
+ """Return the initial read-only browser action without spending a model call."""
+
+ skill_ids = {str(item) for item in state.get("skill_ids") or [] if str(item).strip()}
+ objective = str(state.get("objective") or "")
+ if "browser-tabs" not in skill_ids or requires_tab_pilot_evidence(objective):
+ return ""
+ target_url = objective_browser_target(objective)
+ if not target_url:
+ return ""
+ evidence = dict(state.get("completion_evidence") or {})
+ if (
+ evidence.get("browser_source") == "moltbot"
+ and evidence.get("browser_snapshot_id")
+ and browser_target_matches(
+ target_url, str(evidence.get("browser_requested_url") or "")
+ )
+ ):
+ return ""
+ latest = str(state.get("current_prompt") or "")
+ if any(
+ marker in latest
+ for marker in ("**[MOLTBOT OFFLINE]**", "**[MOLTBOT FAILED]**", "**[MOLTBOT BUSY]**")
+ ):
+ return ""
+ return f'{{"action":"moltbot_browse","url":{json.dumps(target_url)}}}'
+
def autonomous_step_prompt(state: Dict[str, object]) -> str:
"""Keep the true objective in every model turn without fabricating chat."""
@@ -4927,25 +4954,13 @@ def autonomous_step_prompt(state: Dict[str, object]) -> str:
except CatalogError as error:
skill_context = f"Skill catalog unavailable: {error}. Do not propose another operator tool."
browser_directive = ""
- completion_evidence = dict(state.get("completion_evidence") or {})
- objective_text = str(state.get("objective") or "")
- target_url = objective_browser_target(objective_text)
- explicit_tab_pilot = requires_tab_pilot_evidence(objective_text)
- inspection_ready = bool(
- completion_evidence.get("browser_source") == "moltbot"
- and completion_evidence.get("browser_snapshot_id")
- and target_url
- and browser_target_matches(
- target_url, str(completion_evidence.get("browser_requested_url") or "")
+ browser_tool_reply = deterministic_browser_inspection_reply(state)
+ if browser_tool_reply:
+ browser_directive = (
+ "\n\nBROWSER PROOF REQUIRED FOR THIS TURN:\n"
+ "Return exactly this one tool tag with no prose:\n"
+ f"{browser_tool_reply}"
)
- )
- if "browser-tabs" in skill_ids and not explicit_tab_pilot and not inspection_ready:
- if target_url:
- browser_directive = (
- "\n\nBROWSER PROOF REQUIRED FOR THIS TURN:\n"
- "Return exactly this one tool tag with no prose:\n"
- f'{{"action":"moltbot_browse","url":{json.dumps(target_url)}}}'
- )
return (
"[AUTONOMOUS RUN CHECKPOINT]\n"
f"Run id: {state.get('id')}\n"
@@ -5071,20 +5086,34 @@ def execute_autonomous_run(
break
state = AUTONOMOUS_RUN_STORE.get(run_id)
- step_prompt = autonomous_step_prompt(state)
- AUTONOMOUS_RUN_STORE.append_trace(
- run_id,
- "model_request",
- step=int((state.get("usage") or {}).get("steps") or 0) + 1,
- prompt=step_prompt,
- )
- model_agent_mode = agent_mode if state.get("skill_ids") else "conversation"
- reply = autonomous_model_reply(step_prompt, comp_mode, model_agent_mode, history, upload_context)
- state = AUTONOMOUS_RUN_STORE.checkpoint(
- run_id,
- usage_delta={"steps": 1, "model_calls": 1},
- )
- AUTONOMOUS_RUN_STORE.append_trace(run_id, "model_reply", reply=reply)
+ deterministic_reply = deterministic_browser_inspection_reply(state)
+ if deterministic_reply:
+ reply = deterministic_reply
+ AUTONOMOUS_RUN_STORE.append_trace(
+ run_id,
+ "deterministic_tool_proposal",
+ tool="moltbot_browse",
+ reason="explicit_url_inspection",
+ )
+ state = AUTONOMOUS_RUN_STORE.checkpoint(
+ run_id,
+ usage_delta={"steps": 1},
+ )
+ else:
+ step_prompt = autonomous_step_prompt(state)
+ AUTONOMOUS_RUN_STORE.append_trace(
+ run_id,
+ "model_request",
+ step=int((state.get("usage") or {}).get("steps") or 0) + 1,
+ prompt=step_prompt,
+ )
+ model_agent_mode = agent_mode if state.get("skill_ids") else "conversation"
+ reply = autonomous_model_reply(step_prompt, comp_mode, model_agent_mode, history, upload_context)
+ state = AUTONOMOUS_RUN_STORE.checkpoint(
+ run_id,
+ usage_delta={"steps": 1, "model_calls": 1},
+ )
+ AUTONOMOUS_RUN_STORE.append_trace(run_id, "model_reply", reply=reply)
post_allowed, post_reason = AUTONOMOUS_RUN_STORE.budget_guard(run_id)
if not post_allowed and post_reason in {"revoked", "stop_requested"}:
diff --git a/openzero/tests/test_autonomous_runtime.py b/openzero/tests/test_autonomous_runtime.py
index fb4f6f9..a8ff19d 100644
--- a/openzero/tests/test_autonomous_runtime.py
+++ b/openzero/tests/test_autonomous_runtime.py
@@ -1108,6 +1108,9 @@ def test_every_continuation_prompt_names_the_original_objective(self):
self.assertIn("`text_generation` is not a tool.", self.source)
self.assertIn("Never repeat or expose this checkpoint.", self.source)
self.assertIn("BROWSER PROOF REQUIRED FOR THIS TURN", self.source)
+ self.assertIn("def deterministic_browser_inspection_reply(", self.source)
+ self.assertIn('"deterministic_tool_proposal"', self.source)
+ self.assertIn('usage_delta={"steps": 1}', self.source)
self.assertIn('"action":"moltbot_browse"', self.source)
def test_plain_conversation_and_model_format_recovery_are_guarded(self):