Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ced8bc1a | ||
|
|
c9ae60d1ab | ||
|
|
31f4d70471 | ||
|
|
8444210d91 | ||
|
|
f2dda679eb | ||
|
|
85e83ebb35 | ||
|
|
521dca85a4 | ||
|
|
fcc3c5132c | ||
|
|
7569386e56 |
@@ -0,0 +1,56 @@
|
||||
"""API adapter for agent context routing and visibility."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from fastapi import APIRouter
|
||||
except Exception:
|
||||
APIRouter = None
|
||||
|
||||
from .agent_context_dashboard import AgentContextDashboard
|
||||
from .agent_context_rbac import AgentContextRBAC
|
||||
from .agent_context_resolver import AgentContextResolver
|
||||
|
||||
resolver = AgentContextResolver()
|
||||
rbac = AgentContextRBAC()
|
||||
dashboard = AgentContextDashboard()
|
||||
|
||||
|
||||
def resolve_agent_context(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
route = rbac.route(payload["user_id"], payload.get("group_id"), payload.get("role", "user"))
|
||||
context = resolver.resolve(
|
||||
user_id=payload["user_id"],
|
||||
group_id=payload.get("group_id", ""),
|
||||
session=payload.get("session"),
|
||||
user_profile=payload.get("user_profile"),
|
||||
group_policy=payload.get("group_policy"),
|
||||
agent_default=payload.get("agent_default"),
|
||||
owner_policy=payload.get("owner_policy"),
|
||||
owner=payload.get("role") == "owner",
|
||||
)
|
||||
return {"routing": route.to_dict(), "context": context.to_dict()}
|
||||
|
||||
|
||||
def get_context_dashboard() -> dict[str, Any]:
|
||||
return dashboard.overview()
|
||||
|
||||
|
||||
def build_router():
|
||||
if APIRouter is None:
|
||||
return None
|
||||
router = APIRouter(prefix="/admin/ai-evolution/context", tags=["context-isolation"])
|
||||
|
||||
@router.post("/resolve", operation_id="resolveAgentContext")
|
||||
def resolve(payload: dict[str, Any]):
|
||||
return resolve_agent_context(payload)
|
||||
|
||||
@router.get("/overview", operation_id="getAgentContextDashboard")
|
||||
def overview():
|
||||
return get_context_dashboard()
|
||||
|
||||
return router
|
||||
|
||||
|
||||
router = build_router()
|
||||
|
||||
__all__ = ["router", "resolve_agent_context", "get_context_dashboard"]
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Audit context resolution decisions."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
class AgentContextAudit:
|
||||
def record(self, context: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"event": "context_loaded",
|
||||
"agent_id": context.get("agent_id"),
|
||||
"user_id": context.get("user_id"),
|
||||
"group_id": context.get("group_id"),
|
||||
"layers": [x.get("name") for x in context.get("layers", [])],
|
||||
"timestamp": utc_now(),
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["AgentContextAudit"]
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Web UI model for context isolation visibility."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class AgentContextDashboard:
|
||||
def overview(self, contexts: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
||||
contexts = contexts or []
|
||||
return {
|
||||
"module": "agent-context-isolation",
|
||||
"route": "/admin/ai-evolution/context",
|
||||
"title": "Agent Context Isolation",
|
||||
"panels": [
|
||||
{
|
||||
"name": "agent-routing",
|
||||
"title": "Agent 路由",
|
||||
"data": contexts,
|
||||
},
|
||||
{
|
||||
"name": "context-layers",
|
||||
"title": "Context 来源层级",
|
||||
"layers": [
|
||||
"safety_policy",
|
||||
"owner_policy",
|
||||
"group_policy",
|
||||
"role_policy",
|
||||
"user_profile",
|
||||
"session_context",
|
||||
"agent_default",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "audit",
|
||||
"title": "Context 审计",
|
||||
"events": [
|
||||
"context_loaded",
|
||||
"profile_updated",
|
||||
"agent_selected",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["AgentContextDashboard"]
|
||||
@@ -0,0 +1,55 @@
|
||||
"""RBAC binding for group main agents and owner agents."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AgentRoutingDecision:
|
||||
user_id: str
|
||||
group_id: str
|
||||
role: str
|
||||
agent_id: str
|
||||
scope: str
|
||||
allowed: bool
|
||||
reason: str
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class AgentContextRBAC:
|
||||
"""Routes users to group agents and owners to dedicated owner agents."""
|
||||
|
||||
VALID_ROLES = {"user", "group_admin", "owner"}
|
||||
|
||||
def route(self, user_id: str, group_id: str | None, role: str) -> AgentRoutingDecision:
|
||||
if role not in self.VALID_ROLES:
|
||||
return AgentRoutingDecision(user_id, group_id or "", role, "", "none", False, "unknown_role")
|
||||
if role == "owner":
|
||||
return AgentRoutingDecision(
|
||||
user_id=user_id,
|
||||
group_id=group_id or "global",
|
||||
role=role,
|
||||
agent_id=f"owner-agent:{user_id}",
|
||||
scope="global",
|
||||
allowed=True,
|
||||
reason="owner_dedicated_agent",
|
||||
)
|
||||
if not group_id:
|
||||
return AgentRoutingDecision(user_id, "", role, "", "none", False, "group_required")
|
||||
scope = "group" if role == "group_admin" else "self"
|
||||
return AgentRoutingDecision(
|
||||
user_id=user_id,
|
||||
group_id=group_id,
|
||||
role=role,
|
||||
agent_id=f"group-main-agent:{group_id}",
|
||||
scope=scope,
|
||||
allowed=True,
|
||||
reason="group_agent_routing",
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["AgentRoutingDecision", "AgentContextRBAC"]
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Multi-user agent context resolver.
|
||||
|
||||
Architecture:
|
||||
- shared agent kernel
|
||||
- group main agent
|
||||
- owner dedicated agent
|
||||
- user profile overlay
|
||||
- session overlay
|
||||
|
||||
Personal preferences never enter shared USER.md.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ContextLayer:
|
||||
name: str
|
||||
priority: int
|
||||
data: dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class EffectiveContext:
|
||||
agent_id: str
|
||||
user_id: str
|
||||
group_id: str
|
||||
layers: list[ContextLayer]
|
||||
resolved: dict[str, Any]
|
||||
resolved_at: str = field(default_factory=utc_now)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"agent_id": self.agent_id,
|
||||
"user_id": self.user_id,
|
||||
"group_id": self.group_id,
|
||||
"layers": [asdict(x) for x in self.layers],
|
||||
"resolved": self.resolved,
|
||||
"resolved_at": self.resolved_at,
|
||||
}
|
||||
|
||||
|
||||
class AgentContextResolver:
|
||||
"""Resolves context without shared user-state pollution."""
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
user_id: str,
|
||||
group_id: str,
|
||||
session: dict[str, Any] | None = None,
|
||||
user_profile: dict[str, Any] | None = None,
|
||||
group_policy: dict[str, Any] | None = None,
|
||||
agent_default: dict[str, Any] | None = None,
|
||||
owner_policy: dict[str, Any] | None = None,
|
||||
owner: bool = False,
|
||||
) -> EffectiveContext:
|
||||
layers = [
|
||||
ContextLayer("agent_default", 10, agent_default or {}),
|
||||
ContextLayer("group_policy", 30, group_policy or {}),
|
||||
ContextLayer("user_profile", 50, user_profile or {}),
|
||||
ContextLayer("session", 70, session or {}),
|
||||
]
|
||||
agent_id = f"group-main-agent:{group_id}"
|
||||
if owner:
|
||||
agent_id = f"owner-agent:{user_id}"
|
||||
layers.append(ContextLayer("owner_policy", 90, owner_policy or {}))
|
||||
resolved: dict[str, Any] = {}
|
||||
# Higher priority overlays lower priority except safety policies handled upstream.
|
||||
for layer in sorted(layers, key=lambda x: x.priority):
|
||||
resolved.update(layer.data)
|
||||
return EffectiveContext(agent_id, user_id, group_id, layers, resolved)
|
||||
|
||||
|
||||
__all__ = ["ContextLayer", "EffectiveContext", "AgentContextResolver"]
|
||||
@@ -0,0 +1,30 @@
|
||||
agent_context_isolation:
|
||||
enabled: true
|
||||
architecture:
|
||||
shared_kernel: true
|
||||
group_main_agent: true
|
||||
owner_dedicated_agent: true
|
||||
user_dedicated_agent: false
|
||||
layers:
|
||||
- system_policy
|
||||
- agent_default
|
||||
- group_policy
|
||||
- role_policy
|
||||
- user_profile
|
||||
- session_context
|
||||
priority:
|
||||
session_context: 70
|
||||
user_profile: 50
|
||||
group_policy: 30
|
||||
agent_default: 10
|
||||
safety_policy: immutable
|
||||
forbidden:
|
||||
- user_preferences_in_USER_md
|
||||
- cross_user_profile_access
|
||||
- shared_personalization_state
|
||||
audit:
|
||||
enabled: true
|
||||
events:
|
||||
- context_loaded
|
||||
- profile_updated
|
||||
- agent_selected
|
||||
@@ -0,0 +1,32 @@
|
||||
# CMD-20260709-enable-group-owner-agent-isolation
|
||||
|
||||
## Goal
|
||||
|
||||
Deploy multi-user agent context isolation:
|
||||
|
||||
- group main agent per group/tenant
|
||||
- dedicated owner agent per owner
|
||||
- user profile and session overlays
|
||||
- no user personalization in shared USER.md
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Deploy context resolver.
|
||||
2. Enable group agent routing.
|
||||
3. Enable owner agent routing.
|
||||
4. Create runtime user profile storage.
|
||||
5. Migrate user-specific preferences out of shared USER.md.
|
||||
6. Enable context audit events.
|
||||
|
||||
## Validation
|
||||
|
||||
- AIOS_USER_PROFILE_ISOLATION_READY
|
||||
- AIOS_GROUP_AGENT_ROUTING_READY
|
||||
- AIOS_OWNER_AGENT_READY
|
||||
- AIOS_CONTEXT_AUDIT_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- Safety policy overrides all user customization.
|
||||
- No cross-user profile access.
|
||||
- No personal profile data in shared Git configuration.
|
||||
@@ -0,0 +1,77 @@
|
||||
# CMD-20260709-fix-openapi-workspace-permission
|
||||
|
||||
## Goal
|
||||
|
||||
Fix Git workspace permission issue that blocks writing OpenAPI files.
|
||||
|
||||
## Symptom
|
||||
|
||||
ChatGPT Actions failed to write:
|
||||
|
||||
```text
|
||||
openapi/agent-context-isolation.yaml
|
||||
```
|
||||
|
||||
Error:
|
||||
|
||||
```text
|
||||
Permission denied: /data/aios-gitops/workspace/openapi/agent-context-isolation.yaml
|
||||
```
|
||||
|
||||
## Execute
|
||||
|
||||
1. Inspect permissions:
|
||||
|
||||
```bash
|
||||
ls -ld /data/aios-gitops/workspace/openapi
|
||||
ls -la /data/aios-gitops/workspace/openapi | head
|
||||
stat /data/aios-gitops/workspace/openapi
|
||||
ps aux | grep -E 'aios-gitops|model-api|actions' | grep -v grep
|
||||
```
|
||||
|
||||
2. Identify runtime write user for Actions API.
|
||||
|
||||
3. Fix ownership/permissions with least privilege. Expected owner should match the GitOps runtime write user, likely `aiman` or the service user.
|
||||
|
||||
Example only after confirming user/group:
|
||||
|
||||
```bash
|
||||
sudo chown -R <runtime_user>:<runtime_group> /data/aios-gitops/workspace/openapi
|
||||
sudo chmod -R u+rwX,g+rwX /data/aios-gitops/workspace/openapi
|
||||
```
|
||||
|
||||
4. Do not make the directory world-writable unless explicitly approved.
|
||||
|
||||
5. Retry creating:
|
||||
|
||||
```text
|
||||
openapi/agent-context-isolation.yaml
|
||||
```
|
||||
|
||||
6. Commit through GitOps patch branch only:
|
||||
|
||||
```text
|
||||
patch/group-owner-agent-context-isolation
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
- OpenAPI file write succeeds.
|
||||
- Git commit succeeds.
|
||||
- No direct main push.
|
||||
- No secret written.
|
||||
- Permission repair is recorded in audit.
|
||||
|
||||
Expected signal:
|
||||
|
||||
```text
|
||||
AIOS_OPENAPI_WORKSPACE_PERMISSION_READY
|
||||
```
|
||||
|
||||
## Forbidden
|
||||
|
||||
- Do not push main.
|
||||
- Do not create tag.
|
||||
- Do not chmod 777 unless owner explicitly approves.
|
||||
- Do not write secrets.
|
||||
- Do not change unrelated directories.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "branch",
|
||||
"branch": "patch/group-owner-agent-context-isolation",
|
||||
"base": "main",
|
||||
"created_by": "chatgpt-gitops-bot",
|
||||
"created_at": "2026-07-09T01:33:04Z",
|
||||
"direct_main_push": false,
|
||||
"owner_approval_required": true
|
||||
}
|
||||
Reference in New Issue
Block a user