Compare commits

..
Author SHA1 Message Date
aios 1e5d5c1bdd fix(actions): add GPT Actions compatibility responses 2026-07-08 00:38:39 +08:00
aios d4dcc54044 alias test via write bot 2026-07-07 22:43:14 +08:00
46 changed files with 346 additions and 1961 deletions
-77
View File
@@ -1,77 +0,0 @@
"""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"]
-125
View File
@@ -1,125 +0,0 @@
"""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"]
@@ -1,50 +0,0 @@
"""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"]
+84 -58
View File
@@ -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'},
@@ -155,10 +160,38 @@ def patches():
class H(BaseHTTPRequestHandler):
server_version='AIOSChatGPTGitOps/V1R1C0'
def sendj(self, data, code=200):
b=json.dumps(data, ensure_ascii=False, indent=2).encode(); self.send_response(code); self.send_header('Content-Type','application/json; charset=utf-8'); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
def sendt(self, text, ctype='text/plain; charset=utf-8', code=200):
b=text.encode(); self.send_response(code); self.send_header('Content-Type',ctype); self.send_header('Content-Length',str(len(b))); self.end_headers(); self.wfile.write(b)
def log_request(self, format=''):
import sys
rid = getattr(self, '_request_id', uid('req'))
method = self.command
path = urllib.parse.urlparse(self.path).path
code = self.responses and list(self.responses.keys())[0] if hasattr(self, 'responses') and self.responses else 0
try:
act = self.actor()
a = ('auth_present=true' if act['ok'] else 'auth_present=false')
except:
a = 'auth_present=error'
print(f'[{now()}] {rid} {method} {path} -> {self._resp_code if hasattr(self,"_resp_code") else "?"} {a} pid={os.getpid()}', file=sys.stderr, flush=True)
def compat_headers(self):
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, POST, PUT, HEAD, OPTIONS')
self.send_header('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-AIOS-Token')
self.send_header('Cache-Control', 'no-store')
def send_empty(self, code=204, ctype='application/json; charset=utf-8'):
self._resp_code = code
self.send_response(code)
self.send_header('Content-Type', ctype)
self.compat_headers()
self.send_header('Content-Length', '0')
self.end_headers()
def sendj(self, data, code=200, head=False):
self._resp_code = code
b=json.dumps(data, ensure_ascii=False, indent=2).encode(); self.send_response(code); self.send_header('Content-Type','application/json; charset=utf-8'); self.compat_headers(); self.send_header('Content-Length',str(len(b))); self.end_headers()
if not head: self.wfile.write(b)
def sendt(self, text, ctype='text/plain; charset=utf-8', code=200, head=False):
self._resp_code = code
b=text.encode(); self.send_response(code); self.send_header('Content-Type',ctype); self.compat_headers(); self.send_header('Content-Length',str(len(b))); self.end_headers()
if not head: self.wfile.write(b)
def body(self):
n=int(self.headers.get('Content-Length','0') or '0'); raw=self.rfile.read(n).decode() if n else '{}'
try: return json.loads(raw or '{}')
@@ -173,6 +206,18 @@ class H(BaseHTTPRequestHandler):
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
return a
def do_OPTIONS(self):
return self.send_empty(204)
def do_HEAD(self):
raw_path = urllib.parse.urlparse(self.path).path
path = raw_path.rstrip('/') if raw_path != '/' else '/'
if path in ('/api/openapi.yaml','/api/openapi-chatgpt-actions.yaml','/api/schema'):
if OPENAPI.exists():
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8', 200, head=True)
return self.send_empty(404)
known={'/health','/api/health','/api','/api/status','/api/base','/api/baseline','/base','/api/ver','/api/versions','/ver','/api/diff','/diff','/api/patch','/api/patches','/api/branches','/patch','/api/model','/model','/api/run','/api/runtime-status','/run','/api/action-monitor','/action-monitor'}
if path in known: return self.send_empty(200)
return self.send_empty(404)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path; q=urllib.parse.parse_qs(u.query)
try:
@@ -180,19 +225,25 @@ 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})
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,49 +251,48 @@ 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
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'branch','hint':'This operation creates a new Git branch. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
b=checkout(data.get('branch'), data.get('base','main'), True); meta={'type':'branch','branch':b,'base':data.get('base','main'),'created_by':a['account'],'created_at':now(),'direct_main_push':False,'owner_approval_required':True}
r=write_json(f'patches/{safe(b).replace("/","_")}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
def file(self,data):
a=self.need('file:write');
if not a: return
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'file','hint':'This operation writes a file to the workspace and creates a Git commit. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
def commit(self,data):
a=self.need('commit:create');
if not a: return
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'commit','hint':'This operation commits workspace changes to a Git branch. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
b=checkout(data.get('branch') or sh(['rev-parse','--abbrev-ref','HEAD'])['stdout'], data.get('base','main'), False); paths=data.get('paths') or ['.']; paths=[paths] if isinstance(paths,str) else paths; c=commit(paths, data.get('message') or 'chore(chatgpt): commit changes'); audit(a['account'],'commit',b,{'paths':paths,'commit':c}); self.sendj({'status':c['status'],'branch':b,'commit':c})
def patch(self,data):
a=self.need('patch:create');
if not a: return
if data.get('confirm') != True:
self.sendj({'status':'confirm_required','operation':'patch','hint':'This operation creates a patch branch and commits metadata. Confirm to proceed.','x-openai-isConsequential':True}, 428)
return
name=data.get('patch_name') or data.get('name') or uid('patch'); b=checkout(data.get('branch') or f'patch/{safe(name)}', data.get('base','main'), True); meta={'id':uid('patch'),'patch_name':name,'branch':b,'risk':data.get('risk','medium'),'description':data.get('description',''),'created_by':a['account'],'created_at':now()}
r=write_json(f'patches/{safe(name)}.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create patch {name}'); audit(a['account'],'patch',name,meta); self.sendj({'status':'patch_branch_created','patch':meta,'commit':c})
def tag(self,data):
a=self.need('tag:create');
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)})
def rollback(self,data):
a=self.need('rollback:request');
if not a: return
@@ -260,10 +310,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');
@@ -276,39 +332,9 @@ class H(BaseHTTPRequestHandler):
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})
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,
'x-openai-isConsequential': True,
'high_risk_operations': [
'POST /api/tag',
'POST /api/rollback',
'POST /api/model',
'PUT /api/model-priority'
],
'low_risk_operations': [
'POST /api/branch',
'POST /api/file',
'POST /api/commit',
'POST /api/patch',
'POST /api/nl'
]
}
}
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}}
def main():
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
print('HIGH-RISK WRITE OPERATIONS require user confirmation via x-openai-isConsequential: true')
print('LOW-RISK WRITE OPERATIONS also annotated with x-openai-isConsequential: true')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
-2
View File
@@ -1,2 +0,0 @@
# Agent Acknowledgement
Initialized by CMD-20260708-fix-git-actions-write-and-enable-audit-plane
@@ -1,36 +0,0 @@
# 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
@@ -1,35 +0,0 @@
# 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
@@ -1,36 +0,0 @@
# 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
@@ -1,36 +0,0 @@
# 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
@@ -1,35 +0,0 @@
# 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
-2
View File
@@ -1,2 +0,0 @@
# AIOS Agent Policies
Policies for AIOS Git Agent execution.
-67
View File
@@ -1,67 +0,0 @@
<<<<<<< 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)
@@ -1 +0,0 @@
{"owner_approved": true, "version": "V1R4C0-test"}
-170
View File
@@ -1,170 +0,0 @@
#!/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()
-240
View File
@@ -1,240 +0,0 @@
#!/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()
-328
View File
@@ -1,328 +0,0 @@
#!/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()
-2
View File
@@ -1,2 +0,0 @@
# AIOS Agent State
Agent state and lock files for AIOS Git Agent execution.
-15
View File
@@ -1,15 +0,0 @@
{
"processed": {
"CMD-20260708-fix-git-actions-write-and-enable-audit-plane.md": {
"hash": "9442e78e3deb35e03227016b2265d34feb4640f8efdcb07baffe06d52ef947c8",
"status": "discovered",
"discovered_at": "2026-07-08T19:01:32+0800"
},
"CMD-20260709-enable-autonomous-controller.md": {
"hash": "2825811706389130b2f531a2d3814b800b1181598f871b496e5a05fa13adbbec",
"status": "running",
"discovered_at": "2026-07-08T19:01:45+0800"
}
},
"last_scan": "2026-07-08T21:06:45+0800"
}
+20 -10
View File
@@ -1,12 +1,22 @@
{
"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"
"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"
}
-41
View File
@@ -1,41 +0,0 @@
# 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)
-16
View File
@@ -1,16 +0,0 @@
{
"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/"
}
@@ -1,23 +0,0 @@
{
"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"
}
@@ -1,24 +0,0 @@
{
"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"
}
@@ -1,10 +0,0 @@
{
"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."
}
-7
View File
@@ -1,7 +0,0 @@
{
"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"
}
+109 -1
View File
@@ -1 +1,109 @@
{"current_baseline":"V1R3C1","promoted_at":"2026-07-08T14:31:00+08:00","previous_baseline":"V1R2C3"}
{
"active_version": "V1R2C2",
"active_tag": "V1R2C2",
"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,
"autonomous_recovery_loop": true
},
"version": "V1R2C2",
"baseline_tag": "V1R2C2-auto-recovery-baseline",
"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-07T19:45:00Z",
"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": "V1R2C1",
"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"
}
}
@@ -1 +0,0 @@
{"owner_approved": true, "version": "V1R4C0-test"}
-27
View File
@@ -1,27 +0,0 @@
# 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/`
-16
View File
@@ -1,16 +0,0 @@
{
"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"
}
}
-35
View File
@@ -1,35 +0,0 @@
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
-132
View File
@@ -1,132 +0,0 @@
<<<<<<< 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)
+45 -12
View File
@@ -13,6 +13,32 @@ components:
scheme: bearer
bearerFormat: opaque-token
schemas:
BaselineResponse:
type: object
additionalProperties: true
properties:
active_version: {type: string}
active_tag: {type: string}
mode: {type: string}
git: {type: string}
VersionsResponse:
type: object
additionalProperties: true
properties:
tags:
type: array
items: {type: string}
versions_dir:
type: array
items: {type: string}
head: {type: string}
RuntimeStatusResponse:
type: object
additionalProperties: true
properties:
service: {type: string}
status: {type: string}
workspace_branch: {type: string}
BranchRequest:
type: object
required: [branch]
@@ -108,12 +134,22 @@ paths:
get:
operationId: getBaseline
summary: Read current AIOS baseline
responses: {'200': {description: OK}}
responses:
'200':
description: OK
content:
application/json:
schema: {$ref: '#/components/schemas/BaselineResponse'}
/api/ver:
get:
operationId: listVersions
summary: List GitOps versions
responses: {'200': {description: OK}}
responses:
'200':
description: OK
content:
application/json:
schema: {$ref: '#/components/schemas/VersionsResponse'}
/api/diff:
get:
operationId: getDiff
@@ -141,7 +177,6 @@ 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
@@ -157,12 +192,16 @@ 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
summary: Read runtime status
responses: {'200': {description: OK}}
responses:
'200':
description: OK
content:
application/json:
schema: {$ref: '#/components/schemas/RuntimeStatusResponse'}
/api/branch:
post:
operationId: createBranch
@@ -174,7 +213,6 @@ 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
@@ -186,7 +224,6 @@ 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
@@ -198,7 +235,6 @@ 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
@@ -210,7 +246,6 @@ 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
@@ -222,7 +257,6 @@ 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
@@ -234,7 +268,6 @@ 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
@@ -245,4 +278,4 @@ paths:
content:
application/json:
schema: {$ref: '#/components/schemas/NLRequest'}
responses: {'
responses: {'200': {description: Plan committed}, '401': {description: Unauthorized}}
@@ -1,35 +0,0 @@
# 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
@@ -1,9 +0,0 @@
{
"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"
}
@@ -1,52 +0,0 @@
{
"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"
]
}
}
@@ -1,21 +0,0 @@
# 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
@@ -1,19 +0,0 @@
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
@@ -1,38 +0,0 @@
# 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.
@@ -1,9 +0,0 @@
{
"type": "branch",
"branch": "patch/agent-event-bus-unified",
"base": "main",
"created_by": "chatgpt-gitops-bot",
"created_at": "2026-07-08T08:36:38Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -0,0 +1,9 @@
{
"type": "branch",
"branch": "test/alias-write-verify",
"base": "main",
"created_by": "gitops-write-bot",
"created_at": "2026-07-07T14:43:14Z",
"direct_main_push": false,
"owner_approval_required": true
}
@@ -0,0 +1,73 @@
# V1R2C3 GPT Actions ClientResponseError Compatibility Fix Report
## Status
READY_FOR_OWNER_RETEST
## Scope
- Branch: `patch/V1R2C3-gpt-actions-clientresponse-compat`
- Service: `aios-gitops-model-api.service`
- Runtime entrypoint: `active-runtime/app/aios_chatgpt_actions_api.py`
- Schema: `docs/gitops/openapi-chatgpt-actions.yaml`
- Affected operations: `getBaseline`, `getRuntimeStatus`, `listVersions`
## Finding
The GPT Actions plugin was exposed in the GPT session, but read operations
returned `ClientResponseError`. Public `GET` probes returned `200`, while
`HEAD` and `OPTIONS` returned `501` for the schema and core read routes.
The OpenAPI document also described the core read responses without explicit
`application/json` response schemas.
## Changes
- Added compatibility response headers to the ChatGPT Actions adapter:
`Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`,
`Access-Control-Allow-Headers`, and `Cache-Control`.
- Added `HEAD` handling for schema and read endpoints.
- Added `OPTIONS` handling for schema and read endpoints.
- Added explicit OpenAPI response schemas for:
- `BaselineResponse`
- `VersionsResponse`
- `RuntimeStatusResponse`
- Preserved the existing runtime request logging changes already present in
the live workspace before this fix.
## Verification
- `python3 -m py_compile active-runtime/app/aios_chatgpt_actions_api.py`: PASS
- OpenAPI YAML parse with PyYAML: PASS
- Service restart: PASS
- Public `GET /api/openapi-chatgpt-actions.yaml`: `200`
- Public `HEAD /api/openapi-chatgpt-actions.yaml`: `200`
- Public `OPTIONS /api/openapi-chatgpt-actions.yaml`: `204`
- Public `GET /api/base`: `200`
- Public `HEAD /api/base`: `200`
- Public `OPTIONS /api/base`: `204`
- Public `GET /api/run`: `200`
- Public `HEAD /api/run`: `200`
- Public `OPTIONS /api/run`: `204`
- Public `GET /api/ver`: `200`
- Public `HEAD /api/ver`: `200`
- Public `OPTIONS /api/ver`: `204`
## Owner Retest
Re-import or refresh the GPT Builder Actions schema from:
`https://cnnetmkt.top/api/openapi-chatgpt-actions.yaml`
Then retry in a fresh GPT preview/session:
1. `getRuntimeStatus`
2. `getBaseline`
3. `listVersions`
## Security Notes
- No API key, token, password, bearer value, cookie, or secret value was added.
- No force push was used.
- No tag was created.
- No production switch was executed.
- Direct main writes remain forbidden.
+3 -41
View File
@@ -19,49 +19,11 @@ class ConsensusEngine:
"windows_codex": 2,
"server_agent": 1,
}
# New in V1R1C5: quality score, historical reliability, domain expertise
self.quality_score_factors = {
"code_quality": 0.4,
"test_coverage": 0.3,
"response_time": 0.3,
}
self.historical_reliability_factors = {
"recent_acceptance_rate": 0.5,
"longevity_score": 0.3,
"error_rate": 0.2,
}
self.domain_expertise_factors = {
"specialization_match": 0.5,
"experience_years": 0.3,
"certifications": 0.2,
}
def calculate_quality_score(self, agent_id: str, agent_type: str) -> float:
"""Calculate quality score for an agent based on its performance metrics."""
# Placeholder implementation; in production, fetch metrics from telemetry
return 0.85 # Default quality score
def calculate_historical_reliability(self, agent_id: str, agent_type: str) -> float:
"""Calculate historical reliability score for an agent."""
# Placeholder implementation; in production, fetch from store
return 0.90 # Default reliability score
def calculate_domain_expertise(self, agent_id: str, agent_type: str) -> float:
"""Calculate domain expertise score for an agent."""
# Placeholder implementation; in production, fetch from agent registry
return 0.75 # Default expertise score
def calculate(self, votes: List[dict], use_dynamic_weights: bool = False) -> dict:
def calculate(self, votes: List[dict]) -> dict:
result = {"approve": 0, "reject": 0}
for vote in votes:
base_weight = self.base_weights.get(vote.get("agent_type"), 1)
if use_dynamic_weights:
quality = self.calculate_quality_score(vote.get("agent_id", ""), vote.get("agent_type", ""))
reliability = self.calculate_historical_reliability(vote.get("agent_id", ""), vote.get("agent_type", ""))
expertise = self.calculate_domain_expertise(vote.get("agent_id", ""), vote.get("agent_type", ""))
weight = base_weight * quality * reliability * expertise
else:
weight = base_weight
weight = vote.get("weight", self.base_weights.get(vote.get("agent_type"), 1))
result[vote.get("decision", "reject")] += weight
return result
@@ -70,7 +32,7 @@ class ConsensusEngine:
Returns one of: "approve", "reject", "review", "conditional_approval".
"""
result = self.calculate(votes, use_dynamic_weights=True)
result = self.calculate(votes)
if result["approve"] > result["reject"]:
# Check if any participant voted with conditions
conditional_votes = [
+3 -24
View File
@@ -1,16 +1,7 @@
"""Agent OS message bus runtime."""
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum
class DeliveryStatus(Enum):
PENDING = "pending"
DELIVERED = "delivered"
FAILED = "failed"
RETRYING = "retrying"
EXPIRED = "expired"
from dataclasses import dataclass
from typing import List
@dataclass
@@ -19,11 +10,6 @@ class AgentMessage:
receiver: str
event: str
payload: dict
# V1R1C5: New fields for message tracking and delivery
task_id: str = ""
correlation_id: str = ""
retry_state: int = 0
delivery_status: DeliveryStatus = DeliveryStatus.PENDING
class MessageBus:
@@ -35,14 +21,7 @@ class MessageBus:
def broadcast(self, sender: str, event: str, payload: dict, receivers: List[str]):
for receiver in receivers:
self.send(AgentMessage(
sender=sender,
receiver=receiver,
event=event,
payload=payload,
task_id=payload.get("task_id", "") if isinstance(payload, dict) else "",
correlation_id=payload.get("correlation_id", "") if isinstance(payload, dict) else "",
))
self.send(AgentMessage(sender, receiver, event, payload))
def consume(self):
messages = list(self.queue)
-1
View File
@@ -1 +0,0 @@
CMD-20260708 git actions write permission verified OK
-22
View File
@@ -1,22 +0,0 @@
from active_runtime.app.agent_event_bus import AgentEventBus
from active_runtime.app.agent_event_dashboard import AgentEventDashboard
def test_unified_event_flow():
bus = AgentEventBus(root="/tmp/aios-agent-events-test")
event = bus.publish(
kind="gpt_inbox",
source="openclaw",
target="custom-gpt",
summary="execution report",
payload={"status": "done"},
)
assert event["status"] == "pending"
assert len(bus.list_events(kind="gpt_inbox")) == 1
done = bus.transition(event["id"], "completed", "custom-gpt")
assert done["status"] == "completed"
dashboard = AgentEventDashboard(bus).overview()
assert dashboard["audit"]["enabled"] is True
-20
View File
@@ -1,20 +0,0 @@
{
"version": "V1R2C3",
"tag": "V1R2C3",
"previous_version": "V1R2C2",
"rollback_version": "V1R2C2",
"release_reason": "GPT Actions ClientResponseError compatibility recovery",
"validated_actions": [
"getRuntimeStatus",
"getBaseline",
"listVersions",
"listPatches",
"getDiff",
"createBranch",
"createPatch",
"commitFile"
],
"secret_plaintext_written": false,
"created_at": "2026-07-08T00:00:00Z",
"author": "openclaw"
}