Compare commits

...
4 changed files with 127 additions and 0 deletions
@@ -0,0 +1,68 @@
"""User profile isolation layer for multi-user agents.
Global agent instructions define agent behavior. User preferences must be scoped
by user identity and session, never stored in a shared USER.md.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from hashlib import sha256
from pathlib import Path
import json
from typing import Any
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
def safe_user_key(user_id: str) -> str:
return sha256(user_id.encode("utf-8")).hexdigest()[:24]
@dataclass
class UserProfile:
user_key: str
display_name: str | None = None
preferences: dict[str, Any] = field(default_factory=dict)
created_at: str = field(default_factory=utc_now)
updated_at: str = field(default_factory=utc_now)
def to_dict(self):
return asdict(self)
class UserProfileStore:
def __init__(self, root: str = "runtime/user-profiles"):
self.root = Path(root)
self.root.mkdir(parents=True, exist_ok=True)
def get(self, user_id: str) -> dict[str, Any]:
path = self.root / f"{safe_user_key(user_id)}.json"
if not path.exists():
return UserProfile(user_key=safe_user_key(user_id)).to_dict()
return json.loads(path.read_text(encoding="utf-8"))
def update(self, user_id: str, updates: dict[str, Any]) -> dict[str, Any]:
profile = self.get(user_id)
if "display_name" in updates:
profile["display_name"] = updates["display_name"]
profile.setdefault("preferences", {}).update(updates.get("preferences", {}))
profile["updated_at"] = utc_now()
path = self.root / f"{safe_user_key(user_id)}.json"
path.write_text(json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8")
return profile
def runtime_context(self, user_id: str) -> dict[str, Any]:
profile = self.get(user_id)
return {
"user_profile": profile,
"source": "user_profile_store",
"shared_agent_defaults": "USER.md",
"isolation": True,
}
__all__ = ["UserProfile", "UserProfileStore", "safe_user_key"]
+20
View File
@@ -0,0 +1,20 @@
user_profile_isolation:
enabled: true
principle: one_agent_many_users_requires_scoped_user_context
global_files:
USER.md: agent_defaults_only
user_data:
storage: runtime/user-profiles
isolation_key: user_id_hash
git_commit_required: false
reason: user preferences are runtime data, not shared configuration
forbidden:
- shared_user_preferences_in_USER_MD
- cross_user_profile_access
- user_identity_data_in_public_agent_config
audit:
enabled: true
events:
- profile_created
- profile_updated
- context_loaded
@@ -0,0 +1,30 @@
# CMD-20260709-enable-user-profile-isolation
## Goal
Fix multi-user agent context leakage caused by shared USER.md personalization.
## Rules
- USER.md is agent-level default behavior only.
- User names, preferences and personalized settings must come from user profile storage.
- Every WeChat/user session loads only its own profile by user_id.
## Deploy
1. Deploy user profile isolation runtime layer.
2. Create runtime/user-profiles storage.
3. Migrate existing shared personalization from USER.md into explicit owner profile if needed.
4. Remove user-specific names from shared USER.md.
5. Verify two users cannot read each other's profiles.
## Validation signals
- AIOS_USER_PROFILE_ISOLATION_READY
- AIOS_MULTIUSER_CONTEXT_SAFE
## Governance
- User preferences are runtime data, not Git configuration.
- No personal profile data in shared Git files.
- Audit profile create/update/context load events.
@@ -0,0 +1,9 @@
{
"type": "branch",
"branch": "patch/user-profile-isolation",
"base": "main",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-09T01:19:21Z",
"direct_main_push": false,
"owner_approval_required": true
}