refactor: enhance user context handling and update template mappings

This commit is contained in:
Abhimanyu Saharan
2026-02-15 14:21:49 +05:30
parent 1996e21695
commit f1d8da7008
12 changed files with 116 additions and 127 deletions

View File

@@ -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 = {

View File

@@ -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]
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 "",

View File

@@ -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,

View File

@@ -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),

View File

@@ -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 }}

View File

@@ -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" %}

View File

@@ -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.

View File

@@ -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 %}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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]

View File

@@ -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