From f1d8da7008762943f90bf4565c5cb8e639da9e30 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 15 Feb 2026 14:21:49 +0530 Subject: [PATCH] refactor: enhance user context handling and update template mappings --- backend/app/services/openclaw/constants.py | 6 ++- backend/app/services/openclaw/provisioning.py | 29 +++++++++++++- .../app/services/openclaw/provisioning_db.py | 16 ++++++++ backend/scripts/sync_gateway_templates.py | 14 ++++++- backend/templates/BOARD_IDENTITY.md.j2 | 3 +- backend/templates/BOARD_TOOLS.md.j2 | 11 ++++- backend/templates/BOARD_USER.md.j2 | 36 +++++------------ backend/templates/IDENTITY.md | 24 ----------- backend/templates/MAIN_TOOLS.md | 11 ----- backend/templates/MAIN_USER.md | 19 --------- backend/templates/MEMORY.md | 40 ------------------- .../tests/test_agent_provisioning_utils.py | 34 ++++++++++++++++ 12 files changed, 116 insertions(+), 127 deletions(-) delete mode 100644 backend/templates/IDENTITY.md delete mode 100644 backend/templates/MAIN_TOOLS.md delete mode 100644 backend/templates/MAIN_USER.md delete mode 100644 backend/templates/MEMORY.md diff --git a/backend/app/services/openclaw/constants.py b/backend/app/services/openclaw/constants.py index 5e1b48fb..e7c3e433 100644 --- a/backend/app/services/openclaw/constants.py +++ b/backend/app/services/openclaw/constants.py @@ -93,10 +93,12 @@ _SESSION_KEY_PARTS_MIN = SESSION_KEY_PARTS_MIN MAIN_TEMPLATE_MAP = { "AGENTS.md": "BOARD_AGENTS.md.j2", + "IDENTITY.md": "BOARD_IDENTITY.md.j2", "SOUL.md": "BOARD_SOUL.md.j2", + "MEMORY.md": "BOARD_MEMORY.md.j2", "HEARTBEAT.md": "BOARD_HEARTBEAT.md.j2", - "USER.md": "MAIN_USER.md", - "TOOLS.md": "MAIN_TOOLS.md", + "USER.md": "BOARD_USER.md.j2", + "TOOLS.md": "BOARD_TOOLS.md.j2", } BOARD_SHARED_TEMPLATE_MAP = { diff --git a/backend/app/services/openclaw/provisioning.py b/backend/app/services/openclaw/provisioning.py index 39913ee6..79a13ae2 100644 --- a/backend/app/services/openclaw/provisioning.py +++ b/backend/app/services/openclaw/provisioning.py @@ -161,16 +161,41 @@ def _workspace_path(agent: Agent, workspace_root: str) -> str: return f"{root}/workspace-{slugify(key)}" +def _email_local_part(email: str) -> str: + normalized = email.strip() + if not normalized: + return "" + local, _sep, _domain = normalized.partition("@") + return local.strip() or normalized + + +def _display_name(user: User | None) -> str: + if user is None: + return "" + name = (user.name or "").strip() + if name: + return name + return (user.email or "").strip() + + def _preferred_name(user: User | None) -> str: preferred_name = (user.preferred_name or "") if user else "" if preferred_name: preferred_name = preferred_name.strip().split()[0] - return preferred_name + if preferred_name: + return preferred_name + display_name = _display_name(user) + if display_name: + if "@" in display_name: + return _email_local_part(display_name) + return display_name.split()[0] + email = (user.email or "") if user else "" + return _email_local_part(email) def _user_context(user: User | None) -> dict[str, str]: return { - "user_name": (user.name or "") if user else "", + "user_name": _display_name(user), "user_preferred_name": _preferred_name(user), "user_pronouns": (user.pronouns or "") if user else "", "user_timezone": (user.timezone or "") if user else "", diff --git a/backend/app/services/openclaw/provisioning_db.py b/backend/app/services/openclaw/provisioning_db.py index 2272a078..7ed003cd 100644 --- a/backend/app/services/openclaw/provisioning_db.py +++ b/backend/app/services/openclaw/provisioning_db.py @@ -252,9 +252,25 @@ class OpenClawProvisioningService(OpenClawDBService): reset_sessions=options.reset_sessions, rotate_tokens=options.rotate_tokens, force_bootstrap=options.force_bootstrap, + overwrite=options.overwrite, board_id=options.board_id, ) + if template_user is None: + result = _base_result( + gateway, + include_main=options.include_main, + reset_sessions=options.reset_sessions, + ) + _append_sync_error( + result, + message=( + "Organization owner not found (required for gateway template USER.md " + "rendering)." + ), + ) + return result + result = _base_result( gateway, include_main=options.include_main, diff --git a/backend/scripts/sync_gateway_templates.py b/backend/scripts/sync_gateway_templates.py index 97ee109e..d201d428 100644 --- a/backend/scripts/sync_gateway_templates.py +++ b/backend/scripts/sync_gateway_templates.py @@ -23,6 +23,12 @@ def _parse_args() -> argparse.Namespace: default=None, help="Optional Board UUID filter", ) + parser.add_argument( + "--user-id", + type=str, + default=None, + help="Optional User UUID for USER.md rendering context", + ) parser.add_argument( "--include-main", action=argparse.BooleanOptionalAction, @@ -62,6 +68,7 @@ def _parse_args() -> argparse.Namespace: async def _run() -> int: from app.db.session import async_session_maker from app.models.gateways import Gateway + from app.models.users import User from app.services.openclaw.provisioning_db import ( GatewayTemplateSyncOptions, OpenClawProvisioningService, @@ -70,17 +77,22 @@ async def _run() -> int: args = _parse_args() gateway_id = UUID(args.gateway_id) board_id = UUID(args.board_id) if args.board_id else None + user_id = UUID(args.user_id) if args.user_id else None async with async_session_maker() as session: gateway = await session.get(Gateway, gateway_id) if gateway is None: message = f"Gateway not found: {gateway_id}" raise SystemExit(message) + template_user = await session.get(User, user_id) if user_id else None + if user_id and template_user is None: + message = f"User not found: {user_id}" + raise SystemExit(message) result = await OpenClawProvisioningService(session).sync_gateway_templates( gateway, GatewayTemplateSyncOptions( - user=None, + user=template_user, include_main=bool(args.include_main), lead_only=bool(args.lead_only), reset_sessions=bool(args.reset_sessions), diff --git a/backend/templates/BOARD_IDENTITY.md.j2 b/backend/templates/BOARD_IDENTITY.md.j2 index 7a3bb4af..f81e1de4 100644 --- a/backend/templates/BOARD_IDENTITY.md.j2 +++ b/backend/templates/BOARD_IDENTITY.md.j2 @@ -1,6 +1,7 @@ -# IDENTITY.md {% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %} +# IDENTITY.md + ## Core - Name: {{ agent_name }} - Agent ID: {{ agent_id }} diff --git a/backend/templates/BOARD_TOOLS.md.j2 b/backend/templates/BOARD_TOOLS.md.j2 index 7fa4f742..f829332d 100644 --- a/backend/templates/BOARD_TOOLS.md.j2 +++ b/backend/templates/BOARD_TOOLS.md.j2 @@ -1,16 +1,23 @@ -# TOOLS.md +{% set is_main = (is_main_agent | default(false) | string | lower) in ["true", "1", "yes"] %} {% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %} +# TOOLS.md - `BASE_URL={{ base_url }}` - `AUTH_TOKEN={{ auth_token }}` - `AGENT_NAME={{ agent_name }}` - `AGENT_ID={{ agent_id }}` +{% if board_id is defined %} - `BOARD_ID={{ board_id }}` +{% endif %} - `WORKSPACE_ROOT={{ workspace_root }}` +{% if workspace_path is defined %} - `WORKSPACE_PATH={{ workspace_path }}` +{% endif %} - Required tools: `curl`, `jq` -{% if is_lead %} +{% if is_main %} +{% set role_tag = "agent-main" %} +{% elif is_lead %} {% set role_tag = "agent-lead" %} {% else %} {% set role_tag = "agent-worker" %} diff --git a/backend/templates/BOARD_USER.md.j2 b/backend/templates/BOARD_USER.md.j2 index 1f1e28fe..36fe26cc 100644 --- a/backend/templates/BOARD_USER.md.j2 +++ b/backend/templates/BOARD_USER.md.j2 @@ -1,35 +1,21 @@ # USER.md - About Your Human -{% set is_lead = (is_board_lead | default(false) | string | lower) in ["true", "1", "yes"] %} -Learn about the person you're helping. Update this as you go. +_Learn about the person you're helping. Update this as you go._ -## Human Profile -- Name: {{ user_name }} -- Preferred name: {{ user_preferred_name }} -- Pronouns (optional): {{ user_pronouns }} -- Timezone: {{ user_timezone }} -- Notes: {{ user_notes }} +- **Name:** {{ user_name }} +- **What to call them:** {{ user_preferred_name }} +- **Pronouns:** _(optional)_ {{ user_pronouns }} +- **Timezone:** {{ user_timezone }} +- **Notes:** {{ user_notes }} ## Context + +{% if user_context %} {{ user_context }} - -## Board Objective Snapshot -- Board name: {{ board_name }} -- Board type: {{ board_type }} -- Objective: {{ board_objective }} -- Success metrics: {{ board_success_metrics }} -- Target date: {{ board_target_date }} - -{% if is_lead %} -## Intake Notes (Lead) -Use this section for durable, human-provided answers gathered in board chat -(goal clarification, constraints, preferences). Keep it short and factual. - -- [YYYY-MM-DD] ... +{% else %} +_(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)_ {% endif %} --- -The more you know, the better you can help. But remember: you're learning about a person, not building a dossier. Respect the difference. - -If any field is blank, leave it blank. Do not invent values. +The more you know, the better you can help. But remember - you're learning about a person, not building a dossier. Respect the difference. diff --git a/backend/templates/IDENTITY.md b/backend/templates/IDENTITY.md deleted file mode 100644 index 50393668..00000000 --- a/backend/templates/IDENTITY.md +++ /dev/null @@ -1,24 +0,0 @@ -# IDENTITY.md — Who Am I? - -Name: {{ agent_name }} - -Agent ID: {{ agent_id }} - -Creature: {{ identity_role }} - -Vibe: {{ identity_communication_style }} - -Emoji: {{ identity_emoji }} - -{% if identity_purpose %} -Purpose: {{ identity_purpose }} -{% endif %} - -{% if identity_personality %} -Personality: {{ identity_personality }} -{% endif %} - -{% if identity_custom_instructions %} -Custom Instructions: -{{ identity_custom_instructions }} -{% endif %} diff --git a/backend/templates/MAIN_TOOLS.md b/backend/templates/MAIN_TOOLS.md deleted file mode 100644 index 7dea11a9..00000000 --- a/backend/templates/MAIN_TOOLS.md +++ /dev/null @@ -1,11 +0,0 @@ -# TOOLS.md (Main Agent) - -BASE_URL={{ base_url }} -AUTH_TOKEN={{ auth_token }} -AGENT_NAME={{ agent_name }} -AGENT_ID={{ agent_id }} -WORKSPACE_ROOT={{ workspace_root }} - -Notes: -- Use curl for API calls. -- Use Mission Control API for outputs. diff --git a/backend/templates/MAIN_USER.md b/backend/templates/MAIN_USER.md deleted file mode 100644 index 4e21b758..00000000 --- a/backend/templates/MAIN_USER.md +++ /dev/null @@ -1,19 +0,0 @@ -# USER.md (Main Agent) - -## User -- Name: {{ user_name }} -- Preferred name: {{ user_preferred_name }} -- Pronouns: {{ user_pronouns }} -- Timezone: {{ user_timezone }} - -## Context -{{ user_context }} - -## Notes -{{ user_notes }} - -## Mission Control -- Base URL: {{ base_url }} -- Auth token: {{ auth_token }} - -You are the **Main Agent** for this gateway. You are not tied to a specific board. diff --git a/backend/templates/MEMORY.md b/backend/templates/MEMORY.md deleted file mode 100644 index f884b29e..00000000 --- a/backend/templates/MEMORY.md +++ /dev/null @@ -1,40 +0,0 @@ -# MEMORY.md - Long-Term Memory - -This is curated knowledge. Update it during consolidation, not constantly during sessions. - -Use this for durable facts, decisions, constraints, recurring patterns, and evolving identity/preferences. -Update during consolidation, not constantly. - -- Preferences / working style: -- What I learned about the human: -- What changed recently: - -{% if board_id is defined %} -## Board Context (read-only unless board goal changes) - -- Board: {{ board_name }} -- Board type: {{ board_type }} -- Objective: {{ board_objective }} -- Success metrics: {{ board_success_metrics }} -- Target date: {{ board_target_date }} -{% endif %} - -## Constraints / Assumptions - -- [Add constraints that affect decisions and task execution] - -## Decisions (with rationale) - -- [Decision] - [Why] - -## Known Risks / Open Questions - -- [Risk or question] - [Mitigation or next step] - -## Useful References - -- [Commands, paths, URLs (without secrets)] - ---- - -Last consolidated: [YYYY-MM-DD] diff --git a/backend/tests/test_agent_provisioning_utils.py b/backend/tests/test_agent_provisioning_utils.py index 91295e9c..27a88370 100644 --- a/backend/tests/test_agent_provisioning_utils.py +++ b/backend/tests/test_agent_provisioning_utils.py @@ -70,6 +70,40 @@ def test_templates_root_points_to_repo_templates_dir(): assert (root / "BOARD_AGENTS.md.j2").exists() +def test_user_context_uses_email_fallback_when_name_is_missing(): + user = SimpleNamespace( + name=None, + preferred_name=None, + pronouns=None, + timezone=None, + notes=None, + context=None, + email="jane.doe@example.com", + ) + + context = agent_provisioning._user_context(user) + + assert context["user_name"] == "jane.doe@example.com" + assert context["user_preferred_name"] == "jane.doe" + + +def test_user_context_prefers_name_token_when_preferred_name_missing(): + user = SimpleNamespace( + name="Jane Doe", + preferred_name=None, + pronouns=None, + timezone=None, + notes=None, + context=None, + email=None, + ) + + context = agent_provisioning._user_context(user) + + assert context["user_name"] == "Jane Doe" + assert context["user_preferred_name"] == "Jane" + + @dataclass class _GatewayStub: id: UUID