Skip to content

Commit f807be6

Browse files
ZD Studiosclaude
andcommitted
feat: ponytail agent mode + agent-supervised dependency updates
PONYTAIL (DietrichGebert/ponytail): a system-prompt overlay that forces the laziest solution that works (~54% less code, YAGNI). Same shape as caveman, so both are now generalised into one composable AGENT_MODES registry read from each project's own SKILL.md — caveman trims what the agent SAYS, ponytail trims what it BUILDS, and they stack. Replaces the caveman-specific endpoint with GET/POST /api/modes, generic /<mode> [level|off] chat commands, per-mode cycle buttons in the composer, and mount_agent_modes() pushing all 13 mode skills into opencode/hermes/openclaw. SUPERVISED UPDATES (aios_updates.py): AIOS now watches each bundled project's upstream and, when one moves ahead, an AGENT reviews the actual commits and changed files before anything is touched, returning SAFE / RISKY / BLOCK with reasoning grounded in how AIOS consumes that project (INTEGRATION_NOTES). Risk tiers gate autonomy: content-tier (fabric patterns, caveman/ponytail skills - nothing executes) may auto-apply on SAFE; service-tier (openclaw, hermes, opencode, CrewAI - real dependency trees) always waits for you. Applying backs up first, reinstalls deps via the detected package manager, health-checks, and rolls back automatically on failure. claude-code-api has no discoverable upstream so it is explicitly pinned rather than guessed at. Fail-safe by construction: a malformed or unreachable agent verdict parses to RISKY, never SAFE — verified by killing the LLM and watching it refuse to apply. New: aios updates [--check|--apply P|--rollback P], GET/POST /api/updates, an Updates dashboard view with verdicts + commit/file evidence and Apply/Skip/Roll back, config updates.supervised/check_interval/auto_apply_safe. Verified against live GitHub: all 7 upstreams resolve, baselines recorded, behind-detection returns real commits+files (fabric 21 ahead, 13 files), verdict parser passes 5/5 including fail-safe, opencode->brain fallback works, and a real review through claude-code returned BLOCK on a planted breaking change, naming the config-key rename, the Node 22 requirement and the matrix-crypto major bump. Also cut GitHub API calls per repo (cached default branch) after hitting the anonymous 60/hr limit during testing; it now degrades to 'unreachable' with a GITHUB_TOKEN hint instead of a false verdict. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c45200e commit f807be6

162 files changed

Lines changed: 14256 additions & 1028 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

aios.config.example.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,17 @@ skills:
7171
# skills/learned/<name>/SKILL.md, which every agent then mounts
7272

7373
updates:
74-
check_on_start: true # on `aios start`, check the repo for updates and notify
74+
check_on_start: true # on `aios start`, check the AIOS repo for updates and notify
7575
auto_update: true # on `aios start`, auto git-pull + reinstall if the repo changed
76+
# Supervised dependency updates: AIOS watches each bundled project's upstream
77+
# (openclaw, hermes, opencode, CrewAI, fabric, caveman, ponytail), and when one
78+
# moves ahead an AGENT reviews the commits + changed files and returns
79+
# SAFE / RISKY / BLOCK before anything is touched.
80+
supervised: true
81+
check_interval: 21600 # seconds between upstream scans (6h)
82+
auto_apply_safe: true # auto-apply ONLY content-tier projects (prompts/skills —
83+
# nothing executes) when an agent says SAFE. Service-tier
84+
# projects with dependency trees always wait for you.
7685

7786
watchdog:
7887
enabled: true # if an agent stops responding, auto-restart it

aios.py

Lines changed: 107 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ def resolve_root(*candidates) -> Path | None:
267267
# caveman = a conciseness system-prompt overlay.
268268
"fabric": resolve_root("fabric-main/fabric-main", "fabric-main", "fabric"),
269269
"caveman": resolve_root("caveman-main/caveman-main", "caveman-main", "caveman"),
270+
"ponytail": resolve_root("ponytail-main/ponytail-main", "ponytail-main", "ponytail"),
270271
}
271272

272273

