refactor: centralize session key generation with new helper functions

This commit is contained in:
Abhimanyu Saharan
2026-02-11 01:56:16 +05:30
parent 275cc6f473
commit 77347534fd
5 changed files with 102 additions and 14 deletions

View File

@@ -0,0 +1,40 @@
"""Deterministic session-key helpers for OpenClaw agents.
Session keys are part of Mission Control's contract with the OpenClaw gateway.
Centralize the string formats here to avoid drift across provisioning, DB workflows,
and API-facing services.
"""
from __future__ import annotations
from uuid import UUID
from app.services.openclaw.constants import AGENT_SESSION_PREFIX
from app.services.openclaw.shared import GatewayAgentIdentity
def gateway_main_session_key(gateway_id: UUID) -> str:
"""Return the deterministic session key for a gateway-main agent."""
return GatewayAgentIdentity.session_key_for_id(gateway_id)
def board_lead_session_key(board_id: UUID) -> str:
"""Return the deterministic session key for a board lead agent."""
return f"{AGENT_SESSION_PREFIX}:lead-{board_id}:main"
def board_agent_session_key(agent_id: UUID) -> str:
"""Return the deterministic session key for a non-lead, board-scoped agent."""
return f"{AGENT_SESSION_PREFIX}:mc-{agent_id}:main"
def board_scoped_session_key(
*,
agent_id: UUID,
board_id: UUID,
is_board_lead: bool,
) -> str:
"""Return the deterministic session key for a board-scoped agent."""
if is_board_lead:
return board_lead_session_key(board_id)
return board_agent_session_key(agent_id)

View File

