Tool Call Component

self._openclaw_session_phase: str = "idle"

Five Property Functions

is_openclaw_running_background
returns self.is_openclaw_running_background()

is_openclaw_animation_suppressed
i) check _openclaw_session_phase is in {"candiate", "active"}
ii) if _openclaw_session_phase == "candiate" or "active" [true]
iii) if _openclaw_session_phase == "idle" [false]

begin_openclaw_candidate
i) if current == idle
ii) set to self._openclaw_session_phase = "candidate"

confirm_openclaw_active
i) if current == idle or active
ii) then promote to self._openclaw_session_phase = "active"

clear_openclaw_session
set _openclaw_session_phase = "idle"

First Shield

_get_tools_for_trigger

if self.is_openclaw_animation_suppressed():
	# do not allow animations in tool_defs
	tool_defs = [t for t in tool_defs if getattr(t, "category", None) != "animation"]
_run_final_bg

- final_user_turn means run on the final user transcript
- self.begin_openclaw_candidate() is called right before scheduling the background task
- animation suppression starts immediately if OpenClaw is one of the final_user_turn tools
- pre-emptive gate because actual selection of openclaw takes time later in the selector LLM during _run_tool_call()

Second Shield

_execute_tool_calls

- execution-time safety gate
- Even if somehow an animation tool call is leaked, do not call at execution time

Setting State Permanently

_run_for_category

# first, clear the candidate state if the critic result is None
if critic_result is None:
    if getattr(self, "_openclaw_session_phase", "idle") == "candidate":
        self.clear_openclaw_session()
        
# second, check the selected tool
selected_openclaw = any(
	((tc.get("function") or {}).get("name") or "").strip().lower() 
		== "openclaw" 
	for tc in tool_calls 
)

if getattr(self, "_openclaw_session_phase", "idle") == "candidate":
	if selected_openclaw:
		# openclaw tool selected mark as active
		self.confirm_openclaw_active()
	else:
		# openclaw tool not selected, clear the state
		self.clear_openclaw_session()

Openclaw Component

_confirm_openclaw_active # forwarding wrapper
_clear_openclaw_session # forwarding wrapper
_finish_openclaw_session 
	# call animation service and set back to idle
	# clear openclaw session
# start animation on execution start
# steering path is no-op
if animation_service:
	self._logger.info("[OpenClaw] Animation -> Working"
	await animation_service.trigger(
		AnimationBridgeMessage(characterState="Working"),
		source="openclaw",
	)

animation_started = True
# always clean up
# but only if animaiton transition happend and animation service exists
finally:
	await self._finish_openclaw_session(
		animation_service=animation_service,
		animation_started=animation_started,
	)