@@ -474,6 +475,10 @@ def service_specs(cfg: dict) -> dict:
474475
# watchdog: auto-restart downed agents, then have an agent diagnose failures
475476
"AIOS_WATCHDOG": "1" if cfg_get(cfg, "watchdog.enabled", True) else "0",
476477
"AIOS_WATCHDOG_INTERVAL": str(cfg_get(cfg, "watchdog.interval", 45)),
478+
# supervised updates: agents review upstream changes before they land
479+
"AIOS_SUPERVISED_UPDATES": "1" if cfg_get(cfg, "updates.supervised", True) else "0",
480+
"AIOS_UPDATE_INTERVAL": str(cfg_get(cfg, "updates.check_interval", 21600)),
481+
"AIOS_AUTO_APPLY": "1" if cfg_get(cfg, "updates.auto_apply_safe", True) else "0",
477482
},
478483
}
479484
return specs
@@ -803,7 +808,7 @@ def cmd_setup(args):
803808
# 6c) mount the bundled AI OS skills (skill-maker, mcp-maker, …) into the agents
804809
if cfg_get(cfg, "skills.mount", True):
805810
mount_aios_skills(cfg)
806-
mount_caveman(cfg) # caveman conciseness skill → every agent
811+
mount_agent_modes(cfg) # caveman + ponytail skills → every agent
807812

808813
# 7) wire openclaw-os plugin (best-effort; needs openclaw runnable)
809814
if cfg_get(cfg, "services.openclaw_os.enabled", True) and not args.skip_wire:
@@ -1168,28 +1173,34 @@ def mount_aios_skills(cfg: dict):
11681173
ok(f"mounted {len(names)} skills: {', '.join(names)}")
11691174

11701175

1171-
def mount_caveman(cfg: dict):
1172-
"""Mount JuliusBrussee/caveman's skills into every agent, so terser 'caveman
1173-
mode' is available not just to the hub Brain but to opencode/hermes/openclaw too."""
1174-
src = PROJECTS.get("caveman") or (ROOT / "caveman-main")
1175-
skdir = src / "skills"
1176-
if not skdir.exists():
1176+
def mount_agent_modes(cfg: dict):
1177+
"""Mount the behavioural-mode skills (caveman = say less, ponytail = build less)
1178+
into every agent, so the modes apply to opencode/hermes/openclaw too — not just
1179+
the hub Brain, which gets them as a system-prompt overlay."""
1180+
names, skdirs = [], []
1181+
for proj in ("caveman", "ponytail"):
1182+
src = PROJECTS.get(proj) or (ROOT / f"{proj}-main")
1183+
skdir = src / "skills"
1184+
if skdir.exists():
1185+
skdirs.append(skdir)
1186+
if not skdirs:
11771187
return
1178-
head("Mounting Caveman skills")
1179-
names = [p.name for p in skdir.iterdir() if p.is_dir() and (p / "SKILL.md").exists()]
1188+
head("Mounting agent-mode skills (caveman · ponytail)")
11801189
targets = [Path.home() / ".openclaw" / "skills", Path.home() / ".hermes" / "skills",
11811190
STATE / "skills"]
1182-
for t in targets:
1183-
for name in names:
1184-
try:
1185-
dst = t / name
1186-
if dst.exists():
1187-
shutil.rmtree(dst)
1188-
dst.parent.mkdir(parents=True, exist_ok=True)
1189-
shutil.copytree(skdir / name, dst)
1190-
except Exception as e:
1191-
warn(f"could not mount caveman/{name}{t}: {e}")
1192-
ok(f"mounted {len(names)} caveman skills: {', '.join(names)}")
1191+
for skdir in skdirs:
1192+
for sk in sorted(p for p in skdir.iterdir() if p.is_dir() and (p / "SKILL.md").exists()):
1193+
names.append(sk.name)
1194+
for t in targets:
1195+
try:
1196+
dst = t / sk.name
1197+
if dst.exists():
1198+
shutil.rmtree(dst)
1199+
dst.parent.mkdir(parents=True, exist_ok=True)
1200+
shutil.copytree(sk, dst)
1201+
except Exception as e:
1202+
warn(f"could not mount {sk.name}{t}: {e}")
1203+
ok(f"mounted {len(names)} mode skills: {', '.join(names)}")
11931204

11941205

11951206
def mount_openui(cfg: dict):
@@ -1810,6 +1821,76 @@ def cmd_wire(args):
18101821
wire_full_control(load_config())
18111822

18121823

