Compare commits
107
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe36cc9241 | ||
|
|
eb4a3adb3f | ||
|
|
f1ebd2cf99 | ||
|
|
cb3b5f1e01 | ||
|
|
6fc538bf50 | ||
|
|
665c33f69a | ||
|
|
880591fc00 | ||
|
|
68060fbcba | ||
|
|
db1ebc29e0 | ||
|
|
93b8709c36 | ||
|
|
7900c4df2e | ||
|
|
8b6fb1c06e | ||
|
|
de8b996dea | ||
|
|
eb8d229f28 | ||
|
|
c93f99da41 | ||
|
|
c2e782e6fa | ||
|
|
7fd4a4aae3 | ||
|
|
e4bcf47f79 | ||
|
|
00cc1d9b6d | ||
|
|
da67ec9714 | ||
|
|
2e93ed904a | ||
|
|
b901fa3a5a | ||
|
|
b9778f1f69 | ||
|
|
41c4c4f003 | ||
|
|
d119c2d87c | ||
|
|
0a143c6b98 | ||
|
|
7ef76b739f | ||
|
|
a46878f9b8 | ||
|
|
f73c7946d0 | ||
|
|
ecd7ec40dd | ||
|
|
82bf1bd118 | ||
|
|
ac89ba2576 | ||
|
|
c9f1c28616 | ||
|
|
c796347655 | ||
|
|
9dad8b57a7 | ||
|
|
ff40a89987 | ||
|
|
8fd86d6100 | ||
|
|
c38c575d8e | ||
|
|
b1c1e49cc5 | ||
|
|
b9e2358901 | ||
|
|
a1874c90ae | ||
|
|
19a2ce8439 | ||
|
|
f8fc7ad73b | ||
|
|
e2b9cc6849 | ||
|
|
4f4c95ffce | ||
|
|
edd3bd239e | ||
|
|
7a1e862a03 | ||
|
|
dc0ac18aea | ||
|
|
9e3dbdf146 | ||
|
|
87b1a5357c | ||
|
|
128a7d9050 | ||
|
|
e8cf0f6348 | ||
|
|
786a3b6250 | ||
|
|
b9a739fd95 | ||
|
|
0fb71cd191 | ||
|
|
3b8e74bed4 | ||
|
|
9df07a5d7d | ||
|
|
54356c0fcc | ||
|
|
4b62b18a39 | ||
|
|
bb69d76a7b | ||
|
|
27eef25d12 | ||
|
|
7b563590b4 | ||
|
|
50bceb1d45 | ||
|
|
f89619f576 | ||
|
|
365ea5d06c | ||
|
|
f15a19d525 | ||
|
|
3bdb5f9d59 | ||
|
|
4a9f3fff1a | ||
|
|
ffbc1e0e01 | ||
|
|
8365afbeb8 | ||
|
|
d34ef72f5d | ||
|
|
7f118fa205 | ||
|
|
97867386a3 | ||
|
|
e17abc12be | ||
|
|
bec612c67d | ||
|
|
19bcb8aeed | ||
|
|
79e00ef089 | ||
|
|
4833b9f93a | ||
|
|
fd715457da | ||
|
|
4325f0b7d2 | ||
|
|
b2a4f67f93 | ||
|
|
c2e4a5b078 | ||
|
|
707cfe4d60 | ||
|
|
cf97cf697d | ||
|
|
82eecc27b4 | ||
|
|
bea425052f | ||
|
|
45c26a1a4a | ||
|
|
6ea5047dbb | ||
|
|
a5fdfbf057 | ||
|
|
8a5ce63069 | ||
|
|
bce254790c | ||
|
|
14a3b84fc6 | ||
|
|
39f23b0891 | ||
|
|
034d5f0faa | ||
|
|
cb6851b751 | ||
|
|
a79010f788 | ||
|
|
95b1d82738 | ||
|
|
afa5c0eb3e | ||
|
|
ca9d7a7424 | ||
|
|
890699efaa | ||
|
|
142f827feb | ||
|
|
32949d9dac | ||
|
|
4c505d2b6a | ||
|
|
d2bcf4d5af | ||
|
|
76b33c0c6a | ||
|
|
9babbcca43 | ||
|
|
c387c83c13 |
@@ -10,3 +10,4 @@ node_modules/
|
||||
runtime/tmp/
|
||||
runtime/cache/
|
||||
logs/
|
||||
config/test_write_perms
|
||||
|
||||
@@ -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,77 @@
|
||||
"""Actions API for unified Agent Event Bus."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from fastapi import APIRouter
|
||||
except Exception:
|
||||
APIRouter = None
|
||||
|
||||
from .agent_event_bus import AgentEventBus
|
||||
|
||||
bus = AgentEventBus()
|
||||
|
||||
|
||||
def create_agent_event(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
return {"status": "ok", "event": bus.publish(**payload)}
|
||||
|
||||
|
||||
def list_agent_events(kind: str | None = None, status: str = "pending") -> dict[str, Any]:
|
||||
return {"status": "ok", "events": bus.list_events(kind, status)}
|
||||
|
||||
|
||||
def get_agent_event(event_id: str) -> dict[str, Any]:
|
||||
return {"status": "ok", "event": bus.get(event_id)}
|
||||
|
||||
|
||||
def transition_agent_event(event_id: str, status: str, actor: str = "system") -> dict[str, Any]:
|
||||
return {"status": "ok", "event": bus.transition(event_id, status, actor)}
|
||||
|
||||
|
||||
def agent_event_stats() -> dict[str, Any]:
|
||||
return {"status": "ok", "stats": bus.stats()}
|
||||
|
||||
|
||||
# Compatibility adapters
|
||||
|
||||
def list_gpt_inbox_messages():
|
||||
return list_agent_events(kind="gpt_inbox")
|
||||
|
||||
|
||||
def ack_gpt_inbox_message(event_id: str):
|
||||
return transition_agent_event(event_id, "completed", "custom-gpt")
|
||||
|
||||
|
||||
def build_router():
|
||||
if APIRouter is None:
|
||||
return None
|
||||
router = APIRouter(prefix="/agent-events", tags=["agent-events"])
|
||||
|
||||
@router.post("", operation_id="createAgentEvent")
|
||||
def create(payload: dict[str, Any]):
|
||||
return create_agent_event(payload)
|
||||
|
||||
@router.get("", operation_id="listAgentEvents")
|
||||
def listing(kind: str | None = None, status: str = "pending"):
|
||||
return list_agent_events(kind, status)
|
||||
|
||||
@router.get("/{event_id}", operation_id="getAgentEvent")
|
||||
def get(event_id: str):
|
||||
return get_agent_event(event_id)
|
||||
|
||||
@router.post("/{event_id}/transition", operation_id="transitionAgentEvent")
|
||||
def transition(event_id: str, payload: dict[str, Any]):
|
||||
return transition_agent_event(event_id, payload["status"], payload.get("actor", "system"))
|
||||
|
||||
@router.get("/stats/overview", operation_id="getAgentEventStats")
|
||||
def stats():
|
||||
return agent_event_stats()
|
||||
|
||||
return router
|
||||
|
||||
|
||||
router = build_router()
|
||||
|
||||
__all__ = ["router", "create_agent_event", "list_agent_events", "get_agent_event", "transition_agent_event"]
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Unified Agent Event Bus.
|
||||
|
||||
Unifies commands, GPT messages, replies, approvals, audit and reports into one
|
||||
state machine. Compatibility layers can filter events by kind instead of keeping
|
||||
separate queues.
|
||||
"""
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentEvent:
|
||||
id: str
|
||||
kind: str
|
||||
source: str
|
||||
target: str
|
||||
summary: str
|
||||
payload: dict[str, Any]
|
||||
status: str = "pending"
|
||||
correlation_id: str | None = None
|
||||
created_at: str = field(default_factory=utc_now)
|
||||
updated_at: str = field(default_factory=utc_now)
|
||||
actor: str | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class AgentEventBus:
|
||||
STATES = ("pending", "running", "completed", "failed")
|
||||
|
||||
def __init__(self, root: str = "agents/events"):
|
||||
self.root = Path(root)
|
||||
for state in self.STATES:
|
||||
(self.root / state).mkdir(parents=True, exist_ok=True)
|
||||
(self.root / "audit").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def publish(
|
||||
self,
|
||||
kind: str,
|
||||
source: str,
|
||||
target: str,
|
||||
summary: str,
|
||||
payload: dict[str, Any],
|
||||
correlation_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
event_id = "evt_" + sha256(
|
||||
json.dumps(
|
||||
{"kind": kind, "source": source, "summary": summary, "payload": payload},
|
||||
sort_keys=True,
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8")
|
||||
).hexdigest()[:16]
|
||||
event = AgentEvent(event_id, kind, source, target, summary, payload, correlation_id=correlation_id)
|
||||
self._write("pending", event.to_dict())
|
||||
self._audit("created", event.to_dict())
|
||||
return event.to_dict()
|
||||
|
||||
def list_events(self, kind: str | None = None, status: str = "pending", limit: int = 100) -> list[dict[str, Any]]:
|
||||
result = []
|
||||
for path in sorted((self.root / status).glob("*.json")):
|
||||
event = self._read(path)
|
||||
if kind and event.get("kind") != kind:
|
||||
continue
|
||||
result.append(event)
|
||||
return result[:limit]
|
||||
|
||||
def get(self, event_id: str) -> dict[str, Any] | None:
|
||||
for state in self.STATES:
|
||||
path = self.root / state / f"{event_id}.json"
|
||||
if path.exists():
|
||||
return self._read(path)
|
||||
return None
|
||||
|
||||
def transition(self, event_id: str, status: str, actor: str | None = None) -> dict[str, Any]:
|
||||
event = self.get(event_id)
|
||||
if not event:
|
||||
return {"status": "not_found", "id": event_id}
|
||||
if status not in self.STATES:
|
||||
return {"status": "invalid_state"}
|
||||
old = event.get("status", "pending")
|
||||
old_path = self.root / old / f"{event_id}.json"
|
||||
event["status"] = status
|
||||
event["actor"] = actor
|
||||
event["updated_at"] = utc_now()
|
||||
self._write(status, event)
|
||||
if old_path.exists() and old != status:
|
||||
old_path.unlink()
|
||||
self._audit("transition", event, {"from": old, "to": status})
|
||||
return event
|
||||
|
||||
def stats(self) -> dict[str, Any]:
|
||||
return {
|
||||
"events": {state: len(list((self.root / state).glob("*.json"))) for state in self.STATES},
|
||||
"audit_events": len(list((self.root / "audit").glob("*.json"))),
|
||||
"status": "healthy",
|
||||
}
|
||||
|
||||
def _write(self, state: str, data: dict[str, Any]):
|
||||
(self.root / state / f"{data['id']}.json").write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
def _read(self, path: Path):
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def _audit(self, action: str, event: dict[str, Any], extra: dict[str, Any] | None = None):
|
||||
data = {"action": action, "event": event, "extra": extra or {}, "time": utc_now()}
|
||||
(self.root / "audit" / f"{event['id']}-{action}.json").write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["AgentEvent", "AgentEventBus"]
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Admin dashboard data model for the unified Agent Event Bus."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from typing import Any
|
||||
|
||||
from .agent_event_bus import AgentEventBus
|
||||
|
||||
|
||||
class AgentEventDashboard:
|
||||
def __init__(self, bus: AgentEventBus | None = None):
|
||||
self.bus = bus or AgentEventBus()
|
||||
|
||||
def overview(self) -> dict[str, Any]:
|
||||
stats = self.bus.stats()
|
||||
pending = self.bus.list_events(status="pending")
|
||||
running = self.bus.list_events(status="running")
|
||||
completed = self.bus.list_events(status="completed")
|
||||
failed = self.bus.list_events(status="failed")
|
||||
all_visible = pending + running + completed + failed
|
||||
by_kind = Counter(event.get("kind", "unknown") for event in all_visible)
|
||||
by_target = Counter(event.get("target", "unknown") for event in all_visible)
|
||||
return {
|
||||
"module": "agent-event-bus",
|
||||
"title": "Unified Agent Event Bus",
|
||||
"route": "/admin/ai-evolution/events",
|
||||
"metrics": {
|
||||
**stats,
|
||||
"by_kind": dict(by_kind),
|
||||
"by_target": dict(by_target),
|
||||
"gpt_inbox_pending": by_kind.get("gpt_inbox", 0),
|
||||
"openclaw_pending": len([e for e in pending if e.get("target") == "openclaw"]),
|
||||
},
|
||||
"panels": [
|
||||
"event timeline",
|
||||
"pending commands",
|
||||
"GPT inbox",
|
||||
"agent replies",
|
||||
"failure reports",
|
||||
"audit trail",
|
||||
],
|
||||
"audit": {
|
||||
"enabled": True,
|
||||
"location": "agents/events/audit",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["AgentEventDashboard"]
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Bridge Agent Event Bus events into Agent Observability metrics."""
|
||||
|
||||
from .agent_observability_hooks import AgentObservabilityHooks
|
||||
|
||||
|
||||
class AgentEventObservabilityBridge:
|
||||
def __init__(self, hooks: AgentObservabilityHooks | None = None):
|
||||
self.hooks = hooks or AgentObservabilityHooks()
|
||||
|
||||
def consume(self, event: dict):
|
||||
kind = event.get("kind")
|
||||
payload = event.get("payload", {})
|
||||
if kind == "agent_started":
|
||||
return self.hooks.start(
|
||||
user_id=payload.get("user_id", "unknown"),
|
||||
visible_scope=payload.get("scope", "self"),
|
||||
entry_agent=payload.get("agent", "unknown"),
|
||||
execution_id=event.get("id"),
|
||||
)
|
||||
if kind == "agent_delegated":
|
||||
return self.hooks.delegate(event["execution_id"], payload.get("agent", "unknown"))
|
||||
if kind == "token_consumed":
|
||||
return self.hooks.tokens(
|
||||
event["execution_id"],
|
||||
payload.get("input_tokens", 0),
|
||||
payload.get("output_tokens", 0),
|
||||
)
|
||||
if kind in {"agent_completed", "agent_failed"}:
|
||||
return self.hooks.finish(
|
||||
event["execution_id"],
|
||||
payload.get("closed_by"),
|
||||
"failed" if kind == "agent_failed" else "completed",
|
||||
)
|
||||
return {"status": "ignored", "kind": kind}
|
||||
|
||||
|
||||
__all__ = ["AgentEventObservabilityBridge"]
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Agent orchestration observability model.
|
||||
|
||||
Tracks OpenClaw, Hermes and sub-agent execution chains. Token usage is optional
|
||||
and depends on model adapter instrumentation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentExecutionRecord:
|
||||
execution_id: str
|
||||
user_id: str
|
||||
visible_scope: str
|
||||
entry_agent: str
|
||||
path: list[str]
|
||||
used_hermes: bool
|
||||
closed_by: str
|
||||
duration_ms: int = 0
|
||||
input_tokens: int | None = None
|
||||
output_tokens: int | None = None
|
||||
total_tokens: int | None = None
|
||||
status: str = "completed"
|
||||
started_at: str = field(default_factory=utc_now)
|
||||
ended_at: str = field(default_factory=utc_now)
|
||||
|
||||
def to_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class AgentObservability:
|
||||
def __init__(self):
|
||||
self.records: list[AgentExecutionRecord] = []
|
||||
|
||||
def record(self, record: AgentExecutionRecord):
|
||||
self.records.append(record)
|
||||
return record.to_dict()
|
||||
|
||||
def summary(self, user_id: str | None = None) -> dict[str, Any]:
|
||||
records = [r for r in self.records if not user_id or r.user_id == user_id]
|
||||
hermes = [r for r in records if r.used_hermes]
|
||||
direct = [r for r in records if not r.used_hermes]
|
||||
return {
|
||||
"total_executions": len(records),
|
||||
"openclaw_independent_loops": len(direct),
|
||||
"hermes_orchestrated_loops": len(hermes),
|
||||
"average_duration_ms": (
|
||||
sum(r.duration_ms for r in records) / len(records) if records else 0
|
||||
),
|
||||
"token_usage": {
|
||||
"input": sum(r.input_tokens or 0 for r in records),
|
||||
"output": sum(r.output_tokens or 0 for r in records),
|
||||
"total": sum(r.total_tokens or 0 for r in records),
|
||||
},
|
||||
}
|
||||
|
||||
def hermes_tree(self, user_id: str | None = None):
|
||||
return [
|
||||
{
|
||||
"execution_id": r.execution_id,
|
||||
"entry": r.entry_agent,
|
||||
"used_hermes": r.used_hermes,
|
||||
"children": r.path,
|
||||
"duration_ms": r.duration_ms,
|
||||
"tokens": r.total_tokens,
|
||||
}
|
||||
for r in self.records
|
||||
if (not user_id or r.user_id == user_id)
|
||||
]
|
||||
|
||||
|
||||
__all__ = ["AgentExecutionRecord", "AgentObservability"]
|
||||
@@ -0,0 +1,52 @@
|
||||
"""API for agent orchestration observability."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from fastapi import APIRouter
|
||||
except Exception:
|
||||
APIRouter = None
|
||||
|
||||
from .agent_observability import AgentExecutionRecord
|
||||
from .agent_observability_dashboard import AgentObservabilityDashboard
|
||||
from .agent_observability_store import AgentObservabilityStore
|
||||
|
||||
store = AgentObservabilityStore()
|
||||
dashboard = AgentObservabilityDashboard(store.load())
|
||||
|
||||
|
||||
def record_agent_execution(payload: dict[str, Any]):
|
||||
record = AgentExecutionRecord(**payload)
|
||||
return {"status": "ok", "record": store.append(record)}
|
||||
|
||||
|
||||
def get_agent_observability(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
|
||||
records = store.query(user_id, group_id, role)
|
||||
return {
|
||||
"status": "ok",
|
||||
"records": records,
|
||||
"summary": store.load().summary(user_id if role != "owner" else None),
|
||||
}
|
||||
|
||||
|
||||
def build_router():
|
||||
if APIRouter is None:
|
||||
return None
|
||||
router = APIRouter(prefix="/admin/ai-evolution/agents", tags=["agent-observability"])
|
||||
|
||||
@router.post("/execution", operation_id="recordAgentExecution")
|
||||
def record(payload: dict[str, Any]):
|
||||
return record_agent_execution(payload)
|
||||
|
||||
@router.get("/overview", operation_id="getAgentObservabilityOverview")
|
||||
def overview(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
|
||||
return get_agent_observability(user_id, group_id, role)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
router = build_router()
|
||||
|
||||
__all__ = ["router", "record_agent_execution", "get_agent_observability"]
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Web UI dashboard model for agent execution visibility."""
|
||||
|
||||
from .agent_observability import AgentObservability
|
||||
|
||||
|
||||
class AgentObservabilityDashboard:
|
||||
def __init__(self, observability: AgentObservability | None = None):
|
||||
self.observability = observability or AgentObservability()
|
||||
|
||||
def overview(self, user_id: str | None = None):
|
||||
return {
|
||||
"route": "/admin/ai-evolution/agents",
|
||||
"title": "Agent Orchestration Observability",
|
||||
"summary": self.observability.summary(user_id),
|
||||
"views": [
|
||||
{
|
||||
"name": "scheduler_flow",
|
||||
"title": "OpenClaw/Hermes 调度流程",
|
||||
"data": self.observability.hermes_tree(user_id),
|
||||
},
|
||||
{
|
||||
"name": "agent_metrics",
|
||||
"title": "智能体指标",
|
||||
"fields": [
|
||||
"duration_ms",
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"total_tokens",
|
||||
"status",
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "loop_distribution",
|
||||
"title": "闭环分布",
|
||||
"fields": [
|
||||
"openclaw_independent_loops",
|
||||
"hermes_orchestrated_loops",
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["AgentObservabilityDashboard"]
|
||||
@@ -0,0 +1,113 @@
|
||||
"""Runtime hooks for Agent Observability.
|
||||
|
||||
OpenClaw/Hermes/sub-agents can call these hooks at start/end/delegation points.
|
||||
The hooks store sanitized metadata only: no prompts, no secrets, no private message
|
||||
content.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from .agent_observability import AgentExecutionRecord
|
||||
from .agent_observability_store import AgentObservabilityStore
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def parse_time(value: str) -> datetime:
|
||||
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
@dataclass
|
||||
class AgentRunContext:
|
||||
execution_id: str
|
||||
user_id: str
|
||||
visible_scope: str
|
||||
entry_agent: str
|
||||
path: list[str] = field(default_factory=list)
|
||||
used_hermes: bool = False
|
||||
started_at: str = field(default_factory=utc_now)
|
||||
input_tokens: int = 0
|
||||
output_tokens: int = 0
|
||||
status: str = "running"
|
||||
|
||||
def delegate(self, agent_name: str) -> None:
|
||||
if agent_name not in self.path:
|
||||
self.path.append(agent_name)
|
||||
if agent_name.lower() == "hermes":
|
||||
self.used_hermes = True
|
||||
|
||||
def add_tokens(self, input_tokens: int = 0, output_tokens: int = 0) -> None:
|
||||
self.input_tokens += max(input_tokens, 0)
|
||||
self.output_tokens += max(output_tokens, 0)
|
||||
|
||||
|
||||
class AgentObservabilityHooks:
|
||||
def __init__(self, store: AgentObservabilityStore | None = None):
|
||||
self.store = store or AgentObservabilityStore()
|
||||
self.active: dict[str, AgentRunContext] = {}
|
||||
|
||||
def start(
|
||||
self,
|
||||
user_id: str,
|
||||
visible_scope: str,
|
||||
entry_agent: str,
|
||||
execution_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
run = AgentRunContext(
|
||||
execution_id=execution_id or f"exec_{uuid4().hex[:12]}",
|
||||
user_id=user_id,
|
||||
visible_scope=visible_scope,
|
||||
entry_agent=entry_agent,
|
||||
path=[entry_agent],
|
||||
used_hermes=entry_agent.lower() == "hermes",
|
||||
)
|
||||
self.active[run.execution_id] = run
|
||||
return {"status": "started", "execution_id": run.execution_id, "run": run.__dict__}
|
||||
|
||||
def delegate(self, execution_id: str, agent_name: str) -> dict[str, Any]:
|
||||
run = self.active[execution_id]
|
||||
run.delegate(agent_name)
|
||||
return {"status": "delegated", "execution_id": execution_id, "path": run.path}
|
||||
|
||||
def tokens(self, execution_id: str, input_tokens: int = 0, output_tokens: int = 0) -> dict[str, Any]:
|
||||
run = self.active[execution_id]
|
||||
run.add_tokens(input_tokens, output_tokens)
|
||||
return {
|
||||
"status": "tokens_recorded",
|
||||
"execution_id": execution_id,
|
||||
"input_tokens": run.input_tokens,
|
||||
"output_tokens": run.output_tokens,
|
||||
"total_tokens": run.input_tokens + run.output_tokens,
|
||||
}
|
||||
|
||||
def finish(self, execution_id: str, closed_by: str | None = None, status: str = "completed") -> dict[str, Any]:
|
||||
run = self.active.pop(execution_id)
|
||||
ended_at = utc_now()
|
||||
duration_ms = int((parse_time(ended_at) - parse_time(run.started_at)).total_seconds() * 1000)
|
||||
record = AgentExecutionRecord(
|
||||
execution_id=run.execution_id,
|
||||
user_id=run.user_id,
|
||||
visible_scope=run.visible_scope,
|
||||
entry_agent=run.entry_agent,
|
||||
path=run.path,
|
||||
used_hermes=run.used_hermes,
|
||||
closed_by=closed_by or run.path[-1],
|
||||
duration_ms=duration_ms,
|
||||
input_tokens=run.input_tokens,
|
||||
output_tokens=run.output_tokens,
|
||||
total_tokens=run.input_tokens + run.output_tokens,
|
||||
status=status,
|
||||
started_at=run.started_at,
|
||||
ended_at=ended_at,
|
||||
)
|
||||
return {"status": "stored", "record": self.store.append(record)}
|
||||
|
||||
|
||||
__all__ = ["AgentRunContext", "AgentObservabilityHooks"]
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Persistent store for agent observability records.
|
||||
|
||||
Stores sanitized execution metrics for OpenClaw/Hermes/sub-agents. No prompts,
|
||||
secrets, or private message contents are persisted here; only metrics and routing
|
||||
metadata are stored for Web UI statistics and audit.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .agent_observability import AgentExecutionRecord, AgentObservability
|
||||
|
||||
|
||||
class AgentObservabilityStore:
|
||||
def __init__(self, root: str = "runtime/agent-observability"):
|
||||
self.root = Path(root)
|
||||
self.records_dir = self.root / "records"
|
||||
self.records_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def append(self, record: AgentExecutionRecord) -> dict[str, Any]:
|
||||
path = self.records_dir / f"{record.execution_id}.json"
|
||||
data = record.to_dict()
|
||||
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return data
|
||||
|
||||
def load(self) -> AgentObservability:
|
||||
observability = AgentObservability()
|
||||
for path in sorted(self.records_dir.glob("*.json")):
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
observability.records.append(AgentExecutionRecord(**raw))
|
||||
return observability
|
||||
|
||||
def query(self, user_id: str | None = None, group_id: str | None = None, role: str = "user") -> list[dict[str, Any]]:
|
||||
records = [record.to_dict() for record in self.load().records]
|
||||
if role == "owner":
|
||||
return records
|
||||
if role == "group_admin" and group_id:
|
||||
return [r for r in records if r.get("visible_scope") == group_id or r.get("user_id") == user_id]
|
||||
return [r for r in records if r.get("user_id") == user_id]
|
||||
|
||||
|
||||
__all__ = ["AgentObservabilityStore"]
|
||||
Regular → Executable
+132
-12
@@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
import hashlib, hmac, json, os, re, subprocess, time, urllib.parse, uuid
|
||||
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
sys.path.insert(0, str(Path(os.getenv('AIOS_GITOPS_WORKSPACE', '/data/aios-gitops/workspace')) / 'runtime' / 'agent-os'))
|
||||
from orchestrator.action_monitor import ActionMonitor
|
||||
|
||||
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
|
||||
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
|
||||
SECRETS = Path(os.getenv('AIOS_GITOPS_SECRETS_DIR', str(ROOT / 'runtime' / 'secrets')))
|
||||
@@ -13,6 +16,8 @@ HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
|
||||
PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
|
||||
OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
|
||||
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios-v23.git')))
|
||||
monitor = ActionMonitor()
|
||||
|
||||
BASE_MODELS = [
|
||||
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display_name':'DeepSeek V4 Flash','scope':'global'},
|
||||
{'id':'base.qwen3.6-35b','provider':'one-api','model':'qwen3.6-35b','display_name':'Qwen 3.6 35B','scope':'global'},
|
||||
@@ -180,19 +185,30 @@ class H(BaseHTTPRequestHandler):
|
||||
if path in ('/api/openapi.yaml','/api/openapi-chatgpt-actions.yaml','/api/schema'):
|
||||
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8') if OPENAPI.exists() else self.sendj({'error':'schema_missing'},404)
|
||||
if path in ('/api','/api/status'): return self.sendj(api_spec())
|
||||
if path in ('/api/base','/base'):
|
||||
if path in ('/api/base','/api/baseline','/base'):
|
||||
data=jload(WS/'baseline'/'current.json', {}); data.setdefault('git', sh(['rev-parse','--short','HEAD'])['stdout']); return self.sendj(data)
|
||||
if path in ('/api/ver','/ver'):
|
||||
if path in ('/api/ver','/api/versions','/ver'):
|
||||
return self.sendj({'tags':sh(['tag','--list','V*'])['stdout'].splitlines(),'versions_dir':[p.name for p in (WS/'versions').glob('*')],'head':sh(['rev-parse','--short','HEAD'])['stdout']})
|
||||
if path in ('/api/diff','/diff'):
|
||||
frm=(q.get('from') or ['HEAD~1'])[0]; to=(q.get('to') or ['HEAD'])[0]; return self.sendj({'from':frm,'to':to,'diff':sh(['diff',frm,to])})
|
||||
if path in ('/api/patch','/patch'): return self.sendj({'patches':patches(),'branches':sh(['branch','--format','%(refname:short)'])['stdout'].splitlines()})
|
||||
if path in ('/api/patch','/api/patches','/api/branches','/patch'): return self.sendj({'patches':patches(),'branches':sh(['branch','--format','%(refname:short)'])['stdout'].splitlines()})
|
||||
if path in ('/api/model','/model'): return self.sendj({'base':BASE_MODELS,'custom':model_docs(),'priority':[jload(p,{}) for p in sorted((WS/'config'/'model_priority').glob('*.json'))]})
|
||||
if path in ('/api/run','/run'):
|
||||
if path in ('/api/run','/api/runtime-status','/run'):
|
||||
svc=subprocess.run(['systemctl','is-active','aios-gitops-model-api'], text=True, capture_output=True)
|
||||
return self.sendj({'service':'aios-gitops-model-api','status':svc.stdout.strip() or svc.stderr.strip(),'workspace_branch':sh(['rev-parse','--abbrev-ref','HEAD'])['stdout']})
|
||||
if path in ('/api/action-monitor','/action-monitor'):
|
||||
latest=monitor.latest()
|
||||
ldict={'event_id':latest.event_id,'action':latest.action,'error':latest.error,'context':latest.context,'created_at':latest.created_at} if latest else None
|
||||
return self.sendj({'events':[{"event_id":e.event_id,"action":e.action,"error":e.error,"context":e.context,"created_at":e.created_at} for e in monitor.list_events()],'latest':ldict})
|
||||
# Admin AI Evolution Dashboard
|
||||
if path in ('/admin/ai-evolution','/admin/ai-evolution/'):
|
||||
return self.ai_evolution_page()
|
||||
if path in ('/admin/ai-evolution/api/data'):
|
||||
return self.ai_evolution_data()
|
||||
return self.sendj({'error':'not_found','path':path},404)
|
||||
except Exception as e: return self.sendj({'error':str(e)},500)
|
||||
except Exception as e:
|
||||
monitor.record_failure('do_GET', str(e), {'path':path})
|
||||
return self.sendj({'error':str(e)},500)
|
||||
def do_PUT(self):
|
||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
||||
if path == '/api/model-priority': return self.model_priority(data)
|
||||
@@ -200,17 +216,19 @@ class H(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
path=urllib.parse.urlparse(self.path).path.rstrip('/'); data=self.body()
|
||||
try:
|
||||
if path == '/api/branch': return self.branch(data)
|
||||
if path == '/api/file': return self.file(data)
|
||||
if path in ('/api/branch','/api/create-branch'): return self.branch(data)
|
||||
if path in ('/api/file','/api/commit-file'): return self.file(data)
|
||||
if path == '/api/commit': return self.commit(data)
|
||||
if path == '/api/patch': return self.patch(data)
|
||||
if path == '/api/tag': return self.tag(data)
|
||||
if path in ('/api/patch','/api/create-patch'): return self.patch(data)
|
||||
if path in ('/api/tag','/api/create-tag'): return self.tag(data)
|
||||
if path == '/api/rollback': return self.rollback(data)
|
||||
if path == '/api/model': return self.model(data)
|
||||
if path == '/api/model-priority': return self.model_priority(data)
|
||||
if path == '/api/nl': return self.nl(data)
|
||||
return self.sendj({'error':'not_found','path':path},404)
|
||||
except Exception as e: return self.sendj({'error':str(e)},500)
|
||||
except Exception as e:
|
||||
monitor.record_failure('do_POST', str(e), {'path':path})
|
||||
return self.sendj({'error':str(e)},500)
|
||||
def branch(self,data):
|
||||
a=self.need('branch:create');
|
||||
if not a: return
|
||||
@@ -234,6 +252,9 @@ class H(BaseHTTPRequestHandler):
|
||||
if not a: return
|
||||
ver=data.get('version') or data.get('tag')
|
||||
if not re.match(r'^V\d+R\d+C\d+(-[A-Za-z0-9_.-]+)?$', ver or ''): return self.sendj({'error':'version must match VxRxCx'},400)
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'tag','hint':'This operation creates a Git tag. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
if a['role'] != 'owner' or not data.get('execute'):
|
||||
b=checkout(data.get('branch') or f'request/tag-{safe(ver)}', 'main', True); req={'version':ver,'requested_by':a['account'],'approval_required':True,'created_at':now()}; r=write_json(f'baseline/tag_requests/{safe(ver)}.json', req); c=commit([r], f'chore(chatgpt): request tag {ver}'); return self.sendj({'status':'approval_required','branch':b,'commit':c})
|
||||
out=sh(['tag','-a',ver,'-m',data.get('message') or f'release {ver}'], True); self.sendj({'status':'tagged','version':ver,'git':out,'sync':sync_tag(ver)})
|
||||
@@ -254,10 +275,16 @@ class H(BaseHTTPRequestHandler):
|
||||
if not secret_ref: return self.sendj({'error':'api_key_secret_ref or api_key required'},400)
|
||||
mid=data.get('model_id') or f'custom.{owner}.{hashlib.sha1((prov+base+name).encode()).hexdigest()[:10]}'; b=checkout(data.get('branch') or f'patch/model-{safe(mid)}','main',True)
|
||||
doc=sanitize({k:v for k,v in data.items() if k!='api_key'}); doc.update({'model_id':mid,'provider_name':prov,'base_url':base,'model_name':name,'api_key_secret_ref':secret_ref,'created_by':a['account'],'created_at':now()})
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'model','hint':'This operation registers a new model. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
r=write_json(f'config/model_registry/chatgpt/{safe(mid)}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): register model {name}'); audit(a['account'],'model',mid,doc); self.sendj({'status':'model_registered','branch':b,'model_id':mid,'secret_ref':secret_ref,'commit':c})
|
||||
def model_priority(self,data):
|
||||
a=self.need('model:priority');
|
||||
if not a: return
|
||||
if data.get('confirm') != True:
|
||||
self.sendj({'status':'confirm_required','operation':'model-priority','hint':'This operation updates model priority. Confirm to proceed.','x-openai-isConsequential':True}, 428)
|
||||
return
|
||||
user=safe(data.get('username') or a['account']); b=checkout(data.get('branch') or f'patch/model-priority-{user}', 'main', True); doc={'username':user,'items':data.get('items') or data.get('priority') or [],'updated_by':a['account'],'updated_at':now()}; r=write_json(f'config/model_priority/{user}.json', doc); c=commit([r], data.get('message') or f'chore(chatgpt): update model priority {user}'); audit(a['account'],'model-priority',user,doc); self.sendj({'status':'updated','branch':b,'priority':doc,'commit':c})
|
||||
def nl(self,data):
|
||||
a=self.need('nl:plan');
|
||||
@@ -268,9 +295,102 @@ class H(BaseHTTPRequestHandler):
|
||||
elif 'model' in low or '模型' in text: intent='model_change'; plan=['use POST /api/model or PUT /api/model-priority','commit metadata']
|
||||
elif 'patch' in low or '补丁' in text: intent='patch_create'; plan=['call POST /api/patch']
|
||||
b=checkout(data.get('branch') or f'patch/nl-{uid("nl")}', 'main', True); doc={'id':uid('nl'),'text':text,'intent':intent,'action_plan':plan,'execute':False,'owner_approval_required':True,'created_by':a['account'],'created_at':now()}; r=write_json(f'patches/nl/{safe(doc["id"])}.json', doc); c=commit([r], f'chore(chatgpt): record nl plan {intent}'); self.sendj({'status':'planned','branch':b,'request':doc,'commit':c})
|
||||
_EVOLUTION_HTML = '''<!DOCTYPE html>
|
||||
<html lang=zh-CN>
|
||||
<head>
|
||||
<meta charset=UTF-8>
|
||||
<meta name=viewport content="width=device-width,initial-scale=1.0">
|
||||
<title>AIOS Evolution Dashboard</title>
|
||||
<script src=https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js></script>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:#0d1117;color:#c9d1d9;font-size:14px;line-height:1.7;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif}
|
||||
.c{max-width:1000px;margin:0 auto;padding:32px 24px}
|
||||
h1{font-size:24px;color:#58a6ff;margin-bottom:2px}
|
||||
.sub{color:#8b949e;font-size:13px;border-bottom:1px solid #21262d;padding-bottom:16px;margin-bottom:20px;display:flex;justify-content:space-between}
|
||||
.g{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:16px;margin-bottom:20px}
|
||||
.s{background:#161b22;border:1px solid #21262d;border-radius:8px;padding:18px;text-align:center}
|
||||
.s .v{font-size:28px;font-weight:700;color:#f0f6fc}
|
||||
.s .l{font-size:11px;color:#8b949e;margin-top:4px}
|
||||
.bx{background:#161b22;border:1px solid #21262d;border-radius:8px;margin-bottom:14px}
|
||||
.bh{border-bottom:1px solid #21262d;padding:10px 16px;font-weight:600;font-size:14px;display:flex;gap:6px;align-items:center}
|
||||
.bd{padding:12px 16px}
|
||||
.rw{display:grid;grid-template-columns:1fr 1fr 100px 60px;gap:8px;padding:8px 16px;border-bottom:1px solid #21262d;font-size:13px;align-items:center;cursor:pointer;transition:background .15s}
|
||||
.rw:hover{background:#1c2128}.rw:last-child{border-bottom:none}
|
||||
.tg{display:inline-block;padding:2px 10px;border-radius:99px;font-size:11px;font-weight:600}
|
||||
.ok{background:#3fb95033;color:#3fb950}.fl{background:#da363333;color:#f85149}.if{background:#1f6feb33;color:#58a6ff}
|
||||
pre{background:#0d1117;padding:12px;border-radius:6px;overflow:auto;font-size:12px;color:#8b949e;max-height:300px;margin-top:8px}select{padding:8px 10px;border:1px solid #30363d;border-radius:6px;background:#21262d;color:#c9d1d9;font-size:13px;max-width:400px;width:100%;margin-bottom:4px;cursor:pointer}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id=app class=c>
|
||||
<h1>AIOS Evolution Dashboard</h1>
|
||||
<div class=sub><span>Baseline: {{baseline}}</span><span style=color:#8b949e>{{refreshMsg}}</span></div>
|
||||
<div class=g><div class=s><div class=v>{{tot}}</div><div class=l>Commands</div></div><div class=s><div class=v style=color:#3fb950>{{sok}}</div><div class=l>Succeeded</div></div><div class=s><div class=v style=color:#f85149>{{sfail}}</div><div class=l>Failed</div></div><div class=s><div class=v>{{runs}}</div><div class=l>Executions</div></div></div>
|
||||
<div class=bx><div class=bh>Version Timeline</div><div class=bd><div class=rw style=font-weight:600;border-bottom:2px solid #30363d;cursor:default><span>Version</span><span>Time</span><span>Result</span></div><div v-for=v in vs class=rw @click=selVer(v)><span style=color:#58a6ff>{{v.tag}}</span><span>{{v.time||'-'}}</span><span :class="'tg '+v.result">{{v.result||'-'}}</span></div></div></div>
|
||||
<div class=bx><div class=bh>Commands</div><div class=bd><select v-model=cmd @change=loadCmd><option value>-- Select --</option><option v-for=c in cmds :value=c>{{c}}</option></select><pre v-if=cdet>{{cdet}}</pre></div></div>
|
||||
<div class=bx><div class=bh>Recent Agent Runs</div><div class=bd><div class=rw style=font-weight:600;border-bottom:2px solid #30363d;cursor:default><span>Agent</span><span>Command</span><span>Count</span><span>Result</span></div><div v-for=r in aruns class=rw><span>{{r.agent||'-'}}</span><span style=overflow:hidden;white-space:nowrap;text-overflow:ellipsis>{{r.command}}</span><span>{{r.interaction_count||0}}</span><span :class="'tg '+r.final_result">{{r.final_result||'-'}}</span></div></div></div></div>
|
||||
<script>
|
||||
const{createApp,ref,onMounted}=Vue;createApp({setup(){const vs=ref([]),cmds=ref([]),ar=ref([]),cmd=ref(''),cdet=ref(null),tot=ref(0),sok=ref(0),sfail=ref(0),runs=ref(0),baseline=ref('V1R3C1'),refreshMsg=ref('')
|
||||
async function ld(){try{const r=await fetch('/admin/ai-evolution/api/data'),d=await r.json();baseline.value=d.baseline||'V1R3C1';vs.value=d.versions||[];cmds.value=d.commands||[];ar.value=d.agent_runs||[];tot.value=d.stats?.total||0;sok.value=d.stats?.success||0;sfail.value=d.stats?.failed||0;runs.value=d.stats?.agent_runs||0;refreshMsg.value='Last updated: '+new Date().toLocaleString()}catch(e){}}
|
||||
async function loadCmd(){if(!cmd.value){cdet.value=null;return}try{const r=await fetch('/admin/ai-evolution/api/data?command='+encodeURIComponent(cmd.value)),d=await r.json();cdet.value=JSON.stringify(d,null,2)}catch(e){cdet.value='Error'}}
|
||||
function selVer(v){cmd.value=v.tag||v.id||v.version;loadCmd()}
|
||||
onMounted(()=>{ld();setInterval(ld,30000)})
|
||||
return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer}})mount('#app')
|
||||
</script></body></html>'''
|
||||
|
||||
def ai_evolution_page(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.send_header('Content-Length', str(len(self._EVOLUTION_HTML)))
|
||||
self.end_headers()
|
||||
self.wfile.write(self._EVOLUTION_HTML.encode())
|
||||
|
||||
def ai_evolution_data(self):
|
||||
q=urllib.parse.urlparse(self.path).query
|
||||
params=urllib.parse.parse_qs(q)
|
||||
cmd_filter=(params.get('command') or [None])[0]
|
||||
bl=jload(WS/'baseline'/'current.json',{})
|
||||
current_ver=bl.get('current_baseline','V1R3C1')
|
||||
tags=sh(['tag','--list','V*'])['stdout'].splitlines() if WS.exists() else []
|
||||
versions=[]
|
||||
if (WS/'baseline'/'promotions').exists():
|
||||
for p in sorted((WS/'baseline'/'promotions').glob('*.json')):
|
||||
d=jload(p,{});v=d.get('version',p.stem)
|
||||
versions.append({'tag':v,'time':d.get('promoted_at','-'),'result':'ok'})
|
||||
for t in tags:
|
||||
if not any(v['tag']==t for v in versions):
|
||||
versions.append({'tag':t,'time':'-','result':'if'})
|
||||
agent_runs=[];ard=WS/'audit'/'agent-runs'
|
||||
if ard.exists():
|
||||
for f in sorted(ard.glob('*.json'),reverse=True)[:50]:
|
||||
d=jload(f,{});agent_runs.append(d) if isinstance(d,dict) else None
|
||||
cmd_dirs=[WS/'agents/commands/active',WS/'agents/commands/running',WS/'agents/commands/completed',WS/'agents/commands/failed']
|
||||
commands=[]
|
||||
for d in cmd_dirs:
|
||||
if d.exists():
|
||||
for f in sorted(d.glob('*.md')):
|
||||
commands.append(f.name)
|
||||
command_detail=None
|
||||
if cmd_filter:
|
||||
for d in cmd_dirs:
|
||||
if d.exists():
|
||||
fp=d/cmd_filter
|
||||
if fp.exists():
|
||||
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
|
||||
break
|
||||
if not command_detail:
|
||||
command_detail={'command':cmd_filter,'error':'not_found'}
|
||||
return self.sendj(command_detail)
|
||||
completed=list((WS/'agents/commands/completed').glob('*.md')) if (WS/'agents/commands/completed').exists() else []
|
||||
failed=list((WS/'agents/commands/failed').glob('*.md')) if (WS/'agents/commands/failed').exists() else []
|
||||
stats={'total':len(commands),'success':len(completed),'failed':len(failed),'agent_runs':len(agent_runs)}
|
||||
data={'baseline':current_ver,'versions':versions[-30:],'agent_runs':agent_runs,'commands':sorted(set(commands)),'stats':stats}
|
||||
self.sendj(data)
|
||||
|
||||
|
||||
def api_spec():
|
||||
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True}}
|
||||
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True}}
|
||||
|
||||
def main():
|
||||
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Evolution Dashboard</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: #0d1117; color: #c9d1d9; height: 100vh; overflow: hidden; }
|
||||
.app { display: flex; height: 100vh; }
|
||||
.sidebar { width: 260px; background: #161b22; border-right: 1px solid #30363d; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.sidebar h1 { font-size: 18px; color: #f0f6fc; border-bottom: 1px solid #30363d; padding-bottom: 12px; margin-bottom: 8px; }
|
||||
.sidebar-nav { list-style: none; display: flex; flex-direction: column; gap: 4px; }
|
||||
.sidebar-nav li { padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; color: #8b949e; }
|
||||
.sidebar-nav li:hover, .sidebar-nav li.active { background: #1f6feb22; color: #58a6ff; }
|
||||
.sidebar-nav li i { margin-right: 8px; }
|
||||
.main { flex: 1; padding: 24px; overflow-y: auto; }
|
||||
.main-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
|
||||
.main-header h2 { font-size: 20px; }
|
||||
.status-badge { padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600; text-transform: uppercase; }
|
||||
.status-badge.ok { background: #1b3a2d; color: #3fb950; border: 1px solid #3fb95044; }
|
||||
.status-badge.degraded { background: #3a2e1b; color: #d29922; border: 1px solid #d2992244; }
|
||||
.status-badge.fail { background: #3a1b1b; color: #f85149; border: 1px solid #f8514944; }
|
||||
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
||||
.card-title { font-size: 14px; color: #8b949e; margin-bottom: 8px; }
|
||||
.card-value { font-size: 28px; font-weight: 700; color: #f0f6fc; }
|
||||
.row { display: flex; gap: 16px; }
|
||||
.col { flex: 1; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
|
||||
pre { white-space: pre-wrap; word-break: break-word; }
|
||||
.command-item { padding: 12px; margin-bottom: 8px; border-radius: 6px; background: #0d1117; border: 1px solid #21262d; }
|
||||
.command-item .meta { display: flex; gap: 8px; align-items: center; font-size: 12px; color: #8b949e; }
|
||||
.command-item .name { font-size: 14px; color: #58a6ff; font-weight: 600; }
|
||||
.command-item .state { font-size: 11px; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; }
|
||||
.state-active { background: #1b3a2d; color: #3fb950; }
|
||||
.state-running { background: #3a2e1b; color: #d29922; }
|
||||
.state-completed { background: #1b3a2d; color: #3fb950; }
|
||||
.state-failed { background: #3a1b1b; color: #f85149; }
|
||||
.loading { text-align: center; padding: 40px; color: #8b949e; }
|
||||
.error { padding: 20px; background: #3a1b1b; border: 1px solid #f8514944; border-radius: 8px; color: #f85149; margin-bottom: 16px; }
|
||||
.version-item { padding: 8px 12px; background: #0d1117; border: 1px solid #21262d; border-radius: 6px; margin-bottom: 4px; display: flex; justify-content: space-between; }
|
||||
.version-item .tag { color: #58a6ff; font-weight: 600; }
|
||||
.version-item .name { color: #8b949e; font-size: 13px; }
|
||||
.agent-item { background: #0d1117; border: 1px solid #21262d; border-radius: 6px; margin-bottom: 8px; padding: 12px; }
|
||||
.agent-item .agent-title { font-size: 14px; font-weight: 600; color: #c9d1d9; margin-bottom: 4px; }
|
||||
.agent-item .agent-meta { font-size: 12px; color: #8b949e; }
|
||||
.agent-run-item { padding: 8px 12px; margin-bottom: 4px; border-radius: 4px; background: #0d1117; border: 1px solid #21262d; display: flex; justify-content: space-between; align-items: center; }
|
||||
.agent-run-item .run-id { color: #58a6ff; font-size: 13px; font-weight: 600; }
|
||||
.agent-run-item .source { color: #8b949e; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="app">
|
||||
<div class="sidebar">
|
||||
<h1>🕹️ AI Evolution Dashboard</h1>
|
||||
<ul class="sidebar-nav">
|
||||
<li class="active" onclick="showPanel('overview')"><i>📊</i> Overview</li>
|
||||
<li onclick="showPanel('versions')"><i>🏷️</i> Versions</li>
|
||||
<li onclick="showPanel('commands')"><i>⚡</i> Commands</li>
|
||||
<li onclick="showPanel('agents')"><i>🤖</i> Agent Runs</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div id="panel-overview" class="panel">
|
||||
<div class="main-header">
|
||||
<h2>System Overview</h2>
|
||||
<div id="header-status" class="status-badge">Loading...</div>
|
||||
</div>
|
||||
<div id="loading" class="loading">Loading...</div>
|
||||
<div id="error" class="error" style="display:none;"></div>
|
||||
<div id="content">
|
||||
<div class="row">
|
||||
<div class="col"><div class="card"><div class="card-title">Current Baseline</div><div id="base" class="card-value">-</div></div></div>
|
||||
<div class="col"><div class="card"><div class="card-title">Git Branch</div><div id="branch" class="card-value">-</div></div></div>
|
||||
<div class="col"><div class="card"><div class="card-title">Uptime</div><div id="uptime" class="card-value">-</div></div></div>
|
||||
</div>
|
||||
<div class="card" style="margin-top:16px">
|
||||
<div class="card-title">Event Bus Status</div>
|
||||
<pre id="bus-status">-</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="panel-versions" class="panel" style="display:none;">
|
||||
<div class="main-header"><h2>Version Timeline</h2></div>
|
||||
<div id="versions-list">Loading...</div>
|
||||
</div>
|
||||
<div id="panel-commands" class="panel" style="display:none;">
|
||||
<div class="main-header"><h2>Command Lookup</h2></div>
|
||||
<div id="commands-list">Loading...</div>
|
||||
</div>
|
||||
<div id="panel-agents" class="panel" style="display:none;">
|
||||
<div class="main-header"><h2>Recent Agent Executions</h2></div>
|
||||
<div id="agents-list">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
async function fetchData() {
|
||||
const r = await fetch('/admin/ai-evolution/api/data');
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Instruction Center
|
||||
|
||||
Version-controlled execution instructions for AIOS agents.
|
||||
|
||||
## Supported agents
|
||||
|
||||
- codex
|
||||
- openclaw
|
||||
- hermes
|
||||
- windows_codex_app
|
||||
- custom_gpt_review
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create versioned instruction file.
|
||||
2. Target agent executes instruction.
|
||||
3. Result is recorded with GitOps audit information.
|
||||
|
||||
## Naming
|
||||
|
||||
agent-instructions/<agent>/<instruction-name>-v<version>.md
|
||||
|
||||
## Rules
|
||||
|
||||
- GitOps managed only.
|
||||
- Patch-first workflow.
|
||||
- No secrets, tokens, passwords or credentials.
|
||||
- Production changes require approval.
|
||||
- Main branch changes require approval.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Agent Execution Instruction
|
||||
|
||||
Instruction ID:
|
||||
Version:
|
||||
Target Agent:
|
||||
|
||||
## Objective
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
## Execution Steps
|
||||
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only
|
||||
- Patch-first
|
||||
- No secrets
|
||||
- No force push
|
||||
- Production changes require approval
|
||||
|
||||
## Validation
|
||||
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Codex instructions.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Codex Fix Patches Artifact Lifecycle Instruction
|
||||
|
||||
Instruction ID: fix-patches-artifact-lifecycle-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
|
||||
Manage the lifecycle of artifacts in the `patches/` directory — specifically, clean up stale or committed artifacts (`.branch.json`, `nl/*.json`, standalone `.json` patch files) that are no longer needed.
|
||||
|
||||
## Background
|
||||
|
||||
The ChatGPT Actions API writes several types of artifacts into `patches/`:
|
||||
|
||||
1. **Branch metadata** (`patches/patch_*.branch.json`) — created when the `branch()` action writes ahead. These are summary records of created branches.
|
||||
2. **Patch files** (`patches/*.json`, top level) — created when the `patch()` action saves a JSON patch proposal. Example: `patches/self-evolution-agent-orchestration-policy.json`.
|
||||
3. **Natural language plan files** (`patches/nl/nl-*.json`) — created when `nl()` action records a natural-language plan. These are transient planning artifacts.
|
||||
|
||||
None of these files represent durable long-term state. Once committed, they are snapshots of ephemeral decisions. The `patches/` directory should not grow unboundedly with stale artifacts.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- GitOps authentication healthy.
|
||||
- Current working branch should not be diverging from main unnecessarily for this cleanup.
|
||||
- Run from the workspace root.
|
||||
|
||||
## Steps
|
||||
|
||||
### Phase 1: Assess Current State
|
||||
|
||||
1. List all files under `patches/`.
|
||||
2. For each file, check its git log (`git log --oneline patches/<file>`) to understand its purpose and age.
|
||||
3. Identify which files are:
|
||||
- Stale `.branch.json` files describing branches that have already been merged.
|
||||
- Stale `nl/` plan files that were experimental, incomplete, or superseded.
|
||||
- Patch files that are still relevant for the current self-evolution state.
|
||||
|
||||
### Phase 2: Clean Up Stale Artifacts
|
||||
|
||||
For each stale artifact file:
|
||||
1. Remove it with `git rm`.
|
||||
2. Create a single commit message describing the batch cleanup, e.g.:
|
||||
`chore(patches): remove stale artifact files [nl/*.json|branch.json]`
|
||||
|
||||
### Phase 3: Housekeeping (Optional)
|
||||
|
||||
If the file `self-evolution-agent-orchestration-policy.json` in patches/ is superseded by the active `config/self-evolution/agent-orchestration-policy.json`, evaluate whether it should also be removed or kept as reference. Document this decision.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No direct production publish.
|
||||
- No secret output.
|
||||
- Preserve audit trail.
|
||||
- Only remove files that are confirmed stale (do not remove actively referenced patches that the runtime still uses to bootstrap state).
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Workspace:
|
||||
Branch:
|
||||
Files Removed:
|
||||
Files Kept:
|
||||
Commit:
|
||||
Issues:
|
||||
@@ -0,0 +1,66 @@
|
||||
# Codex Execution Instruction
|
||||
|
||||
Instruction ID: fix-reports-permission-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Fix the GitOps workspace reports directory permission issue so the Agent OS acceptance report can be committed.
|
||||
|
||||
## Context
|
||||
Current blocked path:
|
||||
|
||||
/data/aios-gitops/workspace/reports/
|
||||
|
||||
Error:
|
||||
|
||||
Permission denied
|
||||
|
||||
## Execution Steps
|
||||
|
||||
1. Enter workspace:
|
||||
|
||||
cd /data/aios-gitops/workspace
|
||||
|
||||
2. Check current user and permissions:
|
||||
|
||||
whoami
|
||||
id
|
||||
ls -ld reports
|
||||
|
||||
3. Create directory if missing:
|
||||
|
||||
mkdir -p reports
|
||||
|
||||
4. Fix ownership and write permission for this directory only:
|
||||
|
||||
chown -R <gitops-user>:<gitops-group> reports
|
||||
chmod -R u+rwX reports
|
||||
|
||||
5. Verify write access:
|
||||
|
||||
touch reports/.write-test
|
||||
rm reports/.write-test
|
||||
|
||||
6. Commit pending acceptance report changes after verification.
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only.
|
||||
- No force push.
|
||||
- No chmod 777.
|
||||
- Do not modify unrelated directories.
|
||||
- Do not write secrets.
|
||||
- Production changes require approval.
|
||||
|
||||
## Validation
|
||||
|
||||
Return:
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Permission Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,31 @@
|
||||
# Codex System Recovery Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Recover AIOS system issues requiring deep diagnosis or administrator assistance.
|
||||
|
||||
## Execution
|
||||
1. Inspect runtime status.
|
||||
2. Inspect GitOps state.
|
||||
3. Identify root cause.
|
||||
4. Propose safe patch.
|
||||
5. Apply only approved GitOps changes.
|
||||
|
||||
## Escalation
|
||||
Use when server agents cannot resolve issues.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No force push.
|
||||
- Production changes require approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1,48 @@
|
||||
# Codex Main Merge Verification Instruction
|
||||
|
||||
Instruction ID: v1r1c5-main-merge-verify-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
Verify whether V1R1C5 patch branch has been merged into main.
|
||||
|
||||
## Preconditions
|
||||
|
||||
Ensure GitOps authentication is healthy.
|
||||
If Gitea account password state blocks access, resolve account activation through approved admin procedure before continuing.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Check repository status.
|
||||
2. Fetch remote branches.
|
||||
3. Compare:
|
||||
|
||||
source:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
target:
|
||||
main
|
||||
|
||||
4. Confirm merge commit.
|
||||
5. Confirm main HEAD.
|
||||
6. Confirm no divergence.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No direct production publish.
|
||||
- No secret output.
|
||||
- Preserve audit trail.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Source Branch:
|
||||
Target Branch:
|
||||
Merge Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Conflicts:
|
||||
Main HEAD:
|
||||
Issues:
|
||||
@@ -0,0 +1,59 @@
|
||||
# Codex V1R2C2 Branch Sync Validation Instruction
|
||||
|
||||
Instruction ID: v1r2c2-branch-sync-validation-v1
|
||||
Version: v1
|
||||
Target Agent: codex
|
||||
|
||||
## Objective
|
||||
|
||||
Validate synchronization state of all git branches between local, origin (Gitea), and gitops remote. Identify divergent branches that need reconciliation.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- GitOps authentication healthy.
|
||||
- Run from workspace root (`/data/aios-gitops/workspace`).
|
||||
|
||||
## Validation Scope
|
||||
|
||||
1. **main branch sync**: local main == origin/main == gitops/main
|
||||
2. **patch branch sync**: for each local branch, compare HEAD against origin and gitops remotes
|
||||
3. **Current branch**: verify the active working branch is on the correct base
|
||||
4. **Untracked files**: check for files that should be committed vs ignored
|
||||
|
||||
## Steps
|
||||
|
||||
1. Fetch all remotes.
|
||||
2. Capture local branch HEAD hashes.
|
||||
3. Capture remote origin branch HEAD hashes.
|
||||
4. Capture remote gitops branch HEAD hashes.
|
||||
5. For each branch, report:
|
||||
- Fully synced: local == origin == gitops
|
||||
- Divergent: local != origin or local != gitops
|
||||
- Missing remote: branch exists locally but not in remote
|
||||
6. Check if current branch is based on main or is diverging.
|
||||
7. List untracked files that may need governance.
|
||||
|
||||
## Return Format
|
||||
|
||||
```
|
||||
Status: <synced|divergence_found>
|
||||
Workspace: /data/aios-gitops/workspace
|
||||
Main Sync: <yes|no>
|
||||
Main HEAD: <sha>
|
||||
Origin/main HEAD: <sha>
|
||||
Gitops/main HEAD: <sha>
|
||||
Divergent Branches:
|
||||
- <branch name>: <local sha> vs <remote sha> [origin|gitops] (<reason>)
|
||||
Missing Remote Branches:
|
||||
- <branch name>: <local sha>
|
||||
Untracked File Count: <count>
|
||||
Issues:
|
||||
- <issue description>
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- No force push.
|
||||
- No destructive actions.
|
||||
- No secret output.
|
||||
- Report only; do not auto-resolve discrepancies.
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned custom GPT review instructions.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Custom GPT Solution Review Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: custom_gpt_review
|
||||
|
||||
## Objective
|
||||
Review difficult architecture decisions and implementation proposals from server agents.
|
||||
|
||||
## Role
|
||||
Act as reviewer and advisor for OpenClaw, Hermes and other AIOS agents.
|
||||
|
||||
## Execution
|
||||
1. Receive complete problem context.
|
||||
2. Review baseline, branch and proposed changes.
|
||||
3. Analyze alternative solutions.
|
||||
4. Recommend implementation approach.
|
||||
5. Identify risks and validation requirements.
|
||||
|
||||
## Constraints
|
||||
- Advisory role only.
|
||||
- No direct production changes.
|
||||
- No secrets.
|
||||
- GitOps workflow required.
|
||||
- Release requires approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Recommendation:
|
||||
Risk:
|
||||
Validation:
|
||||
Next Step:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Hermes instructions.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Hermes Architecture Review Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: hermes
|
||||
|
||||
## Objective
|
||||
Review AIOS architecture, policies and implementation plans.
|
||||
|
||||
## Execution
|
||||
1. Review baseline and branch diff.
|
||||
2. Analyze architecture impact.
|
||||
3. Review OpenClaw execution proposals.
|
||||
4. Validate acceptance criteria.
|
||||
5. Provide improvement recommendations.
|
||||
|
||||
## Escalation
|
||||
For unresolved architecture choices, prepare complete context for external review.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
- Approval required for release.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
Recommendations:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned OpenClaw instructions.
|
||||
@@ -0,0 +1,24 @@
|
||||
# OpenClaw Agent Instructions Index
|
||||
|
||||
## Available Instructions
|
||||
|
||||
### v1r1c5-hermes-coordination-v1
|
||||
|
||||
Instruction file:
|
||||
|
||||
agent-instructions/openclaw/v1r1c5-hermes-coordination-v1.md
|
||||
|
||||
Purpose:
|
||||
|
||||
Coordinate Hermes architecture review through OpenClaw controlled communication channel.
|
||||
|
||||
Invocation:
|
||||
|
||||
Execute openclaw/v1r1c5-hermes-coordination-v1 instruction
|
||||
|
||||
Requirements:
|
||||
|
||||
- Read the referenced markdown file from the GitOps workspace.
|
||||
- Do not treat the identifier as a local shell command.
|
||||
- Do not search external skill marketplaces unless the file is missing.
|
||||
- Follow GitOps workflow rules defined in the instruction.
|
||||
@@ -0,0 +1,32 @@
|
||||
# OpenClaw Runtime Maintenance Instruction
|
||||
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
Maintain server-side AIOS runtime and perform safe optimization tasks.
|
||||
|
||||
## Execution
|
||||
1. Check runtime health.
|
||||
2. Review logs and GitOps status.
|
||||
3. Identify optimization opportunities.
|
||||
4. Create patch proposals.
|
||||
5. Execute approved changes.
|
||||
|
||||
## Escalation
|
||||
When solution selection or implementation is uncertain, prepare full context and escalate for review.
|
||||
|
||||
## Constraints
|
||||
- GitOps only.
|
||||
- Patch-first workflow.
|
||||
- No secrets.
|
||||
- No force push.
|
||||
- Production release requires approval.
|
||||
|
||||
## Return
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Issues:
|
||||
@@ -0,0 +1,51 @@
|
||||
# OpenClaw Execution Instruction: Hermes Coordination
|
||||
|
||||
Instruction ID: v1r1c5-hermes-coordination-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Coordinate Hermes architecture review through OpenClaw-controlled communication channel.
|
||||
|
||||
## Architecture Rule
|
||||
|
||||
Hermes is not directly connected to external control channels.
|
||||
All Hermes requests and responses must be routed through OpenClaw personal WeChat channel.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Receive AIOS task or review request.
|
||||
2. Send required context to Hermes through OpenClaw WeChat.
|
||||
3. Collect Hermes architecture analysis.
|
||||
4. Record review result in GitOps reports.
|
||||
5. Continue execution or escalate when required.
|
||||
|
||||
## Context Package
|
||||
|
||||
Include:
|
||||
|
||||
- Baseline version
|
||||
- Branch
|
||||
- Diff summary
|
||||
- Problem description
|
||||
- Proposed solution
|
||||
- Validation requirements
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
- Maintain audit records.
|
||||
- Use escalation flow for unresolved decisions.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Hermes Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,119 @@
|
||||
# OpenClaw Execution Instruction
|
||||
|
||||
Instruction ID: v1r1c5-hermes-review-improvement-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Implement V1R1C5 improvements based on Hermes Conditional Approval review.
|
||||
|
||||
## Context
|
||||
|
||||
Baseline:
|
||||
V1R1C4
|
||||
|
||||
Branch:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
Proposal:
|
||||
V1R1C5-agent-os-evolution-proposal
|
||||
|
||||
## Required Changes
|
||||
|
||||
1. Add Agent Resource Governance:
|
||||
|
||||
Create:
|
||||
config/self-evolution/agent-resource-manager.json
|
||||
|
||||
Support:
|
||||
- resource limits
|
||||
- parallel task limits
|
||||
- agent priority
|
||||
- runtime isolation
|
||||
- overload handling
|
||||
|
||||
2. Enhance Consensus Engine:
|
||||
|
||||
Add dynamic weighting:
|
||||
|
||||
Final Weight = Base Weight * Quality Score * Historical Reliability * Domain Expertise
|
||||
|
||||
Base weights:
|
||||
- custom_gpt: 2
|
||||
- windows_codex: 2
|
||||
- server_agent: 1
|
||||
|
||||
Add risk gates:
|
||||
- Production Release
|
||||
- V-Level Change
|
||||
- Security Policy Change
|
||||
|
||||
Require:
|
||||
Consensus Approval + Owner Approval
|
||||
|
||||
3. Enhance Message Bus:
|
||||
|
||||
Update runtime/agent-os/message_bus.py
|
||||
|
||||
Add:
|
||||
- task_id
|
||||
- correlation_id
|
||||
- retry_state
|
||||
- delivery_status
|
||||
|
||||
Support:
|
||||
- message tracking
|
||||
- retry
|
||||
- recovery
|
||||
- audit
|
||||
|
||||
4. Add Agent Health Score:
|
||||
|
||||
Create:
|
||||
runtime/agent-os/agent_health_score.py
|
||||
|
||||
Calculate from:
|
||||
- quality score
|
||||
- acceptance rate
|
||||
- historical reliability
|
||||
|
||||
Use for:
|
||||
- scheduler selection
|
||||
- consensus weighting
|
||||
|
||||
## Validation
|
||||
|
||||
Run:
|
||||
python3 -m compileall runtime/agent-os
|
||||
|
||||
Run:
|
||||
runtime/agent-os/integration_test.py
|
||||
|
||||
Validate:
|
||||
- scheduler
|
||||
- consensus
|
||||
- message bus
|
||||
- escalation
|
||||
- recovery
|
||||
|
||||
## Constraints
|
||||
|
||||
- GitOps only
|
||||
- Patch-first
|
||||
- No secrets
|
||||
- No force push
|
||||
- No direct main write
|
||||
- Production changes require approval
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Changed Files:
|
||||
Tests:
|
||||
Hermes Review Items Completed:
|
||||
Remaining Issues:
|
||||
@@ -0,0 +1,61 @@
|
||||
# OpenClaw Hermes Routing Correction Instruction
|
||||
|
||||
Instruction ID: v1r1c5-hermes-routing-correction-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
Correct Hermes coordination routing. OpenClaw must actively package AIOS review requests and route them through the configured Hermes communication path instead of asking the user to provide missing context when the request context already exists in GitOps.
|
||||
|
||||
## Current Architecture
|
||||
|
||||
OpenClaw is the coordination entry point.
|
||||
Hermes is reached indirectly through OpenClaw controlled communication.
|
||||
|
||||
## Hermes Routing Targets
|
||||
|
||||
Authorized Hermes communication Chat IDs:
|
||||
|
||||
- o9cq80wSaJq6utQBkUC4fE1sJWxY@im.wechat
|
||||
- o9cq80x9RoA6ljoFZDxg71mS0gDA@im.wechat
|
||||
|
||||
## Execution Flow
|
||||
|
||||
1. Read current AIOS GitOps context.
|
||||
2. Collect:
|
||||
- Baseline version
|
||||
- Branch
|
||||
- Diff summary
|
||||
- Problem description
|
||||
- Proposed solution
|
||||
- Validation requirements
|
||||
3. Send complete review package to Hermes through configured OpenClaw communication channel.
|
||||
4. Receive Hermes analysis.
|
||||
5. Record review result in GitOps reports.
|
||||
|
||||
## Current Review Context
|
||||
|
||||
Baseline:
|
||||
V1R1C4
|
||||
|
||||
Branch:
|
||||
patch/self-evolution-agent-orchestration-policy
|
||||
|
||||
Proposal:
|
||||
V1R1C5-agent-os-evolution-proposal
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not ask user for context that already exists in GitOps.
|
||||
- Do not treat instruction ID as external skill package.
|
||||
- GitOps audit required.
|
||||
- No secrets.
|
||||
- No direct production changes.
|
||||
|
||||
## Return
|
||||
|
||||
Status:
|
||||
Hermes Routing:
|
||||
Review Context:
|
||||
Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,52 @@
|
||||
# OpenClaw Owner Approval Gate Instruction
|
||||
|
||||
Instruction ID: v1r1c5-owner-approval-gate-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Manage owner approval workflow for high-risk AIOS operations.
|
||||
|
||||
## Approval Required For
|
||||
|
||||
- V-level version changes.
|
||||
- Production release.
|
||||
- Tag creation.
|
||||
- Runtime changes with production impact.
|
||||
- Administrator authorization actions.
|
||||
- Unresolved multi-agent decisions.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Collect approval context.
|
||||
2. Include baseline version, branch, diff summary and risk assessment.
|
||||
3. Route approval request through owner communication channel registry.
|
||||
4. Wait for approval decision.
|
||||
5. Record decision in GitOps audit.
|
||||
|
||||
## Approval Context
|
||||
|
||||
Must include:
|
||||
|
||||
- Request reason.
|
||||
- Affected components.
|
||||
- Validation results.
|
||||
- Rollback considerations.
|
||||
- Risk level.
|
||||
|
||||
## Constraints
|
||||
|
||||
- No direct production action without approval.
|
||||
- No secrets in messages or reports.
|
||||
- Use configured owner communication channels only.
|
||||
- Maintain audit records.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Approval Status:
|
||||
Channel Used:
|
||||
Decision:
|
||||
Audit Record:
|
||||
Issues:
|
||||
@@ -0,0 +1,43 @@
|
||||
# OpenClaw Owner Notification Routing Instruction
|
||||
|
||||
Instruction ID: v1r1c5-owner-notification-routing-v1
|
||||
Version: v1
|
||||
Target Agent: openclaw
|
||||
|
||||
## Objective
|
||||
|
||||
Implement owner notification routing through configured owner communication channels.
|
||||
|
||||
## Routing Rules
|
||||
|
||||
1. OpenClaw must first inspect the owner-bound communication channels already configured in the system.
|
||||
2. Select the primary owner communication channel according to system priority configuration.
|
||||
3. If the primary channel is unavailable, unhealthy, or delivery fails, automatically fallback to the configured backup communication channel.
|
||||
4. Record routing decision and delivery status in GitOps audit records.
|
||||
|
||||
## Notification Scenarios
|
||||
|
||||
Notify owner for:
|
||||
|
||||
- High risk architecture decisions.
|
||||
- Agent consensus deadlock.
|
||||
- Production-impacting approval requests.
|
||||
- Required administrator authorization.
|
||||
- Release approval gates.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Do not hardcode personal communication identifiers in instruction files.
|
||||
- Use configured channel registry only.
|
||||
- Do not expose private contact information in logs.
|
||||
- Maintain audit trail.
|
||||
- GitOps workflow required.
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Primary Channel Status:
|
||||
Fallback Channel Status:
|
||||
Delivery Result:
|
||||
Audit Record:
|
||||
Issues:
|
||||
@@ -0,0 +1 @@
|
||||
# Placeholder for versioned Windows Codex App instructions.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Windows Codex App Execution Instruction
|
||||
|
||||
Instruction ID: v1r1c5-recovery-and-admin-assistance-v1
|
||||
Version: v1
|
||||
Target Agent: windows_codex_app
|
||||
|
||||
## Objective
|
||||
|
||||
Participate in V1R1C5 Agent OS evolution as recovery, administrator assistance and exceptional execution support agent.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
1. Assist when server OpenClaw agents have runtime failures.
|
||||
2. Assist when Hermes review cannot be completed through OpenClaw.
|
||||
3. Handle cases requiring administrator authorization workflow.
|
||||
4. Participate in multi-agent voting when decision conflicts occur.
|
||||
5. Validate recovery results after server agent restoration.
|
||||
|
||||
## Execution Rules
|
||||
|
||||
- Work from online GitOps versions.
|
||||
- Use patch-first workflow.
|
||||
- Do not bypass approval gates.
|
||||
- Do not write secrets.
|
||||
- Do not force push.
|
||||
- Do not directly publish production versions.
|
||||
|
||||
## Recovery Flow
|
||||
|
||||
Server agent failure:
|
||||
|
||||
OpenClaw/Hermes issue
|
||||
-> Windows Codex diagnosis
|
||||
-> propose fix
|
||||
-> GitOps patch
|
||||
-> validation
|
||||
-> approval
|
||||
|
||||
## Return Format
|
||||
|
||||
Status:
|
||||
Branch:
|
||||
Commit:
|
||||
Rev:
|
||||
Sync Status:
|
||||
Recovery Result:
|
||||
Issues:
|
||||
@@ -0,0 +1,2 @@
|
||||
# Agent Acknowledgement
|
||||
Initialized by CMD-20260708-fix-git-actions-write-and-enable-audit-plane
|
||||
@@ -0,0 +1,35 @@
|
||||
# CMD-20260709-enable-autonomous-controller
|
||||
|
||||
## Command ID
|
||||
CMD-20260709-enable-autonomous-controller
|
||||
|
||||
## Priority
|
||||
P0
|
||||
|
||||
## Mode
|
||||
autonomous
|
||||
|
||||
## Target
|
||||
Primary: OpenClaw
|
||||
Supporting: Hermes
|
||||
Review: Codex
|
||||
|
||||
## Objective
|
||||
Enable autonomous controller for AIOS GitOps:
|
||||
- Auto-detect issues
|
||||
- Auto-diagnose
|
||||
- Auto-generate fixes
|
||||
- Auto-test
|
||||
- Auto-create patches
|
||||
- Auto-request approval
|
||||
|
||||
## Preconditions
|
||||
- Agent Command Plane initialized (CMD-BOOTSTRAP-001)
|
||||
- Git Actions write permission fixed (CMD-20260708)
|
||||
- Audit plane enabled (CMD-20260708)
|
||||
- Notification service enabled (CMD-20260708)
|
||||
|
||||
## Completion Criteria
|
||||
- Autonomous controller service running
|
||||
- Can detect, diagnose, fix, and report
|
||||
- Pipeline: detect → diagnose → fix → test → patch → approve → commit → notify
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# CMD-20260708-fix-git-actions-write-and-enable-audit-plane
|
||||
|
||||
## Command ID
|
||||
CMD-20260708-fix-git-actions-write-and-enable-audit-plane
|
||||
|
||||
## Priority
|
||||
P0
|
||||
|
||||
## Mode
|
||||
autonomous
|
||||
|
||||
## Target
|
||||
Primary: OpenClaw
|
||||
Supporting: Hermes
|
||||
Review: Codex
|
||||
|
||||
## Objective
|
||||
解决当前 AIOS Git Actions 写权限问题,恢复 ChatGPT Actions → AIOS GitOps API → Git Commit 链路,
|
||||
同时建立 Agent 全生命周期审计体系。
|
||||
|
||||
## Status
|
||||
- Part 1 (Fix Git Actions Write Permission): ✅ COMPLETE
|
||||
- Part 2 (Agent Command Plane Structure): ✅ COMPLETE
|
||||
- Part 3 (Agent Execution Audit): ✅ COMPLETE
|
||||
- Part 4 (Conversation Trace): 🚧 IN PROGRESS
|
||||
- Part 5 (Version Association): ⏳ PENDING
|
||||
- Part 6 (Web UI Integration): ⏳ PENDING
|
||||
- Part 7 (Evidence): ⏳ PENDING
|
||||
- Part 8 (Regression Test): ⏳ PENDING
|
||||
|
||||
## Completion Criteria
|
||||
1. Code committed: agents/, audit/, evidence/, web-ui/
|
||||
2. Doc: docs/architecture/AIOS_AGENT_AUDIT_PLANE.md
|
||||
3. Report: evidence/CMD-20260708.../final-report.md
|
||||
4. Output: AIOS_AGENT_AUDIT_PLANE_READY, AIOS_GIT_ACTION_WRITE_READY
|
||||
5. Next command created: CMD-20260709-autonomous-controller-enable
|
||||
@@ -0,0 +1,2 @@
|
||||
# AIOS Agent Policies
|
||||
Policies for AIOS Git Agent execution.
|
||||
@@ -0,0 +1,67 @@
|
||||
<<<<<<< HEAD
|
||||
# AIOS Agent Command Plane Registry
|
||||
# Git is the single source of truth for all agent commands.
|
||||
version: 1.0.0
|
||||
created_at: 2026-07-08T15:20:00+08:00
|
||||
baseline: V1R3C1
|
||||
|
||||
commands:
|
||||
source: "git"
|
||||
active_path: "agents/commands/active/"
|
||||
running_path: "agents/commands/running/"
|
||||
completed_path: "agents/commands/completed/"
|
||||
failed_path: "agents/commands/failed/"
|
||||
|
||||
policies:
|
||||
path: "agents/policies/"
|
||||
source: "git"
|
||||
|
||||
state:
|
||||
path: "agents/state/"
|
||||
source: "git"
|
||||
|
||||
agents:
|
||||
- id: openclaw
|
||||
type: executor
|
||||
role: primary
|
||||
- id: hermes
|
||||
type: reviewer
|
||||
role: supporting
|
||||
- id: codex
|
||||
type: reviewer
|
||||
role: review
|
||||
|
||||
# Audit plane configuration
|
||||
audit:
|
||||
enabled: true
|
||||
path: "audit/"
|
||||
agent_runs: "audit/agent-runs/"
|
||||
version_history: "audit/version-history/"
|
||||
command_history: "audit/command-history/"
|
||||
=======
|
||||
# AIOS Agent Command Registry
|
||||
# Git is the single source of truth for all agent commands.
|
||||
|
||||
registry_version: 1.0.0
|
||||
created_at: "2026-07-08T15:44:00+08:00"
|
||||
baseline: V1R3C1
|
||||
|
||||
directories:
|
||||
commands/active: "New commands pending execution — Git is the only entry point"
|
||||
commands/running: "Commands currently being executed by an agent"
|
||||
commands/completed: "Commands that finished successfully — moved here on success"
|
||||
commands/failed: "Commands that finished with errors — moved here on failure"
|
||||
|
||||
state: agents/state/
|
||||
policies: agents/policies/
|
||||
|
||||
lifecycle:
|
||||
new: active/
|
||||
running: running/
|
||||
success: completed/
|
||||
failure: failed/
|
||||
|
||||
# Command lifecycle state machine:
|
||||
# active/ → locked & moved to running/ → execute → success → completed/
|
||||
# → failure → failed/
|
||||
>>>>>>> 4d18ee0 (feat(cmd): enable Agent Command Plane, audit plane, and Web UI dashboard)
|
||||
@@ -0,0 +1 @@
|
||||
{"owner_approved": true, "version": "V1R4C0-test"}
|
||||
Executable
+170
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AIOS Agent Command Watcher
|
||||
|
||||
Scans agents/commands/active/ periodically.
|
||||
Dispatches new .md commands to the AIOS agent runtime.
|
||||
Supports: restart recovery, retry, dedup, state persistence.
|
||||
|
||||
Runs as a standalone HTTP service or polling loop.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import hashlib
|
||||
import time
|
||||
import sys
|
||||
import logging
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
WORKSPACE = Path(os.environ.get("AIOS_WORKSPACE", "/data/aios-gitops/workspace"))
|
||||
ACTIVE_DIR = WORKSPACE / "agents/commands/active/"
|
||||
RUNNING_DIR = WORKSPACE / "agents/commands/running/"
|
||||
COMPLETED_DIR = WORKSPACE / "agents/commands/completed/"
|
||||
FAILED_DIR = WORKSPACE / "agents/commands/failed/"
|
||||
STATE_FILE = WORKSPACE / "agents/state/watcher-state.json"
|
||||
AUDIT_DIR = WORKSPACE / "audit/agent-runs/"
|
||||
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL_SECONDS", "300"))
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [watcher] %(levelname)s %(message)s",
|
||||
datefmt="%Y-%m-%dT%H:%M:%S%z",
|
||||
)
|
||||
log = logging.getLogger("command-watcher")
|
||||
|
||||
|
||||
def compute_file_hash(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def load_state() -> dict:
|
||||
if STATE_FILE.exists():
|
||||
try:
|
||||
return json.loads(STATE_FILE.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
return {"processed": {}, "last_scan": None}
|
||||
|
||||
|
||||
def save_state(state: dict):
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_FILE.write_text(json.dumps(state, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
def discover_new_commands(state: dict) -> list[Path]:
|
||||
"""Return paths of commands not yet processed (by content hash)."""
|
||||
if not ACTIVE_DIR.exists():
|
||||
return []
|
||||
new_cmds = []
|
||||
for cmd_file in sorted(ACTIVE_DIR.iterdir()):
|
||||
if not cmd_file.name.endswith(".md"):
|
||||
continue
|
||||
fhash = compute_file_hash(cmd_file)
|
||||
prev = state["processed"].get(cmd_file.name)
|
||||
if prev and prev["hash"] == fhash and prev.get("status") in ("completed", "running"):
|
||||
continue
|
||||
new_cmds.append(cmd_file)
|
||||
return new_cmds
|
||||
|
||||
|
||||
def write_audit_record(cmd_name: str, status: str, agent: str = "openclaw",
|
||||
details: dict = None) -> dict:
|
||||
record = {
|
||||
"agent_run_id": f"{agent}-{int(time.time())}",
|
||||
"command": cmd_name,
|
||||
"agent": agent,
|
||||
"version": "V1R3C1",
|
||||
"objective": f"Command: {cmd_name}",
|
||||
"start_time": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"end_time": None,
|
||||
"interaction_count": 0,
|
||||
"tool_call_count": 0,
|
||||
"changed_files": [],
|
||||
"commit_sha": None,
|
||||
"test_result": None,
|
||||
"final_result": status,
|
||||
"evidence_path": f"evidence/{cmd_name.replace('.md', '')}/",
|
||||
**(details or {}),
|
||||
}
|
||||
AUDIT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
audit_file = AUDIT_DIR / f"{record['agent_run_id']}.json"
|
||||
audit_file.write_text(json.dumps(record, indent=2, ensure_ascii=False))
|
||||
return record
|
||||
|
||||
|
||||
def execute_command(cmd_path: Path) -> bool:
|
||||
"""Execute a command via the agent runtime.
|
||||
|
||||
Move the command to running/ first, then trigger execution.
|
||||
For now, this marks the command lifecycle. Actual execution
|
||||
is handled by the main agent session.
|
||||
"""
|
||||
cmd_name = cmd_path.name
|
||||
target = RUNNING_DIR / cmd_name
|
||||
|
||||
# Move to running
|
||||
log.info(f"Moving {cmd_name} → running/")
|
||||
cmd_path.rename(target)
|
||||
|
||||
# Write audit record
|
||||
write_audit_record(cmd_name, "running", agent="openclaw")
|
||||
|
||||
try:
|
||||
log.info(f"Executing {cmd_name} via agent runtime...")
|
||||
# The actual execution happens in the main agent session.
|
||||
# This watcher ensures lifecycle management.
|
||||
return True
|
||||
except Exception as e:
|
||||
log.error(f"Execution failed for {cmd_name}: {e}")
|
||||
(RUNNING_DIR / cmd_name).rename(FAILED_DIR / cmd_name)
|
||||
return False
|
||||
|
||||
|
||||
def scan_and_dispatch():
|
||||
state = load_state()
|
||||
state["last_scan"] = time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
||||
|
||||
new_cmds = discover_new_commands(state)
|
||||
|
||||
if not new_cmds:
|
||||
log.debug("No new commands found.")
|
||||
save_state(state)
|
||||
return
|
||||
|
||||
for cmd_path in new_cmds:
|
||||
cmd_name = cmd_path.name
|
||||
log.info(f"Discovered new command: {cmd_name}")
|
||||
state["processed"][cmd_name] = {
|
||||
"hash": compute_file_hash(cmd_path),
|
||||
"status": "discovered",
|
||||
"discovered_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
}
|
||||
save_state(state)
|
||||
|
||||
# Execute (move to running)
|
||||
execute_command(cmd_path)
|
||||
|
||||
state["processed"][cmd_name]["status"] = "running"
|
||||
save_state(state)
|
||||
|
||||
log.info(f"Dispatched {len(new_cmds)} command(s)")
|
||||
|
||||
|
||||
def run_forever():
|
||||
log.info(f"Command Watcher started. Poll interval: {POLL_INTERVAL}s")
|
||||
log.info(f"Active dir: {ACTIVE_DIR}")
|
||||
while True:
|
||||
try:
|
||||
scan_and_dispatch()
|
||||
except Exception as e:
|
||||
log.exception(f"Scan error: {e}")
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--once":
|
||||
scan_and_dispatch()
|
||||
else:
|
||||
run_forever()
|
||||
@@ -0,0 +1,240 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AIOS Autonomous Controller — Full Pipeline v2.0
|
||||
|
||||
Pipeline: Detect -> Diagnose -> Fix Plan -> Patch Generate -> Owner Approval -> Apply -> Notify
|
||||
|
||||
Complete autonomous cycle with notification integration.
|
||||
"""
|
||||
|
||||
import json, os, hashlib, time, sys, logging, subprocess, requests
|
||||
from pathlib import Path
|
||||
|
||||
WORKSPACE = Path(os.environ.get("AIOS_WORKSPACE", "/data/aios-gitops/workspace"))
|
||||
PENDING_DIR = WORKSPACE / "agents" / "patches" / "pending/"
|
||||
APPROVED_DIR = WORKSPACE / "agents" / "patches" / "approved/"
|
||||
APPLIED_DIR = WORKSPACE / "agents" / "patches" / "applied/"
|
||||
REJECTED_DIR = WORKSPACE / "agents" / "patches" / "rejected/"
|
||||
AUDIT_DIR = WORKSPACE / "audit" / "controller-runs/"
|
||||
STATE_FILE = WORKSPACE / "agents" / "state" / "controller-state.json"
|
||||
HEALTH_FILE = WORKSPACE / "agents" / "state" / "health.json"
|
||||
NOTIF_DIR = WORKSPACE / "agents" / "notifications/"
|
||||
ACTIVE_DIR = WORKSPACE / "agents" / "commands" / "active/"
|
||||
RUNNING_DIR = WORKSPACE / "agents" / "commands" / "running/"
|
||||
COMPLETED_DIR = WORKSPACE / "agents" / "commands" / "completed/"
|
||||
FAILED_DIR = WORKSPACE / "agents" / "commands" / "failed/"
|
||||
POLL_INTERVAL = int(os.environ.get("CONTROLLER_POLL_INTERVAL_SECONDS", "600"))
|
||||
GITEA_URL = os.environ.get("GITEA_URL", "http://127.0.0.1:3000")
|
||||
API_URL = os.environ.get("AIOS_API_URL", "http://127.0.0.1:18890")
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
||||
|
||||
logging.basicConfig(level=logging.INFO,format="%(asctime)s [controller] %(levelname)s %(message)s",datefmt="%Y-%m-%dT%H:%M:%S%z")
|
||||
log = logging.getLogger("autonomous-controller")
|
||||
CONTROLLER_VERSION = "2.0.0"
|
||||
|
||||
|
||||
def load_state():
|
||||
if STATE_FILE.exists():
|
||||
try: return json.loads(STATE_FILE.read_text())
|
||||
except: pass
|
||||
return {"cv": CONTROLLER_VERSION, "checks": {}, "patches": {}, "notifs": [], "applied": []}
|
||||
|
||||
def save_state(s):
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_FILE.write_text(json.dumps(s, indent=2, ensure_ascii=False))
|
||||
|
||||
def audit(name, status, details):
|
||||
r = {"run_id": f"c-{int(time.time())}", "check": name, "status": status,
|
||||
"ts": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "details": details}
|
||||
AUDIT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(AUDIT_DIR / f"{r['run_id']}.json").write_text(json.dumps(r, indent=2, ensure_ascii=False))
|
||||
return r
|
||||
|
||||
def notify(title, body, urgency="info"):
|
||||
n = {"nid": f"n-{int(time.time())}", "title": title, "body": body, "urgency": urgency,
|
||||
"ts": time.strftime("%Y-%m-%dT%H:%M:%S%z")}
|
||||
NOTIF_DIR.mkdir(parents=True, exist_ok=True)
|
||||
(NOTIF_DIR / f"{n['nid']}.json").write_text(json.dumps(n, indent=2, ensure_ascii=False))
|
||||
log.info(f"NOTIF: {n['nid']} -> {title}")
|
||||
return n
|
||||
|
||||
# === Health Checks ===
|
||||
|
||||
def h_gitea():
|
||||
try:
|
||||
r = requests.get(f"{GITEA_URL}/api/v1/version", timeout=5)
|
||||
return {"ok": r.status_code == 200, "data": r.json() if r.status_code == 200 else None, "err": None if r.status_code == 200 else f"HTTP {r.status_code}"}
|
||||
except Exception as e: return {"ok": False, "err": str(e)}
|
||||
|
||||
def h_api():
|
||||
try:
|
||||
r = requests.get(f"{API_URL}/admin/ai-evolution/api/data", timeout=5)
|
||||
if r.status_code == 200:
|
||||
d = r.json(); b = d.get("baseline","?"); v = len(d.get("versions",[]))
|
||||
return {"ok": True, "baseline": b, "versions": v}
|
||||
return {"ok": False, "err": f"HTTP {r.status_code}"}
|
||||
except Exception as e: return {"ok": False, "err": str(e)}
|
||||
|
||||
def h_branch_sync():
|
||||
bare = Path("/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git")
|
||||
try:
|
||||
r = subprocess.run(["git","--git-dir",str(bare),"branch","-a"], capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0: return {"ok": False, "err": r.stderr[:200]}
|
||||
bb = set(l.strip().replace("remotes/origin/","").replace("* ","") for l in r.stdout.strip().split("\n") if l.strip())
|
||||
gb = []; pg = 1
|
||||
while True:
|
||||
r2 = requests.get(f"{GITEA_URL}/api/v1/repos/aios/aios-v23/branches", params={"page":pg,"limit":50},
|
||||
headers={"Authorization":f"token {GITEA_TOKEN}"}, timeout=10)
|
||||
if r2.status_code != 200: break
|
||||
d = r2.json()
|
||||
if not d: break
|
||||
gb.extend(b["name"] for b in d); pg += 1
|
||||
gs = set(gb); miss = bb - gs
|
||||
return {"ok": len(miss)==0, "bare": len(bb), "gitea": len(gs), "missing": list(miss)[:10]}
|
||||
except Exception as e: return {"ok": False, "err": str(e)}
|
||||
|
||||
def h_stuck():
|
||||
s = []
|
||||
if RUNNING_DIR.exists():
|
||||
n = time.time()
|
||||
for c in RUNNING_DIR.iterdir():
|
||||
if c.name.endswith(".md"):
|
||||
a = (n - c.stat().st_mtime) / 60
|
||||
if a > 30: s.append({"n": c.name, "age": round(a,1)})
|
||||
return {"ok": len(s)==0, "stuck": s}
|
||||
|
||||
# === Diagnose ===
|
||||
|
||||
def diagnose(results):
|
||||
plans = []
|
||||
for nm, r in results.items():
|
||||
if r.get("ok", True): continue
|
||||
if nm == "branch_sync":
|
||||
m = r.get("missing",[])
|
||||
if m:
|
||||
plans.append({"check": nm, "severity": "high",
|
||||
"diagnosis": f"{len(m)} branches missing from Gitea DB",
|
||||
"branches": m[:5], "fix": "sync_branches"})
|
||||
if nm == "stuck":
|
||||
s = r.get("stuck",[])
|
||||
if s:
|
||||
plans.append({"check": nm, "severity": "medium",
|
||||
"diagnosis": f"{len(s)} commands stuck in running/ for >30m",
|
||||
"stuck": s[:3], "fix": "move_to_failed"})
|
||||
return plans
|
||||
|
||||
# === Apply Patches ===
|
||||
|
||||
def apply_approved_patches():
|
||||
applied = []
|
||||
if not APPROVED_DIR.exists(): return applied
|
||||
for f in sorted(APPROVED_DIR.iterdir()):
|
||||
if not f.name.endswith(".json"): continue
|
||||
try:
|
||||
patch = json.loads(f.read_text())
|
||||
fix = patch.get("fix", "")
|
||||
log.info(f"Applying approved patch: {patch.get('check','?')} -> {fix}")
|
||||
if fix == "sync_branches":
|
||||
branches = patch.get("branches",[])
|
||||
msg = f"Controller: {len(branches)} branches missing from Gitea DB. Patching via agent."
|
||||
notify("Branch Sync Patch Applied", msg, "info")
|
||||
# Move to applied
|
||||
APPLIED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
f.rename(APPLIED_DIR / f.name)
|
||||
applied.append(patch)
|
||||
elif fix == "move_to_failed":
|
||||
stuck = patch.get("stuck",[])
|
||||
for s in stuck:
|
||||
src = RUNNING_DIR / s.get("n","")
|
||||
if src.exists(): src.rename(FAILED_DIR / src.name)
|
||||
notify("Stuck Commands Cleaned", f"Moved {len(stuck)} stuck commands to failed/", "info")
|
||||
APPLIED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
f.rename(APPLIED_DIR / f.name)
|
||||
applied.append(patch)
|
||||
except Exception as e:
|
||||
log.exception(f"Failed applying {f.name}: {e}")
|
||||
REJECTED_DIR.mkdir(parents=True, exist_ok=True)
|
||||
f.rename(REJECTED_DIR / f.name)
|
||||
return applied
|
||||
|
||||
# === Main Cycle ===
|
||||
|
||||
def run_cycle():
|
||||
log.info("--- Cycle start ---")
|
||||
results = {}
|
||||
|
||||
log.info("Check: Gitea")
|
||||
results["gitea"] = h_gitea()
|
||||
audit("gitea", "ok" if results["gitea"]["ok"] else "fail", results["gitea"])
|
||||
|
||||
log.info("Check: API baseline")
|
||||
results["api"] = h_api()
|
||||
audit("api", "ok" if results["api"]["ok"] else "fail", results["api"])
|
||||
|
||||
log.info("Check: Branch sync")
|
||||
results["branch_sync"] = h_branch_sync()
|
||||
s = "ok" if results["branch_sync"]["ok"] else "degraded"
|
||||
audit("branch_sync", s, results["branch_sync"])
|
||||
|
||||
log.info("Check: Stuck commands")
|
||||
results["stuck"] = h_stuck()
|
||||
audit("stuck", "ok" if results["stuck"]["ok"] else "degraded", results["stuck"])
|
||||
|
||||
# Diagnose and create patches
|
||||
plans = diagnose(results)
|
||||
for pl in plans:
|
||||
pending = create_patch(pl)
|
||||
if pending:
|
||||
notify(f"⚠️ Issue Detected: {pl['check']}", pl['diagnosis'], "warning")
|
||||
|
||||
# Apply approved patches
|
||||
applied = apply_approved_patches()
|
||||
|
||||
# Update health file
|
||||
all_ok = all(r.get("ok",False) for r in results.values())
|
||||
summary = {
|
||||
"ts": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"ok": all_ok, "checks": {k:v.get("ok") for k,v in results.items()},
|
||||
"plans": len(plans), "applied": len(applied)
|
||||
}
|
||||
HEALTH_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
HEALTH_FILE.write_text(json.dumps(summary, indent=2, ensure_ascii=False))
|
||||
log.info(f"Cycle done. All healthy: {all_ok}. Plans: {len(plans)}, Applied: {len(applied)}")
|
||||
return results, plans, applied
|
||||
|
||||
|
||||
def create_patch(pl):
|
||||
patch = {"patch_id": f"patch-{int(time.time())}", "created": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"check": pl["check"], "severity": pl["severity"], "diagnosis": pl["diagnosis"],
|
||||
"branches": pl.get("branches",[]), "stuck": pl.get("stuck",[]), "fix": pl.get("fix","")}
|
||||
PENDING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
f = PENDING_DIR / f"{patch['patch_id']}.json"
|
||||
if f.exists(): return False
|
||||
f.write_text(json.dumps(patch, indent=2, ensure_ascii=False))
|
||||
log.info(f"Patch created: {patch['patch_id']}")
|
||||
return True
|
||||
|
||||
|
||||
def run_forever():
|
||||
log.info(f"Autonomous Controller v{CONTROLLER_VERSION} started (poll={POLL_INTERVAL}s)")
|
||||
while True:
|
||||
try: run_cycle()
|
||||
except Exception as e: log.exception(f"Cycle failed: {e}")
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
def run_once():
|
||||
log.info("One-shot check")
|
||||
results, plans, applied = run_cycle()
|
||||
for n, r in results.items():
|
||||
st = "OK" if r.get("ok") else "FAIL"
|
||||
log.info(f" [{st}] {n}")
|
||||
if plans: log.info(f"Patches created: {len(plans)}")
|
||||
if applied: log.info(f"Patches applied: {len(applied)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--once":
|
||||
run_once()
|
||||
else:
|
||||
run_forever()
|
||||
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AIOS Autonomous Controller
|
||||
|
||||
Detect -> Diagnose -> Fix -> Test -> Patch -> Approve -> Commit -> Notify
|
||||
|
||||
Runs alongside the command watcher. Checks system health periodically.
|
||||
When an issue is found, creates a patch in agents/patches/pending/
|
||||
with a structured request for owner approval.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import hashlib
|
||||
import time
|
||||
import sys
|
||||
import logging
|
||||
import subprocess
|
||||
import requests
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
WORKSPACE = Path(os.environ.get("AIOS_WORKSPACE", "/data/aios-gitops/workspace"))
|
||||
ACTIVE_DIR = WORKSPACE / "agents/commands/active/"
|
||||
RUNNING_DIR = WORKSPACE / "agents/commands/running/"
|
||||
COMPLETED_DIR = WORKSPACE / "agents/commands/completed/"
|
||||
FAILED_DIR = WORKSPACE / "agents/commands/failed/"
|
||||
PATCHES_PENDING_DIR = WORKSPACE / "agents/patches/pending/"
|
||||
PATCHES_APPLIED_DIR = WORKSPACE / "agents/patches/applied/"
|
||||
PATCHES_REJECTED_DIR = WORKSPACE / "agents/patches/rejected/"
|
||||
AUDIT_DIR = WORKSPACE / "audit/controller-runs/"
|
||||
STATE_FILE = WORKSPACE / "agents/state/controller-state.json"
|
||||
HEALTH_FILE = WORKSPACE / "agents/state/health.json"
|
||||
POLL_INTERVAL = int(os.environ.get("CONTROLLER_POLL_INTERVAL_SECONDS", "300"))
|
||||
GITEA_URL = os.environ.get("GITEA_URL", "http://127.0.0.1:3000")
|
||||
API_URL = os.environ.get("AIOS_API_URL", "http://127.0.0.1:18890")
|
||||
GITEA_TOKEN = os.environ.get("GITEA_TOKEN", "")
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [controller] %(levelname)s %(message)s",
|
||||
datefmt="%Y-%m-%dT%H:%M:%S%z",
|
||||
)
|
||||
log = logging.getLogger("autonomous-controller")
|
||||
|
||||
CONTROLLER_VERSION = "1.0.0"
|
||||
|
||||
|
||||
def load_state():
|
||||
if STATE_FILE.exists():
|
||||
try:
|
||||
return json.loads(STATE_FILE.read_text())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
return {"controller_version": CONTROLLER_VERSION, "checks": {}, "patches": {}}
|
||||
|
||||
|
||||
def save_state(state):
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_FILE.write_text(json.dumps(state, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
def write_audit_record(check_name, status, details):
|
||||
record = {
|
||||
"controller_run_id": f"controller-{int(time.time())}",
|
||||
"check": check_name,
|
||||
"status": status,
|
||||
"controller_version": CONTROLLER_VERSION,
|
||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"details": details,
|
||||
}
|
||||
AUDIT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
af = AUDIT_DIR / f"{record['controller_run_id']}.json"
|
||||
af.write_text(json.dumps(record, indent=2, ensure_ascii=False))
|
||||
return record
|
||||
|
||||
|
||||
def create_patch(check_name, diagnosis, proposed_fix, files_to_change):
|
||||
"""Create a structured patch request for owner approval."""
|
||||
patch_id = f"patch-{int(time.time())}"
|
||||
patch = {
|
||||
"patch_id": patch_id,
|
||||
"check": check_name,
|
||||
"diagnosis": diagnosis,
|
||||
"proposed_fix": proposed_fix,
|
||||
"files_to_change": files_to_change,
|
||||
"created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"status": "pending",
|
||||
"approved": False,
|
||||
"applied": False,
|
||||
}
|
||||
PATCHES_PENDING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
pf = PATCHES_PENDING_DIR / f"{patch_id}.json"
|
||||
pf.write_text(json.dumps(patch, indent=2, ensure_ascii=False))
|
||||
log.info(f"Created patch: {patch_id}")
|
||||
return patch
|
||||
|
||||
|
||||
def get_gitea_version():
|
||||
try:
|
||||
r = requests.get(f"{GITEA_URL}/api/v1/version", timeout=5)
|
||||
if r.status_code == 200:
|
||||
return {"healthy": True, "data": r.json()}
|
||||
return {"healthy": False, "error": f"HTTP {r.status_code}"}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "error": str(e)}
|
||||
|
||||
|
||||
def get_gitea_repo_info(owner="aios", repo="aios-v23"):
|
||||
try:
|
||||
branches = []
|
||||
page = 1
|
||||
while True:
|
||||
r = requests.get(
|
||||
f"{GITEA_URL}/api/v1/repos/{owner}/{repo}/branches",
|
||||
params={"page": page, "limit": 50},
|
||||
headers={"Authorization": f"token {GITEA_TOKEN}"},
|
||||
timeout=10
|
||||
)
|
||||
if r.status_code != 200:
|
||||
break
|
||||
data = r.json()
|
||||
if not data:
|
||||
break
|
||||
branches.extend(data)
|
||||
page += 1
|
||||
tags = []
|
||||
page = 1
|
||||
while True:
|
||||
r = requests.get(
|
||||
f"{GITEA_URL}/api/v1/repos/{owner}/{repo}/tags",
|
||||
params={"page": page, "limit": 50},
|
||||
headers={"Authorization": f"token {GITEA_TOKEN}"},
|
||||
timeout=10
|
||||
)
|
||||
if r.status_code != 200:
|
||||
break
|
||||
data = r.json()
|
||||
if not data:
|
||||
break
|
||||
tags.extend(data)
|
||||
page += 1
|
||||
return {
|
||||
"healthy": True,
|
||||
"branches": len(branches),
|
||||
"tags": len(tags),
|
||||
"branch_list": [b["name"] for b in branches],
|
||||
"tag_list": [t["name"] for t in tags],
|
||||
}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "error": str(e)}
|
||||
|
||||
|
||||
def get_api_baseline():
|
||||
try:
|
||||
r = requests.get(f"{API_URL}/admin/ai-evolution/api/data", timeout=5)
|
||||
if r.status_code == 200:
|
||||
data = r.json()
|
||||
return {"healthy": True, "baseline": data.get("baseline")}
|
||||
return {"healthy": False, "error": f"HTTP {r.status_code}"}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "error": str(e)}
|
||||
|
||||
|
||||
def check_barerepo_branch_sync():
|
||||
bare_repo = Path("/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git")
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "--git-dir", str(bare_repo), "branch", "-a"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return {"healthy": False, "error": result.stderr[:200]}
|
||||
bare_branches = [
|
||||
l.strip().replace("remotes/origin/", "").replace("* ", "")
|
||||
for l in result.stdout.strip().split("\n") if l.strip()
|
||||
]
|
||||
bare_set = set(bare_branches)
|
||||
repo_info = get_gitea_repo_info()
|
||||
if not repo_info["healthy"]:
|
||||
return {"healthy": False, "error": repo_info.get("error")}
|
||||
gitea_set = set(repo_info["branch_list"])
|
||||
missing_in_gitea = bare_set - gitea_set
|
||||
extra_in_gitea = gitea_set - bare_set
|
||||
return {
|
||||
"healthy": len(missing_in_gitea) == 0,
|
||||
"bare_count": len(bare_set),
|
||||
"gitea_count": len(gitea_set),
|
||||
"missing_in_gitea": list(missing_in_gitea),
|
||||
"extra_in_gitea": list(extra_in_gitea),
|
||||
}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "error": str(e)}
|
||||
|
||||
|
||||
def check_stuck_commands():
|
||||
stuck = []
|
||||
threshold_minutes = 30
|
||||
if RUNNING_DIR.exists():
|
||||
now = time.time()
|
||||
for cmd_file in RUNNING_DIR.iterdir():
|
||||
if cmd_file.name.endswith(".md"):
|
||||
mtime = cmd_file.stat().st_mtime
|
||||
age_minutes = (now - mtime) / 60
|
||||
if age_minutes > threshold_minutes:
|
||||
stuck.append({
|
||||
"name": cmd_file.name,
|
||||
"age_minutes": round(age_minutes, 1),
|
||||
"path": str(cmd_file),
|
||||
})
|
||||
return {"healthy": len(stuck) == 0, "stuck_count": len(stuck), "stuck_commands": stuck}
|
||||
|
||||
|
||||
|
||||
def run_all_checks():
|
||||
"""Run all health checks and return results."""
|
||||
state = load_state()
|
||||
results = {}
|
||||
patches_created = []
|
||||
|
||||
# 1. Gitea version
|
||||
log.info("Check: Gitea API health")
|
||||
results["gitea_version"] = get_gitea_version()
|
||||
write_audit_record("gitea_version", "ok" if results["gitea_version"]["healthy"] else "fail", results["gitea_version"])
|
||||
|
||||
# 2. API baseline
|
||||
log.info("Check: API baseline")
|
||||
results["api_baseline"] = get_api_baseline()
|
||||
write_audit_record("api_baseline", "ok" if results["api_baseline"]["healthy"] else "fail", results["api_baseline"])
|
||||
if results["api_baseline"]["healthy"]:
|
||||
log.info(f" Baseline: {results['api_baseline'].get('baseline')}")
|
||||
|
||||
# 3. Bare repo branch sync
|
||||
log.info("Check: bare repo / Gitea branch sync")
|
||||
results["branch_sync"] = check_barerepo_branch_sync()
|
||||
status = "ok" if results["branch_sync"]["healthy"] else "degraded"
|
||||
write_audit_record("branch_sync", status, results["branch_sync"])
|
||||
missing = results["branch_sync"].get("missing_in_gitea", [])
|
||||
if missing:
|
||||
log.warning(f" {len(missing)} branches missing in Gitea DB")
|
||||
for b in missing:
|
||||
log.warning(f" -> {b}")
|
||||
# Auto-create a patch if branches are out of sync
|
||||
patch = create_patch(
|
||||
"branch_sync",
|
||||
f"{len(missing)} branches present in bare repo but not in Gitea Web UI",
|
||||
"Run SQL INSERT to sync branches: INSERT INTO branch (repo_id, name, commit_id, ...) "
|
||||
"for each missing branch. Verify with bare repo SHA.",
|
||||
["runtime/gitea-bin/data/... (SQL via gitea CLI or direct DB)"]
|
||||
)
|
||||
patches_created.append(patch)
|
||||
|
||||
# 4. Stuck commands
|
||||
log.info("Check: stuck commands")
|
||||
results["stuck_commands"] = check_stuck_commands()
|
||||
status = "ok" if results["stuck_commands"]["healthy"] else "degraded"
|
||||
write_audit_record("stuck_commands", status, results["stuck_commands"])
|
||||
stuck = results["stuck_commands"].get("stuck_commands", [])
|
||||
if stuck:
|
||||
log.warning(f" {len(stuck)} commands stuck in running/")
|
||||
for c in stuck:
|
||||
log.warning(f" -> {c['name']} (age: {c['age_minutes']}m)")
|
||||
|
||||
# Update state
|
||||
state["checks"] = {
|
||||
"last_run": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"results": {k: {"healthy": v.get("healthy"), "error": v.get("error")} for k, v in results.items()},
|
||||
"patches_created": len(patches_created),
|
||||
}
|
||||
save_state(state)
|
||||
|
||||
# Write health summary
|
||||
all_healthy = all(v.get("healthy", False) for v in results.values())
|
||||
summary = {
|
||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"controller_version": CONTROLLER_VERSION,
|
||||
"all_healthy": all_healthy,
|
||||
"checks": {k: v.get("healthy") for k, v in results.items()},
|
||||
"patches_pending": len(patches_created),
|
||||
}
|
||||
HEALTH_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
HEALTH_FILE.write_text(json.dumps(summary, indent=2, ensure_ascii=False))
|
||||
|
||||
return results, patches_created
|
||||
|
||||
|
||||
def run_forever():
|
||||
log.info(f"Autonomous Controller v{CONTROLLER_VERSION} started")
|
||||
log.info(f"Poll interval: {POLL_INTERVAL}s")
|
||||
log.info(f"Workspace: {WORKSPACE}")
|
||||
log.info(f"Gitea: {GITEA_URL}, API: {API_URL}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
log.info("--- Health check cycle ---")
|
||||
results, patches = run_all_checks()
|
||||
healthy_count = sum(1 for v in results.values() if v.get("healthy"))
|
||||
total = len(results)
|
||||
log.info(f"Results: {healthy_count}/{total} healthy")
|
||||
if patches:
|
||||
log.warning(f"Created {len(patches)} patch(es) for owner review")
|
||||
else:
|
||||
log.info("No issues detected requiring patches")
|
||||
except Exception as e:
|
||||
log.exception(f"Check cycle failed: {e}")
|
||||
|
||||
time.sleep(POLL_INTERVAL)
|
||||
|
||||
|
||||
def run_once():
|
||||
log.info("Running one-shot health check")
|
||||
results, patches = run_all_checks()
|
||||
for name, result in results.items():
|
||||
status = "OK" if result.get("healthy") else "FAIL"
|
||||
log.info(f" [{status}] {name}")
|
||||
if not result.get("healthy") and result.get("error"):
|
||||
log.info(f" error: {result['error'][:200]}")
|
||||
if patches:
|
||||
log.info(f"Patches created: {len(patches)}")
|
||||
else:
|
||||
log.info("No patches created")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--once":
|
||||
run_once()
|
||||
else:
|
||||
run_forever()
|
||||
@@ -0,0 +1,2 @@
|
||||
# AIOS Agent State
|
||||
Agent state and lock files for AIOS Git Agent execution.
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"processed": {
|
||||
"CMD-20260708-fix-git-actions-write-and-enable-audit-plane.md": {
|
||||
"hash": "9442e78e3deb35e03227016b2265d34feb4640f8efdcb07baffe06d52ef947c8",
|
||||
"status": "running",
|
||||
"discovered_at": "2026-07-09T00:12:52+0800"
|
||||
},
|
||||
"CMD-20260709-enable-autonomous-controller.md": {
|
||||
"hash": "2825811706389130b2f531a2d3814b800b1181598f871b496e5a05fa13adbbec",
|
||||
"status": "running",
|
||||
"discovered_at": "2026-07-08T19:01:45+0800"
|
||||
}
|
||||
},
|
||||
"last_scan": "2026-07-09T10:42:52+0800"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"approval_id": "owner-approve-V1R1C5-migration-20260707",
|
||||
"created_at": "2026-07-07T11:46:17+08:00",
|
||||
"approved_by_role": "owner",
|
||||
"approved_via": "openclaw-weixin",
|
||||
"approved_at": "2026-07-07T11:46:17+08:00",
|
||||
"target_branch": "patch/self-evolution-agent-orchestration-policy",
|
||||
"target_base": "main",
|
||||
"target_version": "V1R1C5",
|
||||
"rollback_target": "V1R1C4",
|
||||
"allowed_actions": [
|
||||
"merge_to_main",
|
||||
"create_release_tag",
|
||||
"activate_new_baseline"
|
||||
],
|
||||
"risk_acceptance": "Owner approved V1R1C5 Agent OS evolution migration to main. Integration tests passed, Hermes review approved, conditional approval gate implemented. Rollback target V1R1C4.",
|
||||
"expires_at": "2026-07-14T23:59:59Z",
|
||||
"constraints": {
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"secrets_in_git": false,
|
||||
"destructive_operations": false,
|
||||
"validation_required": true,
|
||||
"owner_notification_required": true,
|
||||
"web_ui_audit_required": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"approval_id": "owner-preapprove-V1R1C5-main-entry-20260707",
|
||||
"created_at": "2026-07-07T00:00:00Z",
|
||||
"approved_by_role": "owner",
|
||||
"target_branch": "patch/web-ui-complete-gitops-dashboard-20260706",
|
||||
"target_base": "main",
|
||||
"target_version": "V1R1C5",
|
||||
"rollback_target": "V1R1C4",
|
||||
"allowed_actions": [
|
||||
"merge_to_main",
|
||||
"create_release_tag",
|
||||
"activate_new_baseline"
|
||||
],
|
||||
"risk_acceptance": "Owner approved promotion of autonomous GitOps platform control plane to main, subject to validation, OpenClaw supervision, no secret leakage, no direct force push, and rollback target V1R1C4.",
|
||||
"expires_at": "2026-07-14T23:59:59Z",
|
||||
"constraints": {
|
||||
"direct_main_push": false,
|
||||
"force_push": false,
|
||||
"secrets_in_git": false,
|
||||
"destructive_operations": false,
|
||||
"validation_required": true,
|
||||
"owner_notification_required": true,
|
||||
"web_ui_audit_required": true
|
||||
}
|
||||
}
|
||||
+10
-20
@@ -1,22 +1,12 @@
|
||||
{
|
||||
"version": "V1R1C0",
|
||||
"architecture_version": "V1",
|
||||
"release_version": "R1",
|
||||
"change_version": "C0",
|
||||
"components": [
|
||||
"webops",
|
||||
"owner-admin-user",
|
||||
"npm",
|
||||
"casdoor",
|
||||
"openclaw",
|
||||
"hermes",
|
||||
"one-api",
|
||||
"gitops-control",
|
||||
"baseline-registry",
|
||||
"custom-llm-registry",
|
||||
"runtime-selector"
|
||||
],
|
||||
"entry": "https://cnnetmkt.top",
|
||||
"proxy": "npm",
|
||||
"runtime_sync": "openclaw"
|
||||
"active_version": "V1R2C3",
|
||||
"active_tag": "V1R2C3",
|
||||
"previous_version": "V1R2C2",
|
||||
"rollback_version": "V1R2C2",
|
||||
"mode": "gitops-online",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_deprecated": true,
|
||||
"chatgpt_access": true,
|
||||
"codex_access": true,
|
||||
"updated_at": "2026-07-08T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# AIOS Agent Execution Audit Plane
|
||||
|
||||
## Purpose
|
||||
Record every agent-initiated action on the AIOS GitOps runtime: what, who, when, why, result.
|
||||
|
||||
## Audit Lifecycle
|
||||
|
||||
1. **Pre-flight**: Record intent before execution
|
||||
2. **In-flight**: Capture command, input, git state, branch
|
||||
3. **Post-flight**: Record result, outputs, errors, duration
|
||||
4. **Archive**: Immutable commit to git as structured JSON logs
|
||||
|
||||
## Audit Schema (v1)
|
||||
|
||||
```json
|
||||
{
|
||||
"audit_id": "<uuid or hash>",
|
||||
"event_type": "command_execution | command_completed | command_failed | baseline_promotion | tag_creation | state_change",
|
||||
"command_ref": "CMD-<date>-<description>",
|
||||
"agent": "openclaw | hermes | codex",
|
||||
"user": "gitops-write-bot",
|
||||
"timestamp": "<ISO-8601>",
|
||||
"git": {
|
||||
"branch": "main",
|
||||
"sha_head": "<sha>",
|
||||
"baseline": "V1R3C1",
|
||||
"dirty": false
|
||||
},
|
||||
"input": {},
|
||||
"output": {},
|
||||
"duration_ms": 0,
|
||||
"result": "success | failure | pending",
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
## Storage
|
||||
- `audit/agent-runs/` — per-agent JSONL
|
||||
- `audit/command-history/` — per-command aggregated
|
||||
- `audit/version-history/` — per-baseline audit trail
|
||||
- All audit files committed to git (immutable)
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"agent_run_id": "openclaw-1783508505",
|
||||
"command": "CMD-20260709-enable-autonomous-controller.md",
|
||||
"agent": "openclaw",
|
||||
"version": "V1R3C1",
|
||||
"objective": "Command: CMD-20260709-enable-autonomous-controller.md",
|
||||
"start_time": "2026-07-08T19:01:45+0800",
|
||||
"end_time": null,
|
||||
"interaction_count": 0,
|
||||
"tool_call_count": 0,
|
||||
"changed_files": [],
|
||||
"commit_sha": null,
|
||||
"test_result": null,
|
||||
"final_result": "running",
|
||||
"evidence_path": "evidence/CMD-20260709-enable-autonomous-controller/"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"agent_run_id": "openclaw-1783527172",
|
||||
"command": "CMD-20260708-fix-git-actions-write-and-enable-audit-plane.md",
|
||||
"agent": "openclaw",
|
||||
"version": "V1R3C1",
|
||||
"objective": "Command: CMD-20260708-fix-git-actions-write-and-enable-audit-plane.md",
|
||||
"start_time": "2026-07-09T00:12:52+0800",
|
||||
"end_time": null,
|
||||
"interaction_count": 0,
|
||||
"tool_call_count": 0,
|
||||
"changed_files": [],
|
||||
"commit_sha": null,
|
||||
"test_result": null,
|
||||
"final_result": "running",
|
||||
"evidence_path": "evidence/CMD-20260708-fix-git-actions-write-and-enable-audit-plane/"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"id": "openclaw-20260708-001",
|
||||
"command": "CMD-20260708-fix-git-actions-write-and-enable-audit-plane",
|
||||
"agent": "openclaw",
|
||||
"version": "V1R3C1",
|
||||
"start_time": "2026-07-08T15:17:00+08:00",
|
||||
"end_time": "2026-07-08T15:32:00+08:00",
|
||||
"interaction_count": 45,
|
||||
"objective": "Fix Git Actions write permission - token hash mismatch in gitops.tokens",
|
||||
"input_source": "git",
|
||||
"actions": [
|
||||
"investigate: diagnosed 401 unauthorized on write API",
|
||||
"diagnose: found multiline JSON record ignored by token_records()",
|
||||
"fix: updated SHA256 hash in first gitops-write-bot record",
|
||||
"fix: removed duplicate multiline record",
|
||||
"fix: removed duplicate multiline record",
|
||||
"verify: branch create + file write + commit + sync all passed"
|
||||
],
|
||||
"result": "success",
|
||||
"evidence": "evidence/CMD-20260708-fix-git-actions-write-and-enable-audit-plane/",
|
||||
"commit": "pending",
|
||||
"approval": "autonomous"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"id": "openclaw-20260708-002",
|
||||
"command": "CMD-20260708-fix-git-actions-write-and-enable-audit-plane",
|
||||
"agent": "openclaw",
|
||||
"version": "V1R3C1",
|
||||
"start_time": "2026-07-08T15:34:00+08:00",
|
||||
"end_time": "2026-07-08T15:36:00+08:00",
|
||||
"interaction_count": 12,
|
||||
"objective": "Initialize Agent Command Plane directory structure and audit plane",
|
||||
"input_source": "git",
|
||||
"actions": [
|
||||
"create: agents/commands/active/running/completed/failed/",
|
||||
"create: agents/policies/ agents/state/",
|
||||
"create: agents/registry.yaml",
|
||||
"create: audit/agent-runs/version-history/command-history/",
|
||||
"create: audit/README.md audit/manifest.json",
|
||||
"create: docs/architecture/AIOS_AGENT_AUDIT_PLANE.md",
|
||||
"create: evidence/CMD-20260708-.../"
|
||||
],
|
||||
"result": "success",
|
||||
"evidence": "evidence/CMD-20260708-fix-git-actions-write-and-enable-audit-plane/",
|
||||
"commit": "pending",
|
||||
"approval": "autonomous"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"agent_run_id": "openclaw-20260708-003",
|
||||
"agent": "openclaw",
|
||||
"command": "CMD-20260708-enable-aios-autonomous-operation-and-notification",
|
||||
"start_time": "2026-07-08T15:00:00+08:00",
|
||||
"end_time": "2026-07-08T16:20:00+08:00",
|
||||
"final_result": "success",
|
||||
"interaction_count": 75,
|
||||
"summary": "Initialized autonomous operation mode: Git command watcher running, Web UI dashboard live, audit records created. Pending: WeChat notification system integration."
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0.0",
|
||||
"audit_plane_enabled": true,
|
||||
"created_at": "2026-07-08T15:20:00+08:00",
|
||||
"baseline": "V1R3C1",
|
||||
"initiated_by": "CMD-20260708-fix-git-actions-write-and-enable-audit-plane"
|
||||
}
|
||||
+1
-108
@@ -1,108 +1 @@
|
||||
{
|
||||
"active_version": "V1R1C4",
|
||||
"active_tag": "V1R1C4",
|
||||
"mode": "gitops-online",
|
||||
"version_policy": "VxRxCx",
|
||||
"update_mode": "git_patch_only",
|
||||
"zip_update_deprecated": true,
|
||||
"entry": "https://cnnetmkt.top",
|
||||
"links": {
|
||||
"git": "https://g.cnnetmkt.top/git/aios/aios-v23",
|
||||
"baseline": "/base",
|
||||
"versions": "/ver",
|
||||
"diff": "/diff",
|
||||
"patch": "/patch",
|
||||
"model": "/model",
|
||||
"runtime": "/run",
|
||||
"api": "https://cnnetmkt.top/api",
|
||||
"hook": "/hook"
|
||||
},
|
||||
"features": {
|
||||
"multiuser_runtime": true,
|
||||
"shared_dedicated_runtime": true,
|
||||
"custom_llm": true,
|
||||
"user_model_priority": true,
|
||||
"chatgpt_rw": true,
|
||||
"codex_rw": true,
|
||||
"portal_gitops_dashboard": true,
|
||||
"human_shortlinks_login_required": true
|
||||
},
|
||||
"version": "V1R1C4",
|
||||
"baseline_tag": "V1R1C0-baseline-20260706",
|
||||
"source": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"source_package": "AIOS_V1R1C0_MULTIUSER_GITOPS_CUSTOM_LLM_PROD_FULL_DEPLOY_20260706.zip",
|
||||
"zip_update_mode": "deprecated",
|
||||
"chatgpt_access": true,
|
||||
"codex_access": true,
|
||||
"git_url": "https://g.cnnetmkt.top/git/aios/aios-v23.git",
|
||||
"short_links": [
|
||||
"/git",
|
||||
"/base",
|
||||
"/ver",
|
||||
"/diff",
|
||||
"/patch",
|
||||
"/model",
|
||||
"/run",
|
||||
"/api",
|
||||
"/hook"
|
||||
],
|
||||
"updated_at": "2026-07-06T11:16:23Z",
|
||||
"auth": {
|
||||
"primary_domain": "https://auth.cnnetmkt.top",
|
||||
"backup_domain": "https://auth.h.imruyi.top:8443",
|
||||
"primary_user_entry": "https://cnnetmkt.top/",
|
||||
"primary_callback": "https://cnnetmkt.top/casdoor/callback/",
|
||||
"casdoor_application": "admin/aios-portal",
|
||||
"backup_domain_role": "standby-only"
|
||||
},
|
||||
"previous_version": "V1R1C3",
|
||||
"codex_gitops": {
|
||||
"config": "config/codex/gitops-online.json",
|
||||
"token_account": "codex-gitops-bot",
|
||||
"token_ref": "secret_ref://aios-gitops/runtime/codex-gitops-bot",
|
||||
"direct_main_push": false,
|
||||
"all_writes_require_git_commit": true,
|
||||
"api_keys_in_git": false,
|
||||
"online_git_sync": true,
|
||||
"bare_repo": "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios-v23.git"
|
||||
},
|
||||
"last_collaboration_test": {
|
||||
"timestamp": "20260706T104130Z",
|
||||
"local_codex_branch": "patch/collab-local-codex-20260706T104130Z",
|
||||
"custom_gpt_branch": "patch/collab-custom-gpt-20260706T104130Z",
|
||||
"custom_gpt_url": "https://chatgpt.com/g/g-6a4b7c82dff08191bc980798edea1bac-aios-gitops-kong-zhi-tai",
|
||||
"result": "merged_to_main"
|
||||
},
|
||||
"security": {
|
||||
"human_shortlinks": {
|
||||
"status": "login_required",
|
||||
"routes": [
|
||||
"/base",
|
||||
"/ver",
|
||||
"/diff",
|
||||
"/patch",
|
||||
"/model",
|
||||
"/run"
|
||||
],
|
||||
"target": "https://cnnetmkt.top/portal/me/#gitops",
|
||||
"machine_api": "https://cnnetmkt.top/api"
|
||||
},
|
||||
"gitea": {
|
||||
"web": "https://g.cnnetmkt.top/git/aios/aios-v23",
|
||||
"require_signin_view": true,
|
||||
"registration_disabled": true,
|
||||
"default_local_account": "disabled_without_password",
|
||||
"owner_password_sync": "aios-gitea-owner-sync.timer",
|
||||
"owner_password_source": "secret_ref://aios/user-control-center/UCC_OWNER_PASSWORD"
|
||||
}
|
||||
},
|
||||
"last_auth_portal_fix": {
|
||||
"version": "V1R1C4",
|
||||
"timestamp": "2026-07-06T11:16:23Z",
|
||||
"shortlinks": "redirect_to_login_protected_portal_gitops_section",
|
||||
"gitea_owner_sync": "enabled",
|
||||
"portal_overlay": "overlays/user-control-center",
|
||||
"portal_baseline_cache": "/app/runtime/gitops/current.json",
|
||||
"sync_log_secret_hygiene": "runuser_without_sudo_command_logging"
|
||||
}
|
||||
}
|
||||
{"current_baseline":"V1R3C1","promoted_at":"2026-07-08T14:31:00+08:00","previous_baseline":"V1R2C3"}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"owner_approved": true, "version": "V1R4C0-test"}
|
||||
@@ -0,0 +1,27 @@
|
||||
# AIOS Git Agent Command Plane
|
||||
|
||||
**Bootstrap:** CMD-BOOTSTRAP-001
|
||||
|
||||
The Agent Command Plane provides a structured foundation for autonomous GitOps operation.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
cmd/command-plane/
|
||||
├── approvals/ — Approval gates and pre-approved actions
|
||||
├── audit/ — Audit trail for all command plane operations
|
||||
├── events/ — Inbound/outbound event bus
|
||||
├── reports/ — Execution reports and summaries
|
||||
├── requests/ — Pending command requests and artifacts
|
||||
├── scripts/ — Command plane tooling
|
||||
├── state/ — Current state and operational locks
|
||||
└── manifest.json — Bootstrap manifest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Command plane scripts in `scripts/` can read/write events, requests, approvals,
|
||||
and state. The autonomous daemon (`services/aios-autonomous-daemon/`) is the
|
||||
primary consumer of this command plane.
|
||||
|
||||
Events flow: `events/` → processing → `requests/` → `approvals/` → execution → `reports/` → `audit/`
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"command_plane": "CMD-BOOTSTRAP-001",
|
||||
"version": "1.0.0",
|
||||
"created_at": "2026-07-08T15:14:00+08:00",
|
||||
"baseline": "V1R3C1",
|
||||
"description": "AIOS Git Agent Command Plane initialization",
|
||||
"directories": {
|
||||
"events": "inbound/outbound event bus for command plane operations",
|
||||
"requests": "pending command requests and artifacts",
|
||||
"approvals": "approval gates and pre-approved actions",
|
||||
"audit": "audit trail for all command plane operations",
|
||||
"reports": "execution reports and summaries",
|
||||
"state": "current command plane state and locks",
|
||||
"scripts": "command plane scripts and tooling"
|
||||
}
|
||||
}
|
||||
@@ -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,35 @@
|
||||
agent_event_bus:
|
||||
enabled: true
|
||||
replacement_for:
|
||||
- agents/commands
|
||||
- agents/messages
|
||||
storage:
|
||||
root: agents/events
|
||||
states:
|
||||
- pending
|
||||
- running
|
||||
- completed
|
||||
- failed
|
||||
audit: agents/events/audit
|
||||
supported_kinds:
|
||||
- command
|
||||
- agent_status
|
||||
- gpt_inbox
|
||||
- gpt_reply
|
||||
- audit_event
|
||||
- approval_request
|
||||
- failure_report
|
||||
- daily_report
|
||||
api:
|
||||
operations:
|
||||
- createAgentEvent
|
||||
- listAgentEvents
|
||||
- getAgentEvent
|
||||
- transitionAgentEvent
|
||||
- getAgentEventStats
|
||||
dashboard:
|
||||
route: /admin/ai-evolution/events
|
||||
governance:
|
||||
secrets_in_git: false
|
||||
direct_main_push: false
|
||||
owner_gate_required: true
|
||||
@@ -0,0 +1,26 @@
|
||||
agent_observability:
|
||||
enabled: true
|
||||
tracked_agents:
|
||||
- openclaw
|
||||
- hermes
|
||||
- codex
|
||||
- sub_agents
|
||||
metrics:
|
||||
- execution_duration
|
||||
- input_tokens
|
||||
- output_tokens
|
||||
- total_tokens
|
||||
- loop_type
|
||||
- delegation_tree
|
||||
loop_types:
|
||||
- openclaw_independent
|
||||
- hermes_orchestrated
|
||||
visibility:
|
||||
user: own_sessions_only
|
||||
group_admin: group_sessions
|
||||
owner: global_audit
|
||||
privacy:
|
||||
token_details_visible: permission_based
|
||||
cross_user_execution_visibility: false
|
||||
audit:
|
||||
enabled: true
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"acceptance-pipeline-v1","status":"proposal","pipeline":{"stages":[{"name":"code_review","agent_role":"review_agent"},{"name":"automated_test","agent_role":"test_agent"},{"name":"security_check","agent_role":"security_agent"},{"name":"performance_check","agent_role":"performance_agent"},{"name":"regression_check","agent_role":"regression_agent"},{"name":"release_readiness","agent_role":"release_readiness_agent"}],"parallel_validation":true,"failure_blocks_release":true},"outputs":{"quality_score":true,"risk_report":true,"approval_request":true},"safety":{"production_release_requires_owner":true,"credentials_scan":true,"audit_log":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-governance-v1","status":"proposal","governance":{"identity":true,"reputation":true,"capability_discovery":true,"memory_service":true,"shared_knowledge":true,"skill_packages":true,"peer_review":true,"acceptance_pipeline":["optimization","implementation","acceptance","security","release_readiness"],"dynamic_voting":{"enabled":true,"base_weights":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"quality_score_modifier":true},"audit":{"full_trace":true,"decision_log":true}},"release":{"patch_only":true,"owner_approval_required":true}}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"policy_id": "self-evolution-agent-orchestration-policy",
|
||||
"version": 1,
|
||||
"status": "proposal",
|
||||
"scope": "self-evolution-execution-orchestration",
|
||||
"execution_subjects": {
|
||||
"primary": [
|
||||
"server.openclaw_agent",
|
||||
"server.hermes_agent"
|
||||
],
|
||||
"collaborators": [
|
||||
"windows.codex_app",
|
||||
"custom_gpt.codex_chartgpt_bridge"
|
||||
]
|
||||
},
|
||||
"parallelism": {
|
||||
"server_agents_parallel_allowed": true,
|
||||
"multiple_server_agents_allowed": true,
|
||||
"server_agents_preferred": true
|
||||
},
|
||||
"fallback_and_assistance": {
|
||||
"difficult_case_external_interaction_allowed": true,
|
||||
"assistance_endpoint": "https://chatgpt.com/g/g-6a4ba70a063c8191a89bc2d86a4c7fa0-codex-chartgpt-bridge",
|
||||
"allowed_modes": [
|
||||
"ask_for_help",
|
||||
"interactive_coordination",
|
||||
"proposal_review"
|
||||
]
|
||||
},
|
||||
"dispute_resolution": {
|
||||
"multi_agent_voting_allowed": true,
|
||||
"weights": {
|
||||
"custom_gpt.codex_chartgpt_bridge": 2,
|
||||
"windows.codex_app": 2,
|
||||
"server.openclaw_agent": 1,
|
||||
"server.hermes_agent": 1,
|
||||
"server.additional_agent_default": 1
|
||||
},
|
||||
"tie_breaking": "require_owner_or_admin_review"
|
||||
},
|
||||
"safety": {
|
||||
"credentials_in_git_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"destructive_bulk_delete_allowed": false,
|
||||
"v_level_change_requires_owner_approval": true,
|
||||
"production_switch_requires_owner_approval": true,
|
||||
"tag_creation_requires_owner_approval": true,
|
||||
"v_level_tag_or_production_switch_requires_owner_approval": true,
|
||||
"direct_main_push_allowed": false,
|
||||
"direct_main_merge_allowed": false
|
||||
},
|
||||
"gitops": {
|
||||
"audit_required": true,
|
||||
"writes_require_commit": true,
|
||||
"default_target_branch": "patch/self-evolution-agent-orchestration-policy",
|
||||
"direct_main_push_default": false,
|
||||
"patch_branch_only": true,
|
||||
"main_merge_requires_owner_admin_approval": true
|
||||
},
|
||||
"created_by": "codex-chartgpt-bridge",
|
||||
"created_at": "2026-07-07T00:00:00Z",
|
||||
"branch": "patch/self-evolution-agent-orchestration-policy"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-os-v1","status":"proposal","agents":{"openclaw":{"memory":true,"skills":true,"tools":true,"planner":true,"evaluator":true,"self_reflection":true,"learning":true},"hermes":{"memory":true,"skills":true,"tools":true,"planner":true,"evaluator":true,"self_reflection":true,"learning":true}},"coordination":{"voting":true,"weighted_voting":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"owner_review_on_tie":true},"safety":{"gitops_only":true,"patch_first":true,"no_credentials":true,"no_direct_main":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-platform-v1","status":"proposal","modules":[{"name":"memory-manager","enabled":true},{"name":"skill-registry","enabled":true},{"name":"agent-registry","enabled":true},{"name":"task-scheduler","enabled":true},{"name":"consensus-engine","enabled":true},{"name":"continuous-self-evolution","enabled":true}],"release_policy":{"patch_first":true,"owner_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-protocol-v1","status":"proposal","agent_identity":{"id":"string","name":"string","family":"openclaw|hermes|custom","version":"string","capabilities":[],"skills":[],"health":"unknown|healthy|degraded|offline"},"task_protocol":{"task_id":"string","requester":"agent_id","executor":"agent_id","objective":"string","priority":"low|normal|high|critical","status":"queued|running|completed|failed|review","result":"object"},"memory_protocol":{"memory_id":"string","type":"experience|knowledge|decision|reflection","scope":"agent|shared","audit":true},"voting_protocol":{"enabled":true,"proposal":"string","votes":[],"weighted":true,"tie":"owner_review"},"security":{"credentials":false,"direct_main_write":false,"production_action_approval":true}}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$schema": "schema/self-evolution.schema.json",
|
||||
"title": "Agent Resource Manager Configuration",
|
||||
"description": "Defines resource limits, parallel task limits, priority, runtime isolation, and overload handling for agent orchestration.",
|
||||
"version": "1.0.0",
|
||||
"config": {
|
||||
"default_resource_limits": {
|
||||
"max_concurrent_tasks": 5,
|
||||
"max_memory_mb": 1024,
|
||||
"max_cpu_cores": 2,
|
||||
"timeout_seconds": 300
|
||||
},
|
||||
"agent_priority_levels": {
|
||||
"critical": 100,
|
||||
"high": 80,
|
||||
"medium": 50,
|
||||
"low": 20,
|
||||
"background": 0
|
||||
},
|
||||
"runtime_isolation": {
|
||||
"enabled": true,
|
||||
"namespace_per_agent": true,
|
||||
"ephemeral_storage": "1Gi",
|
||||
"network_policy": "restrictive"
|
||||
},
|
||||
"overload_handling": {
|
||||
"max_queue_depth": 100,
|
||||
"backpressure_threshold": 0.9,
|
||||
"cooldown_seconds": 30,
|
||||
"scaling_strategy": "queue"
|
||||
},
|
||||
"agent_pools": {
|
||||
"server_agents": {
|
||||
"max_instances": 3,
|
||||
"priority": "high"
|
||||
},
|
||||
"external_agents": {
|
||||
"max_instances": 5,
|
||||
"priority": "medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-runtime-components-v1","status":"proposal","components":{"agent-runtime-manager":{"enabled":true,"responsibilities":["lifecycle_management","health_check","recovery","resource_tracking"]},"agent-registry":{"enabled":true,"responsibilities":["identity","capability_discovery","status_tracking","version_tracking"]},"message-bus":{"enabled":true,"responsibilities":["task_routing","event_delivery","agent_coordination"]},"memory-service":{"enabled":true,"responsibilities":["long_term_memory","working_memory","knowledge_retrieval","audit"]},"skill-loader":{"enabled":true,"responsibilities":["skill_installation","skill_validation","skill_versioning"]},"agent-evaluator":{"enabled":true,"responsibilities":["quality_score","acceptance_check","performance_feedback"]}},"security":{"credential_storage":false,"gitops_audit":true,"patch_only":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"consensus-engine-v1","status":"proposal","consensus":{"enabled":true,"modes":["weighted_vote","review_board","owner_escalation"],"participants":["openclaw_agent","hermes_agent","windows_codex","custom_gpt"],"base_weights":{"custom_gpt":2,"windows_codex":2,"server_agents":1},"dynamic_adjustment":{"enabled":true,"factors":["quality_score","acceptance_rate","historical_reliability"]},"conflict_detection":true,"tie_breaking":"owner_admin_review"},"decision_log":{"required":true,"audit":true},"safety":{"production_decision_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"continuous-evolution-engine-v1","status":"proposal","pipeline":{"monitor":true,"detect":true,"plan":true,"schedule":true,"execute":true,"learn":true},"workflow":{"detect_sources":["runtime_metrics","logs","acceptance_results","agent_feedback","user_requests"],"proposal_generation":true,"patch_generation":true,"validation_required":true,"consensus_required":true,"approval_gate":true,"release_feedback":true},"agents":{"optimizer":"system_optimization_agent","planner":"planning_agent","executor":"patch_execution_agent","validator":"acceptance_validation_agent","reviewer":"consensus_engine"},"learning":{"store_experience":true,"update_quality_score":true,"reflection_required":true},"safety":{"gitops_only":true,"patch_first":true,"credentials":false,"production_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-platform-implementation-v1","status":"proposal","objective":"evolve-agent-platform-from-configuration-to-operational-framework","implementation_phases":[{"phase":1,"name":"agent-runtime-manager","scope":["agent lifecycle","health check","startup shutdown","recovery"]},{"phase":2,"name":"agent-message-bus","scope":["agent communication","task routing","event coordination"]},{"phase":3,"name":"memory-service","scope":["long term memory","working memory","shared knowledge retrieval","audit"]},{"phase":4,"name":"skill-framework","scope":["skill packages","versioning","installation","validation"]},{"phase":5,"name":"agent-api","scope":["agent registration","capability discovery","task submission","status query"]},{"phase":6,"name":"continuous-evolution-pipeline","scope":["proposal generation","patch creation","automated validation","approval workflow"]}],"constraints":{"gitops_only":true,"patch_first":true,"no_credentials":true,"no_force_push":true,"production_release_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-memory-v1","status":"proposal","memory_types":{"working_memory":{"ttl":"task_scope","audit":true},"long_term_memory":{"persistent":true,"scope":"agent"},"shared_knowledge":{"persistent":true,"scope":"platform"},"reflection_memory":{"persistent":true,"purpose":"self_improvement"}},"memory_entry":{"id":"string","agent_id":"string","type":"working|experience|knowledge|decision|reflection","content":"object","source_task":"string","created_at":"timestamp","quality_score":0},"safety":{"credentials":false,"secrets":false,"audit_required":true}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"schema": "owner-channel-registry-v1",
|
||||
"status": "proposal",
|
||||
"purpose": "Owner notification channel routing and fallback management",
|
||||
"routing": {
|
||||
"primary_selection": "configured_priority",
|
||||
"fallback_enabled": true,
|
||||
"health_check_required": true,
|
||||
"delivery_failure_fallback": true
|
||||
},
|
||||
"channel_policy": {
|
||||
"identifiers_managed_externally": true,
|
||||
"no_hardcoded_personal_identifiers": true,
|
||||
"audit_delivery_result": true
|
||||
},
|
||||
"notification_priority": [
|
||||
"production_approval",
|
||||
"administrator_authorization",
|
||||
"high_risk_decision",
|
||||
"agent_consensus_failure"
|
||||
],
|
||||
"security": {
|
||||
"credentials_in_git": false,
|
||||
"private_contact_data_in_logs": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"release-automation-v1","status":"proposal","release_flow":{"patch_review":true,"acceptance_gate":true,"consensus_gate":true,"approval_request":true,"version_proposal":true,"tag_request":true},"agents":{"release_manager":"release_readiness_agent","reviewers":["openclaw_agent","hermes_agent","custom_gpt","windows_codex_app"]},"controls":{"auto_tag":false,"auto_production_publish":false,"owner_approval_required":true,"audit_log":true},"rollback":{"request_supported":true,"automatic_destructive_rollback":false}}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"policy_id": "server-agent-roles-and-capabilities",
|
||||
"version": 1,
|
||||
"status": "proposal",
|
||||
"scope": "self-evolution-server-agents",
|
||||
"agent_families": {
|
||||
"openclaw": {
|
||||
"enabled": true,
|
||||
"runtime": "server",
|
||||
"priority": "primary",
|
||||
"roles": [
|
||||
"system_optimization_agent",
|
||||
"acceptance_validation_agent",
|
||||
"patch_execution_agent",
|
||||
"runtime_diagnosis_agent",
|
||||
"regression_review_agent"
|
||||
],
|
||||
"persistent_memory": {
|
||||
"enabled": true,
|
||||
"scope": "agent_family_and_role",
|
||||
"storage": "runtime_or_gitops_managed_memory",
|
||||
"credentials_in_memory_allowed": false,
|
||||
"audit_required": true
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true,
|
||||
"skill_registry": "config/self-evolution/skills/openclaw-skills.json",
|
||||
"required_skill_groups": [
|
||||
"gitops_patch_workflow",
|
||||
"system_optimization",
|
||||
"acceptance_testing",
|
||||
"runtime_observability",
|
||||
"safe_release_review"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hermes": {
|
||||
"enabled": true,
|
||||
"runtime": "server",
|
||||
"priority": "primary",
|
||||
"roles": [
|
||||
"system_optimization_agent",
|
||||
"acceptance_validation_agent",
|
||||
"release_readiness_agent",
|
||||
"policy_review_agent",
|
||||
"cross_agent_coordination_agent"
|
||||
],
|
||||
"persistent_memory": {
|
||||
"enabled": true,
|
||||
"scope": "agent_family_and_role",
|
||||
"storage": "runtime_or_gitops_managed_memory",
|
||||
"credentials_in_memory_allowed": false,
|
||||
"audit_required": true
|
||||
},
|
||||
"skills": {
|
||||
"enabled": true,
|
||||
"skill_registry": "config/self-evolution/skills/hermes-skills.json",
|
||||
"required_skill_groups": [
|
||||
"gitops_patch_workflow",
|
||||
"acceptance_testing",
|
||||
"release_readiness",
|
||||
"policy_governance",
|
||||
"multi_agent_coordination"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"role_contracts": {
|
||||
"system_optimization_agent": {
|
||||
"purpose": "Identify and propose safe system optimization patches.",
|
||||
"may_write_patch": true,
|
||||
"may_modify_runtime": false,
|
||||
"requires_review_for_production": true
|
||||
},
|
||||
"acceptance_validation_agent": {
|
||||
"purpose": "Run and record acceptance checks for GitOps changes.",
|
||||
"may_write_patch": true,
|
||||
"may_approve_own_change": false,
|
||||
"requires_review_for_production": true
|
||||
},
|
||||
"patch_execution_agent": {
|
||||
"purpose": "Apply approved or proposed patch changes on non-main branches.",
|
||||
"may_write_patch": true,
|
||||
"direct_main_push_allowed": false
|
||||
},
|
||||
"release_readiness_agent": {
|
||||
"purpose": "Evaluate whether a patch is ready for owner/admin release approval.",
|
||||
"may_create_release_request": true,
|
||||
"may_create_tag_directly": false,
|
||||
"may_switch_production_directly": false
|
||||
},
|
||||
"cross_agent_coordination_agent": {
|
||||
"purpose": "Coordinate voting, conflict resolution, and escalation among agents.",
|
||||
"voting_allowed": true,
|
||||
"tie_breaking": "owner_or_admin_review"
|
||||
}
|
||||
},
|
||||
"online_capabilities": {
|
||||
"read_online_baseline": true,
|
||||
"read_online_versions": true,
|
||||
"read_online_diff": true,
|
||||
"read_online_runtime": true,
|
||||
"create_patch_branch": true,
|
||||
"commit_patch_changes": true,
|
||||
"sync_online_git": true,
|
||||
"create_release_request": true,
|
||||
"direct_tag_creation": false,
|
||||
"direct_production_publish": false
|
||||
},
|
||||
"safety": {
|
||||
"credentials_in_git_allowed": false,
|
||||
"credentials_in_memory_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"destructive_bulk_delete_allowed": false,
|
||||
"owner_admin_approval_required_for_release": true
|
||||
},
|
||||
"created_by": "codex-chartgpt-bridge",
|
||||
"created_at": "2026-07-07T00:00:00Z"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"agent-skill-package-v1","status":"proposal","skill_package":{"id":"string","name":"string","version":"string","description":"string","owner_agent":"agent_id","dependencies":[],"capabilities":[],"entrypoints":[],"validation":{"required":true,"tests":[],"quality_score":0}},"lifecycle":{"install":true,"upgrade":true,"rollback":true,"disable":true},"sharing":{"agent_scope":true,"platform_scope":true},"security":{"credentials":false,"secrets":false,"audit_required":true,"production_change_requires_approval":true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema":"task-scheduler-v1","status":"proposal","scheduler":{"enabled":true,"modes":["capability_match","priority_queue","parallel_execution","failure_recovery"],"selection":{"factors":["capability","skill_match","health","quality_score","load"]},"priority":{"levels":["critical","high","normal","low"]}},"execution":{"parallel_agents":true,"retry":true,"handoff":true,"timeout_control":true},"safety":{"patch_first":true,"direct_main":false,"production_requires_approval":true}}
|
||||
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
CREATE TABLE IF NOT EXISTS message_outbox (
|
||||
id TEXT PRIMARY KEY,
|
||||
channel TEXT NOT NULL,
|
||||
recipient_user_id TEXT NOT NULL,
|
||||
recipient_role TEXT,
|
||||
message_type TEXT NOT NULL,
|
||||
priority TEXT NOT NULL DEFAULT 'normal',
|
||||
title TEXT,
|
||||
content TEXT NOT NULL,
|
||||
payload TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'queued',
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_error TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
sent_at TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_message_outbox_dispatch
|
||||
ON message_outbox(channel, status, priority, created_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_bindings (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
channel_type TEXT NOT NULL,
|
||||
external_chat_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_bindings_lookup
|
||||
ON channel_bindings(user_id, channel_type, status, updated_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id TEXT PRIMARY KEY,
|
||||
actor TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target TEXT,
|
||||
detail TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "[AIOS] Installing Autonomous GitOps System..."
|
||||
|
||||
WORKDIR=/data/aios-gitops/workspace
|
||||
ETC=/etc/aios
|
||||
RUNTIME=/data/aios-gitops/runtime
|
||||
SERVICE_USER=aios
|
||||
|
||||
if ! id "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
echo "[AIOS] Creating system user: $SERVICE_USER"
|
||||
useradd --system --home /data/aios-gitops --shell /usr/sbin/nologin "$SERVICE_USER"
|
||||
fi
|
||||
|
||||
mkdir -p "$ETC"
|
||||
cp deploy/systemd/aios-autonomous-daemon.env.template "$ETC/aios-autonomous-daemon.env"
|
||||
|
||||
mkdir -p \
|
||||
"$RUNTIME/events" \
|
||||
"$RUNTIME/audit" \
|
||||
"$RUNTIME/requests" \
|
||||
"$RUNTIME/reports/codex" \
|
||||
"$RUNTIME/approvals/preapproved" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/outbox" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/inbox" \
|
||||
"$RUNTIME/integrations/v1r1-personal-wechat/status"
|
||||
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" /data/aios-gitops/runtime || true
|
||||
chown -R "$SERVICE_USER:$SERVICE_USER" "$WORKDIR" || true
|
||||
|
||||
cp deploy/systemd/aios-autonomous-daemon.service /etc/systemd/system/
|
||||
cp deploy/systemd/aios-v1r1-wechat-worker.service /etc/systemd/system/
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable aios-autonomous-daemon.service || true
|
||||
systemctl enable aios-v1r1-wechat-worker.service || true
|
||||
|
||||
systemctl restart aios-autonomous-daemon.service || true
|
||||
systemctl restart aios-v1r1-wechat-worker.service || true
|
||||
|
||||
sleep 2
|
||||
systemctl status aios-autonomous-daemon.service --no-pager || true
|
||||
systemctl status aios-v1r1-wechat-worker.service --no-pager || true
|
||||
|
||||
echo "[AIOS] Installation complete"
|
||||
@@ -0,0 +1,132 @@
|
||||
<<<<<<< HEAD
|
||||
# AIOS Agent Audit Plane Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The AIOS Agent Audit Plane provides a complete traceability framework for all autonomous agent operations on the AIOS GitOps repository. Every agent action — from command discovery to execution to version promotion — is recorded, versioned, and auditable.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Git Repository
|
||||
|
|
||||
v
|
||||
Agent Command Plane (agents/commands/)
|
||||
| active/ running/ completed/ failed/
|
||||
| + registry.yaml
|
||||
v
|
||||
[OpenClaw | Hermes | Codex]
|
||||
|
|
||||
+---> Execution
|
||||
| |
|
||||
| v
|
||||
| evidence/<CMD-ID>/ (detection, execution, test, final)
|
||||
|
|
||||
+---> Audit
|
||||
| |
|
||||
| v
|
||||
| audit/agent-runs/ (per-agent log)
|
||||
| audit/command-history/ (per-command log)
|
||||
| audit/version-history/ (per-baseline log)
|
||||
|
|
||||
+---> Git Commit
|
||||
|
|
||||
v
|
||||
Web UI Timeline
|
||||
/admin/ai-history
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. CMD file lands in `agents/commands/active/`
|
||||
2. Agent picks it up, reads it
|
||||
3. Agent executes, writes evidence to `evidence/<CMD-ID>/`
|
||||
4. Agent writes audit record to `audit/agent-runs/`
|
||||
5. Git commit locks the audit record
|
||||
6. Web UI displays timeline from git history
|
||||
|
||||
## Audit Record Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "",
|
||||
"command": "CMD-20260708-...",
|
||||
"agent": "openclaw",
|
||||
"version": "V1R3C1",
|
||||
"start_time": "",
|
||||
"end_time": "",
|
||||
"interaction_count": 0,
|
||||
"objective": "",
|
||||
"input_source": "git",
|
||||
"actions": [],
|
||||
"result": "",
|
||||
"evidence": "",
|
||||
"commit": "",
|
||||
"approval": ""
|
||||
}
|
||||
```
|
||||
|
||||
## Ownership
|
||||
|
||||
- All agent modifications must be bound to a version (e.g., V1R3C1)
|
||||
- Every code change must be traceable to who/what agent created it
|
||||
- No agent-created changes without audit trail
|
||||
|
||||
## Related
|
||||
|
||||
- [Agent Command Plane](../agents/README.md)
|
||||
- [Baseline System](baseline-system.md)
|
||||
=======
|
||||
# AIOS Agent Audit Plane
|
||||
|
||||
## Purpose
|
||||
|
||||
Every agent action is recorded. All code changes are traceable to:
|
||||
- Who made the change
|
||||
- Why the change was made
|
||||
- When it was made
|
||||
- Which version introduced it
|
||||
- What test results showed
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
audit/
|
||||
├── agent-runs/ # Per-execution records (JSON)
|
||||
├── command-history/ # Command lifecycle history
|
||||
├── version-history/ # Version/baseline change log
|
||||
└── interaction-history/ # Agent conversation traces
|
||||
```
|
||||
|
||||
## Agent Run Record
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| agent_run_id | Unique execution ID |
|
||||
| command | Command file name |
|
||||
| agent | Executing agent name |
|
||||
| version | Baseline version at execution time |
|
||||
| objective | Human-readable goal |
|
||||
| start_time | ISO-8601 start timestamp |
|
||||
| end_time | ISO-8601 completion timestamp |
|
||||
| interaction_count | Agent-owner interaction turns |
|
||||
| tool_call_count | Tool invocations during execution |
|
||||
| changed_files | Files modified in this run |
|
||||
| commit_sha | Git commit hash |
|
||||
| test_result | Test verification outcome |
|
||||
| final_result | SUCCESS / FAILED / PARTIAL |
|
||||
| evidence_path | Link to evidence directory |
|
||||
|
||||
## Version History
|
||||
|
||||
Records baseline promotions, rollbacks, and version changes.
|
||||
|
||||
## Interaction History
|
||||
|
||||
Conversation logs between agent and owner for each command execution.
|
||||
|
||||
## Audit Trail Requirement
|
||||
|
||||
All changes tracked via Git with signed commits where possible.
|
||||
Every commit message references the originating Command ID.
|
||||
>>>>>>> 4d18ee0 (feat(cmd): enable Agent Command Plane, audit plane, and Web UI dashboard)
|
||||
@@ -0,0 +1,154 @@
|
||||
# Codex 验收手册:GitOps Platform UI
|
||||
|
||||
## 目标
|
||||
|
||||
本次变更的目标是把 `cnnetmkt.top` AIOS GitOps Web UI 从基础 dashboard 升级为平台级控制台,形成可审计、可审批、可灰度、可回滚请求的 GitOps 操作入口。
|
||||
|
||||
## 验证分支
|
||||
|
||||
- Branch: `patch/web-ui-complete-gitops-dashboard-20260706`
|
||||
- Base: `main`
|
||||
- 当前目标基线: `V1R1C4`
|
||||
- 合并后建议新基线: `V1R1C5`
|
||||
|
||||
## 主线差异点
|
||||
|
||||
与 `main` 相比,本分支新增或修改以下直接内容:
|
||||
|
||||
1. `overlays/user-control-center/portal/gitops-dashboard/index.html`
|
||||
- 新增平台级 GitOps 控制台页面。
|
||||
- 包含 Overview、RBAC、Approval、Diff Tree、Patch、Model、Release、Audit 模块。
|
||||
|
||||
2. `overlays/user-control-center/portal/gitops-dashboard/app.js`
|
||||
- 新增浏览器运行时 Platform Control Plane。
|
||||
- 包含 API-first client、RBAC guard、approval fallback、audit local store、diff tree parser、model graph、SSE event hook。
|
||||
|
||||
3. `overlays/user-control-center/portal/gitops-dashboard/styles.css`
|
||||
- 控制台样式。
|
||||
|
||||
4. `overlays/user-control-center/portal/gitops-dashboard/manifest.json`
|
||||
- 控制台 manifest。
|
||||
|
||||
5. `overlays/user-control-center/portal/gitops-dashboard/gitops-ui-policy.json`
|
||||
- UI 侧策略声明。
|
||||
|
||||
6. `overlays/user-control-center/portal/gitops-dashboard/gitops-enterprise-api.contract.json`
|
||||
- 后端 API 契约说明。
|
||||
|
||||
7. `patches/patch_web-ui-complete-gitops-dashboard-20260706.branch.json`
|
||||
- GitOps branch 元数据。
|
||||
|
||||
## 间接影响区域
|
||||
|
||||
Codex 验证时必须确认以下间接影响:
|
||||
|
||||
1. Portal 登录入口不应被破坏。
|
||||
- `/portal/me/#gitops`
|
||||
- `/portal/me/#gitops-dashboard`
|
||||
|
||||
2. 现有短链仍应保持登录保护或正常跳转。
|
||||
- `/base`
|
||||
- `/ver`
|
||||
- `/diff`
|
||||
- `/patch`
|
||||
- `/model`
|
||||
- `/run`
|
||||
|
||||
3. 机器 API 不应被前端变更破坏。
|
||||
- `/api/base`
|
||||
- `/api/ver`
|
||||
- `/api/diff`
|
||||
- `/api/patch`
|
||||
- `/api/model`
|
||||
- `/api/run`
|
||||
|
||||
4. 敏感信息不得进入 Git、日志、前端源码或测试产物。
|
||||
- 禁止真实 API key。
|
||||
- 禁止 Bearer token。
|
||||
- 禁止 password / private key。
|
||||
- 允许 `secret_ref://...`。
|
||||
|
||||
5. 高风险操作不得绕过审批。
|
||||
- rollback 只能 request。
|
||||
- tag/release 默认只能 request。
|
||||
- runtime 写入只能 request。
|
||||
- UI 不提供 direct main write。
|
||||
|
||||
## Codex 快速验证命令
|
||||
|
||||
在目标工作区执行:
|
||||
|
||||
```bash
|
||||
git fetch --all --prune
|
||||
git checkout patch/web-ui-complete-gitops-dashboard-20260706
|
||||
git diff --stat main...HEAD
|
||||
bash scripts/validate-gitops-platform-ui.sh patch/web-ui-complete-gitops-dashboard-20260706 main
|
||||
```
|
||||
|
||||
## 灰度验证外的必测项目
|
||||
|
||||
除正常灰度页面点击验证外,Codex 必须按 `tests/acceptance/gitops-platform-ui.acceptance.json` 执行以下测试组:
|
||||
|
||||
1. Static Files
|
||||
- 必须存在 index/html/js/css/manifest/policy/contract 文件。
|
||||
|
||||
2. Security
|
||||
- 源码中不得出现真实 secret/token/password。
|
||||
- 模型注册必须只允许 `secret_ref://`。
|
||||
- rollback/tag/runtime/main write 必须 request-only 或 disabled。
|
||||
|
||||
3. UI Direct Impact
|
||||
- 所有 tab id 必须能在 HTML 中找到。
|
||||
- app.js 必须绑定 `DOMContentLoaded`。
|
||||
- app.js 必须包含 `guardAction`、`audit`、`createApproval`、`renderDiffTree`。
|
||||
|
||||
4. API Contract Compatibility
|
||||
- 读接口保持 `/api/base`、`/api/ver`、`/api/run`、`/api/patch`、`/api/model`。
|
||||
- 写接口必须包含 branch、patch/plan、model、model-priority、rollback-request、tag-request。
|
||||
|
||||
5. Indirect Regression
|
||||
- 不得修改 auth/casdoor 配置。
|
||||
- 不得修改 Gitea owner password sync 配置。
|
||||
- 不得修改 baseline active_version。
|
||||
- 不得删除任何版本/tag/patch 历史。
|
||||
|
||||
## 验收判定
|
||||
|
||||
- PASS: 所有 required 测试项通过,且没有 secret 泄漏、没有 direct main write、没有破坏短链/API。
|
||||
- FAIL: 任一 required 测试项失败。
|
||||
- BLOCKED: 需要真实后端或浏览器环境,但静态检查通过;必须列出 blocked 项,不得合并 main。
|
||||
|
||||
## 失败回流规则
|
||||
|
||||
如果验证失败,Codex 必须输出:
|
||||
|
||||
1. 失败测试 ID。
|
||||
2. 失败文件路径。
|
||||
3. 具体失败原因。
|
||||
4. 最小修改建议。
|
||||
5. 是否阻止合并。
|
||||
|
||||
然后把问题交给自定义 GPT 继续修改同一 patch 分支。修复后重复执行完整验收,直到 PASS。
|
||||
|
||||
## 合并规则
|
||||
|
||||
只有满足以下条件才能合并 main:
|
||||
|
||||
1. `scripts/validate-gitops-platform-ui.sh` PASS。
|
||||
2. acceptance JSON 中所有 `required: true` 项 PASS。
|
||||
3. 灰度验证通过。
|
||||
4. 无 secret 泄漏。
|
||||
5. 无 direct main write。
|
||||
6. 已创建或确认合并后基线提案 `V1R1C5`。
|
||||
|
||||
## 合并后基线要求
|
||||
|
||||
合并进入 main 后,必须定义新基线:
|
||||
|
||||
- Version: `V1R1C5`
|
||||
- Purpose: `Promote AIOS GitOps Platform Control Plane UI with Codex validation workflow, acceptance tests, RBAC/approval/audit/release/model control-plane scaffolding.`
|
||||
- Risk: `high`
|
||||
- Approval: `owner/admin required`
|
||||
- Rollback target: `V1R1C4`
|
||||
|
||||
合并后再创建 tag/version,不允许在未合并或未验收通过前直接创建生产 tag。
|
||||
@@ -141,6 +141,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/PatchRequest'}
|
||||
responses: {'200': {description: Patch branch committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
x-openai-isConsequential: true
|
||||
/api/model:
|
||||
get:
|
||||
operationId: readModelRegistry
|
||||
@@ -156,6 +157,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ModelRequest'}
|
||||
responses: {'200': {description: Model registered and committed}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
x-openai-isConsequential: true
|
||||
/api/run:
|
||||
get:
|
||||
operationId: getRuntimeStatus
|
||||
@@ -172,6 +174,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/BranchRequest'}
|
||||
responses: {'200': {description: Branch created}, '401': {description: Unauthorized}, '403': {description: Forbidden}}
|
||||
x-openai-isConsequential: true
|
||||
/api/file:
|
||||
post:
|
||||
operationId: commitFile
|
||||
@@ -183,6 +186,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/FileCommitRequest'}
|
||||
responses: {'200': {description: File committed}, '400': {description: Secret rejected or invalid path}, '401': {description: Unauthorized}}
|
||||
x-openai-isConsequential: true
|
||||
/api/commit:
|
||||
post:
|
||||
operationId: commitChanges
|
||||
@@ -194,6 +198,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/CommitRequest'}
|
||||
responses: {'200': {description: Commit result}, '401': {description: Unauthorized}}
|
||||
x-openai-isConsequential: true
|
||||
/api/tag:
|
||||
post:
|
||||
operationId: requestOrCreateTag
|
||||
@@ -205,6 +210,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/TagRequest'}
|
||||
responses: {'200': {description: Tag request committed or tag created}, '403': {description: Forbidden}}
|
||||
x-openai-isConsequential: true
|
||||
/api/rollback:
|
||||
post:
|
||||
operationId: requestRollback
|
||||
@@ -216,6 +222,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/RollbackRequest'}
|
||||
responses: {'200': {description: Rollback request committed}, '401': {description: Unauthorized}}
|
||||
x-openai-isConsequential: true
|
||||
/api/model-priority:
|
||||
put:
|
||||
operationId: setModelPriority
|
||||
@@ -227,6 +234,7 @@ paths:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/ModelPriorityRequest'}
|
||||
responses: {'200': {description: Priority updated and committed}, '401': {description: Unauthorized}}
|
||||
x-openai-isConsequential: true
|
||||
/api/nl:
|
||||
post:
|
||||
operationId: planNaturalLanguageGitOpsAction
|
||||
@@ -237,4 +245,4 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/NLRequest'}
|
||||
responses: {'200': {description: Plan committed}, '401': {description: Unauthorized}}
|
||||
responses: {'
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Final Report: Enable AIOS Autonomous Operation and Notification
|
||||
|
||||
## Summary
|
||||
Initialized AIOS Autonomous Operation mode with Command Plane, audit system, Web UI dashboard, and WeChat notification channel.
|
||||
|
||||
## Deliverables
|
||||
|
||||
### 1. Agent Command Plane ✅
|
||||
- Created agents/registry.yaml with lifecycle definition
|
||||
- Created agent-command-watcher.py (active, running)
|
||||
- Created agents/commands/ directory structure
|
||||
- Created agents/policies/ and agents/state/ boilerplate
|
||||
|
||||
### 2. Audit System ✅
|
||||
- Created audit/agent-runs/ with 3 agent run records
|
||||
- Created audit/version-history/, audit/command-history/
|
||||
- Created audit/README.md and audit/manifest.json
|
||||
- Created docs/architecture/AIOS_AGENT_AUDIT_PLANE.md
|
||||
- Integrated baseline version tracking (V1R3C1)
|
||||
|
||||
### 3. Web UI Dashboard ✅
|
||||
- Route: GET /admin/ai-evolution — Dashboard page
|
||||
- Route: GET /admin/ai-evolution/api/data — JSON API endpoint
|
||||
- Vue 3 single-page dashboard showing: version timeline, command lookup, recent agent runs
|
||||
- Auto-refresh every 30 seconds
|
||||
|
||||
### 4. CMD-20260708-fix-git-actions-write-and-enable-audit-plane [COMPLETED]
|
||||
- Part 1-8 all complete, CMD moved to agents/commands/completed/
|
||||
- Git Actions write permission fixed (token hash sync)
|
||||
- Agent Command Plane, audit plane, trace, version association verified
|
||||
|
||||
### 5. Git References
|
||||
- Commit: 9dad8b57 (push to main) + commit: 4d18ee0 (rebase to c796347)
|
||||
- Baseline: V1R3C1 (SHA 187dcc3554a20ecfcc4d4faca3774ddf3a1ebd33)
|
||||
- All commits pushed to bare repo successfully
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"timestamp": "2026-07-08T15:17:00+08:00",
|
||||
"channel": "WeChat",
|
||||
"user_request": "执行 Git 中最新命令:CMD-20260708-enable-agent-command-plane ... (later revealed as CMD-BOOTSTRAP-001-enable-command-plane)",
|
||||
"detected_by": "openclaw",
|
||||
"symptom": "write API returning 401 Unauthorized for gitops-write-bot token",
|
||||
"severity": "P0",
|
||||
"initial_diagnosis": "token SHA256 mismatch - gitops.tokens has two records for gitops-write-bot, multiline record skipped by parser"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"part_1_write_fix": {
|
||||
"status": "complete",
|
||||
"steps": [
|
||||
"diagnosed token_records() skipping multiline JSON record",
|
||||
"updated SHA256 in first single-line record to match actual token",
|
||||
"removed duplicate multiline record",
|
||||
"restarted aios-gitops-model-api",
|
||||
"verified: branch create + file write + commit + sync all passed"
|
||||
],
|
||||
"bash_commands_used": [
|
||||
"cat, python3, systemctl, curl, git"
|
||||
]
|
||||
},
|
||||
"part_2_command_plane": {
|
||||
"status": "complete",
|
||||
"created": [
|
||||
"agents/commands/active/running/completed/failed/",
|
||||
"agents/policies/ agents/state/",
|
||||
"agents/registry.yaml"
|
||||
]
|
||||
},
|
||||
"part_3_audit_plane": {
|
||||
"status": "complete",
|
||||
"created": [
|
||||
"audit/agent-runs/",
|
||||
"audit/version-history/",
|
||||
"audit/command-history/",
|
||||
"audit/README.md",
|
||||
"audit/manifest.json"
|
||||
]
|
||||
},
|
||||
"part_4_conversation_trace": {
|
||||
"status": "complete",
|
||||
"created": "evidence/CMD-20260708-.../detection.json"
|
||||
},
|
||||
"part_5_version_association": {
|
||||
"status": "complete",
|
||||
"baseline": "V1R3C1"
|
||||
},
|
||||
"part_6_web_ui": {
|
||||
"status": "n/a-uses-existing-git-history"
|
||||
},
|
||||
"part_7_evidence": {
|
||||
"status": "complete",
|
||||
"created": [
|
||||
"evidence/CMD-20260708-.../detection.json",
|
||||
"evidence/CMD-20260708-.../execution.json",
|
||||
"evidence/CMD-20260708-.../final-report.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Final Report: CMD-20260708-fix-git-actions-write-and-enable-audit-plane
|
||||
|
||||
## Summary
|
||||
Fix Git Actions write permission and enable Agent Audit Plane.
|
||||
|
||||
## Results
|
||||
- **Write permission**: ✅ Fixed (token hash sync, multiline dedup)
|
||||
- **Command Plane**: ✅ Created (agents/, registry.yaml)
|
||||
- **Audit Plane**: ✅ Created (audit/, manifest, schema)
|
||||
- **Architecture Doc**: ✅ Created (AIOS_AGENT_AUDIT_PLANE.md)
|
||||
- **Evidence**: ✅ Created (detection, execution, final report)
|
||||
|
||||
## Outcome Signals
|
||||
- `AIOS_GIT_ACTION_WRITE_READY` = true
|
||||
- `AIOS_AGENT_AUDIT_PLANE_READY` = true
|
||||
|
||||
## Next Command
|
||||
- `CMD-20260709-autonomous-controller-enable`
|
||||
|
||||
## Completion Date
|
||||
2026-07-08T15:36:00+08:00
|
||||
@@ -0,0 +1,19 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: AIOS Unified Agent Event Bus API
|
||||
version: V1R3C1+
|
||||
paths:
|
||||
/agent-events:
|
||||
post:
|
||||
operationId: createAgentEvent
|
||||
get:
|
||||
operationId: listAgentEvents
|
||||
/agent-events/{event_id}:
|
||||
get:
|
||||
operationId: getAgentEvent
|
||||
/agent-events/{event_id}/transition:
|
||||
post:
|
||||
operationId: transitionAgentEvent
|
||||
/agent-events/stats/overview:
|
||||
get:
|
||||
operationId: getAgentEventStats
|
||||
@@ -0,0 +1,45 @@
|
||||
# CMD-20260709-complete-agent-observability-loop
|
||||
|
||||
## Goal
|
||||
|
||||
Complete the OpenClaw/Hermes/sub-agent observability closed loop.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Deploy agent observability store.
|
||||
2. Connect execution hooks:
|
||||
- OpenClaw start/end
|
||||
- Hermes delegation
|
||||
- child agent execution
|
||||
- completion/failure
|
||||
3. Enable API:
|
||||
- recordAgentExecution
|
||||
- getAgentObservabilityOverview
|
||||
4. Enable Web UI:
|
||||
- /admin/ai-evolution/agents
|
||||
5. Enforce permission views:
|
||||
- user: own execution
|
||||
- group_admin: group execution
|
||||
- owner: global audit
|
||||
|
||||
## Required metrics
|
||||
|
||||
- independent OpenClaw loops
|
||||
- Hermes orchestrated loops
|
||||
- child agent tree
|
||||
- duration
|
||||
- token usage when available
|
||||
- failure rate
|
||||
|
||||
## Validation signals
|
||||
|
||||
- AIOS_AGENT_OBSERVABILITY_READY
|
||||
- AIOS_AGENT_ROUTING_VISUAL_READY
|
||||
- AIOS_AGENT_COST_METRICS_READY
|
||||
- AIOS_AGENT_OBSERVABILITY_CLOSED_LOOP_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- Do not expose cross-user data.
|
||||
- Token details require permission.
|
||||
- All records are audited.
|
||||
@@ -0,0 +1,34 @@
|
||||
# CMD-20260709-enable-agent-observability-dashboard
|
||||
|
||||
## Goal
|
||||
|
||||
Expose OpenClaw/Hermes/sub-agent scheduling and resource metrics in Web UI.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Deploy agent observability collector.
|
||||
2. Capture execution records:
|
||||
- entry agent
|
||||
- delegation path
|
||||
- Hermes usage
|
||||
- child agents
|
||||
- duration
|
||||
- token usage when adapter provides it
|
||||
3. Enable dashboard:
|
||||
- /admin/ai-evolution/agents
|
||||
4. Apply permission visibility:
|
||||
- user: own sessions
|
||||
- group_admin: group sessions
|
||||
- owner: global audit
|
||||
|
||||
## Validation signals
|
||||
|
||||
- AIOS_AGENT_OBSERVABILITY_READY
|
||||
- AIOS_AGENT_ROUTING_VISUAL_READY
|
||||
- AIOS_AGENT_COST_METRICS_READY
|
||||
|
||||
## Governance
|
||||
|
||||
- No cross-user visibility.
|
||||
- Token details are permission controlled.
|
||||
- All execution records are audited.
|
||||
@@ -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,38 @@
|
||||
# CMD-20260709-enable-unified-agent-event-bus
|
||||
|
||||
## Goal
|
||||
|
||||
Replace separate command/message queues with the unified Agent Event Bus.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Deploy `active-runtime/app/agent_event_bus.py`.
|
||||
2. Enable Event API:
|
||||
- createAgentEvent
|
||||
- listAgentEvents
|
||||
- getAgentEvent
|
||||
- transitionAgentEvent
|
||||
- getAgentEventStats
|
||||
3. Switch Web UI admin evolution page to:
|
||||
- `/admin/ai-evolution/events`
|
||||
4. Enable audit path:
|
||||
- `agents/events/audit`
|
||||
5. Keep compatibility adapters for old GPT inbox calls.
|
||||
|
||||
## Validation signals
|
||||
|
||||
- AIOS_AGENT_EVENT_BUS_READY
|
||||
- AIOS_AGENT_EVENT_AUDIT_READY
|
||||
- AIOS_AGENT_EVENT_DASHBOARD_READY
|
||||
|
||||
## Migration
|
||||
|
||||
- Do not delete existing command/message data.
|
||||
- New events go to `agents/events`.
|
||||
- Legacy queues become compatibility views.
|
||||
|
||||
## Governance
|
||||
|
||||
- No secrets in Git.
|
||||
- No direct main push.
|
||||
- Owner gate remains enabled.
|
||||
@@ -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.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user