@@ -40,6 +40,10 @@ from app.services.openclaw.gateway_rpc import (
) )
from app.services.openclaw.internal.agent_key import agent_key as _agent_key from app.services.openclaw.internal.agent_key import agent_key as _agent_key
from app.services.openclaw.internal.agent_key import slugify from app.services.openclaw.internal.agent_key import slugify
from app.services.openclaw.internal.session_keys import (
board_agent_session_key,
board_lead_session_key,
)
from app.services.openclaw.shared import GatewayAgentIdentity from app.services.openclaw.shared import GatewayAgentIdentity
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -251,8 +255,8 @@ def _session_key(agent: Agent) -> str:
""" """
if agent.is_board_lead and agent.board_id is not None: if agent.is_board_lead and agent.board_id is not None:
return f"agent:lead-{agent.board_id}:main" return board_lead_session_key(agent.board_id)
return f"agent:mc-{agent.id}:main" return board_agent_session_key(agent.id)
def _render_agent_files( def _render_agent_files(

View File

@@ -45,7 +45,6 @@ from app.schemas.gateways import GatewayTemplatesSyncError, GatewayTemplatesSync
from app.services.activity_log import record_activity from app.services.activity_log import record_activity
from app.services.openclaw.constants import ( from app.services.openclaw.constants import (
_TOOLS_KV_RE, _TOOLS_KV_RE,
AGENT_SESSION_PREFIX,
DEFAULT_HEARTBEAT_CONFIG, DEFAULT_HEARTBEAT_CONFIG,
OFFLINE_AFTER, OFFLINE_AFTER,
) )
@@ -68,6 +67,10 @@ from app.services.openclaw.gateway_rpc import (
) )
from app.services.openclaw.internal.agent_key import agent_key as _agent_key from app.services.openclaw.internal.agent_key import agent_key as _agent_key
from app.services.openclaw.internal.retry import GatewayBackoff from app.services.openclaw.internal.retry import GatewayBackoff
from app.services.openclaw.internal.session_keys import (
board_agent_session_key,
board_lead_session_key,
)
from app.services.openclaw.policies import OpenClawAuthorizationPolicy from app.services.openclaw.policies import OpenClawAuthorizationPolicy
from app.services.openclaw.provisioning import ( from app.services.openclaw.provisioning import (
OpenClawGatewayControlPlane, OpenClawGatewayControlPlane,
@@ -139,7 +142,7 @@ class OpenClawProvisioningService(OpenClawDBService):
@staticmethod @staticmethod
def lead_session_key(board: Board) -> str: def lead_session_key(board: Board) -> str:
return f"agent:lead-{board.id}:main" return board_lead_session_key(board.id)
@staticmethod @staticmethod
def lead_agent_name(_: Board) -> str: def lead_agent_name(_: Board) -> str:
@@ -732,8 +735,8 @@ class AgentLifecycleService(OpenClawDBService):
detail="Gateway main agent session key is required", detail="Gateway main agent session key is required",
) )
if agent.is_board_lead: if agent.is_board_lead:
return f"{AGENT_SESSION_PREFIX}:lead-{agent.board_id}:main" return board_lead_session_key(agent.board_id)
return f"{AGENT_SESSION_PREFIX}:mc-{agent.id}:main" return board_agent_session_key(agent.id)
@classmethod @classmethod
def workspace_path(cls, agent_name: str, workspace_root: str | None) -> str: def workspace_path(cls, agent_name: str, workspace_root: str | None) -> str:

View File

@@ -8,9 +8,7 @@ from typing import TYPE_CHECKING
from uuid import UUID from uuid import UUID
from fastapi import HTTPException, status from fastapi import HTTPException, status
from sqlmodel import col
from app.models.agents import Agent
from app.models.boards import Board from app.models.boards import Board
from app.schemas.gateway_api import ( from app.schemas.gateway_api import (
GatewayResolveQuery, GatewayResolveQuery,
@@ -31,6 +29,7 @@ from app.services.openclaw.gateway_rpc import (
send_message, send_message,
) )
from app.services.openclaw.policies import OpenClawAuthorizationPolicy from app.services.openclaw.policies import OpenClawAuthorizationPolicy
from app.services.openclaw.shared import GatewayAgentIdentity
from app.services.organizations import require_board_access from app.services.organizations import require_board_access
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -124,12 +123,7 @@ class GatewaySessionService(OpenClawDBService):
await require_board_access(self.session, user=user, board=board, write=False) await require_board_access(self.session, user=user, board=board, write=False)
gateway = await require_gateway_for_board(self.session, board) gateway = await require_gateway_for_board(self.session, board)
config = gateway_client_config(gateway) config = gateway_client_config(gateway)
main_agent = ( main_session = GatewayAgentIdentity.session_key(gateway)
await Agent.objects.filter_by(gateway_id=gateway.id)
.filter(col(Agent.board_id).is_(None))
.first(self.session)
)
main_session = main_agent.openclaw_session_id if main_agent else None
return ( return (
board, board,
config, config,

View File

@@ -0,0 +1,47 @@
# ruff: noqa: S101
"""Unit tests for deterministic OpenClaw session-key helpers."""
from __future__ import annotations
from uuid import UUID
from app.services.openclaw.internal.session_keys import (
board_agent_session_key,
board_lead_session_key,
board_scoped_session_key,
gateway_main_session_key,
)
from app.services.openclaw.shared import GatewayAgentIdentity
def test_gateway_main_session_key_matches_gateway_identity() -> None:
gateway_id = UUID("00000000-0000-0000-0000-000000000123")
assert gateway_main_session_key(gateway_id) == GatewayAgentIdentity.session_key_for_id(gateway_id)
def test_board_lead_session_key_format() -> None:
board_id = UUID("00000000-0000-0000-0000-000000000456")
assert board_lead_session_key(board_id) == f"agent:lead-{board_id}:main"
def test_board_agent_session_key_format() -> None:
agent_id = UUID("00000000-0000-0000-0000-000000000789")
assert board_agent_session_key(agent_id) == f"agent:mc-{agent_id}:main"
def test_board_scoped_session_key_selects_lead() -> None:
agent_id = UUID("00000000-0000-0000-0000-000000000001")
board_id = UUID("00000000-0000-0000-0000-000000000002")
assert (
board_scoped_session_key(agent_id=agent_id, board_id=board_id, is_board_lead=True)
== board_lead_session_key(board_id)
)
def test_board_scoped_session_key_selects_non_lead() -> None:
agent_id = UUID("00000000-0000-0000-0000-000000000001")
board_id = UUID("00000000-0000-0000-0000-000000000002")
assert (
board_scoped_session_key(agent_id=agent_id, board_id=board_id, is_board_lead=False)
== board_agent_session_key(agent_id)
)