1824+
def cmd_updates(args):
1825+
"""Supervised updates: what moved upstream, what the agents think, apply/rollback."""
1826+
sys.path.insert(0, str(ROOT))
1827+
import aios_updates as U
1828+
cfg = load_config()
1829+
1830+
if getattr(args, "rollback", None):
1831+
r = U.rollback(args.rollback)
1832+
(ok if r.get("ok") else warn)(str(r))
1833+
return
1834+
1835+
if getattr(args, "apply", None):
1836+
name = args.apply
1837+
hurl = cfg_get(cfg, f"health.{name}", "")
1838+
head(f"Applying upstream update: {name}")
1839+
r = U.apply(name, health_url=hurl, log=lambda m: say(f"{C.GRY}{m}{C.R}"))
1840+
if r.get("ok"):
1841+
ok(f"{name} updated → {r['sha']} (backup: {r['backup']})")
1842+
elif r.get("rolled_back"):
1843+
warn(f"{name}: verification failed, rolled back. " + "; ".join(r.get("problems", [])))
1844+
else:
1845+
warn(f"{name}: {r.get('error')}")
1846+
return
1847+
1848+
head("Supervised updates — checking upstreams")
1849+
ups = U.check_all()
1850+
behind = [u for u in ups if u.get("behind")]
1851+
for u in ups:
1852+
st = u.get("status", "?")
1853+
mark = {"behind": f"{C.YEL}{C.R}", "current": f"{C.GRN}{C.R}",
1854+
"baseline": f"{C.CYN}·{C.R}", "pinned": f"{C.GRY}·{C.R}"}.get(st, f"{C.GRY}·{C.R}")
1855+
extra = (f"{u.get('ahead_by', '?')} commits behind" if u.get("behind")
1856+
else (u.get("note") or st))
1857+
say(f" {mark} {u['name']:<12} {C.GRY}{extra}{C.R}")
1858+
1859+
if not behind:
1860+
say(f"\n{C.GRN}Everything is current.{C.R}")
1861+
return
1862+
1863+
if getattr(args, "check", False):
1864+
say(f"\n{C.GRY}Run `aios updates` without --check to have the agents review these.{C.R}")
1865+
return
1866+
1867+
# Ask an agent about each pending update, through the running hub if it's up.
1868+
hub = f"http://127.0.0.1:{cfg_get(cfg, 'services.hub.port', 8787)}"
1869+
tok = load_env(ROOT / ".env").get("AIOS_HUB_TOKEN", "")
1870+
1871+
def ask(target, message, system):
1872+
body = json.dumps({"target": target, "message": f"{system}\n\n{message}"}).encode()
1873+
rq = urllib.request.Request(hub + "/api/chat", data=body, method="POST",
1874+
headers={"Content-Type": "application/json",
1875+
"Authorization": "Bearer " + tok})
1876+
with urllib.request.urlopen(rq, timeout=300) as r:
1877+
return json.loads(r.read()).get("reply", "")
1878+
1879+
head("Agent review")
1880+
for u in behind:
1881+
try:
1882+
v = U.review(u, ask)
1883+
except Exception as e:
1884+
warn(f"{u['name']}: could not reach the hub for review ({e}). Is `aios start hub` up?")
1885+
continue
1886+
colour = {"SAFE": C.GRN, "RISKY": C.YEL, "BLOCK": C.RED}.get(v["verdict"], C.GRY)
1887+
say(f"\n {C.B}{u['name']}{C.R} {colour}{v['verdict']}{C.R}")
1888+
say(f" {C.GRY}{v['why']}{C.R}")
1889+
if v.get("watch"):
1890+
say(f" {C.GRY}watch: {v['watch']}{C.R}")
1891+
say(f" {C.GRY}apply with:{C.R} aios updates --apply {u['name']}")
1892+
1893+
18131894
def cmd_token(args):
18141895
"""Print (or rotate) the hub token."""
18151896
if getattr(args, "rotate", False):
@@ -2038,6 +2119,12 @@ def build_parser():
20382119
sub.add_parser("url", help="print dashboard URLs").set_defaults(func=cmd_url)
20392120
sub.add_parser("wire", help="(re)install the openclaw-os plugin + re-apply full control").set_defaults(func=cmd_wire)
20402121

2122+
s = sub.add_parser("updates", help="supervised updates: agents review upstream changes first")
2123+
s.add_argument("--check", action="store_true", help="report only, skip the agent review")
2124+
s.add_argument("--apply", metavar="PROJECT", help="apply one project's update (verify + auto-rollback)")
2125+
s.add_argument("--rollback", metavar="PROJECT", help="restore a project's previous version")
2126+
s.set_defaults(func=cmd_updates)
2127+
20412128
s = sub.add_parser("token", help="print the hub token (needed for non-loopback access)")
20422129
s.add_argument("--rotate", action="store_true", help="generate a fresh token")
20432130
s.set_defaults(func=cmd_token)

0 commit comments

Comments
 (0)