Skip to content

Commit 26ae27e

Browse files
ZD Studiosclaude
andcommitted
feat: integrate ruflo + real YouTube/Reddit/HN/web reading
RUFLO (ruvnet/ruflo) — agent meta-harness: swarms, 314+ MCP tools, vector memory, federation, 30+ plugins. The runtime ships on npm, so `aios ruflo` delegates to `npx ruflo@latest` and the vendored tree is trimmed 92MB -> 10MB (skills + docs only, 136 plugin skills preserved). New: `aios ruflo plugins` catalogues them, `aios ruflo mcp-register` registers its MCP server with Claude Code, the ruflo-orchestration skill, and ruvnet/ruflo added to supervised updates. WEB READING (aios_web.py) — the agents can now actually read a link instead of guessing from the URL slug: youtube(url) full transcript (flat / timestamped / segments), title, channel, duration, views, description, chapters, available languages reddit(t) a thread + comment tree, a subreddit listing, or a search hackernews() story search, or a story with its comment thread fetch(url) any page reduced to readable text (auto-delegates YT/Reddit URLs) search(q) DuckDuckGo, no key, sponsored slots filtered Exposed as POST /api/web, `aios web <op>`, chat commands /yt /reddit /hn /web, and the web-research skill; the Brain's prompt now tells it to use these and never claim it cannot open a link. Every call is audited. Both sources fought back, and the fixes are the substance here: - YouTube's timedtext endpoint now returns HTTP 200 with an EMPTY body — verified across five InnerTube clients and a full cookie session. Pure scraping is dead. yt-dlp is the only thing that keeps up, so it's the primary path (metadata + captions) with the stdlib scrape as fallback, and `aios setup` installs it. When it's absent you still get metadata plus a note naming the exact fix. - Reddit 403s its JSON API from this network regardless of User-Agent (tested four, plus old.reddit, api.reddit and a redlib mirror). RSS still works, so that's the fallback: responses carry source="rss" and scores are null — the skill tells agents not to report vote counts they didn't get. Added a request throttle, 429 backoff, and caching of the JSON-blocked state (retrying it burned two requests per call and pushed us into rate limits before RSS even ran). Verified live: pulled the real transcript of the first YouTube video ever ("Me at the zoo", 6 segments + 3 chapters), a real r/programming thread with 8 comments via RSS, HN search, DuckDuckGo results, and all of it through the hub endpoint and the /yt + /reddit chat commands. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a5d54a7 commit 26ae27e

787 files changed

Lines changed: 139188 additions & 6 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.

README.md

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# 🧠 The AI OS
44

5-
### Eight open-source AI projects. One operating system. One Control Room.
5+
### Nine open-source AI projects. One operating system. One Control Room.
66

77
<p align="center">
88
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-8B5CF6?style=for-the-badge" alt="MIT License"></a>
@@ -23,6 +23,7 @@
2323
<img src="https://img.shields.io/badge/+_Fabric-255_patterns-0EA5E9?style=flat-square" alt="Fabric">
2424
<img src="https://img.shields.io/badge/+_Caveman-say_less-A16207?style=flat-square" alt="Caveman">
2525
<img src="https://img.shields.io/badge/+_Ponytail-build_less-DB2777?style=flat-square" alt="Ponytail">
26+
<img src="https://img.shields.io/badge/+_Ruflo-314_MCP_tools-7C3AED?style=flat-square" alt="Ruflo">
2627
</p>
2728

2829
**[Install](#-install-one-line)** · **[Control Room](#-the-control-room)** · **[Architecture](#️-architecture)** · **[Commands](#️-command-reference)** · **[Full docs](README.aios.md)** · **[Website](https://zdstudios.github.io/AIOS/)**
@@ -31,7 +32,7 @@
3132

3233
---
3334

34-
**The AI OS** takes eight independent open-source AI projects and makes them work as **one auto-configured
35+
**The AI OS** takes nine independent open-source AI projects and makes them work as **one auto-configured
3536
system** you drive from a single command — **`aios`** — and a single web **Control Room** where you can
3637
**talk to everything and have the agents talk to each other**.
3738

@@ -41,7 +42,7 @@ together, mounts your shared skills, and adds **OpenUI** generative-UI context t
4142

4243
> You can't fuse three runtimes and multiple package managers into a single file — that would just break
4344
> everything. So "one file" here means **one control surface** (`aios`) + **one dashboard** (the Hub) over
44-
> eight real, unmodified projects. It installs, configures, wires, runs, tests, and debugs all of them.
45+
> nine real, unmodified projects. It installs, configures, wires, runs, tests, and debugs all of them.
4546
4647
<div align="center">
4748

@@ -240,6 +241,54 @@ That is deliberately the "ambient authority" posture that made **OpenClaw's CVE-
240241

241242
**Active Memory:** a memory sub-agent runs on **every** turn — recall is a free FTS5 query, fact-extraction is one small async call — so the agents actually learn your workflow over time instead of only reading memory at session start (`memory.active`).
242243

244+
## 🌐 It can actually read the web
245+
246+
Paste a link and the agents read it — no API keys, no "I can't open URLs".
247+
248+
| Source | What you get |
249+
|---|---|
250+
| **YouTube** | Full **transcript** (flat, timestamped, or segment objects), title, channel, duration, views, description, **chapters**, available languages |
251+
| **Reddit** | A thread with its **comment tree**, a subreddit listing, or a search |
252+
| **Hacker News** | Story search, or a story with its full comment thread |
253+
| **Any page** | Reduced to clean readable text |
254+
| **Search** | DuckDuckGo, no key |
255+
256+
```bash
257+
aios web youtube "https://youtu.be/VIDEO_ID" # transcript in your terminal
258+
aios web reddit "r/LocalLLaMA" # or a thread URL, or a search
259+
aios web fetch "https://example.com/article"
260+
```
261+
262+
In chat: `/yt <url>`, `/reddit <target>`, `/hn <query>`, `/web <url>` — or just paste a
263+
link and ask; the Brain calls `POST /api/web` itself. The bundled **`web-research`**
264+
skill teaches every agent the same API.
265+
266+
Two honest caveats, both surfaced in the response rather than hidden:
267+
- **YouTube transcripts need `yt-dlp`** (`aios setup` installs it). YouTube now serves
268+
its caption endpoint an empty body unless the request carries a proof-of-origin
269+
token, so scraping it directly no longer works — yt-dlp is what keeps up with that.
270+
Without it you still get metadata, and the `note` tells you what to install.
271+
- **Reddit blocks its JSON API from many networks**, so AIOS falls back to RSS
272+
automatically. When that happens the response says `"source":"rss"` and **scores are
273+
`null`** — the agents are told not to report vote counts they didn't actually get.
274+
Reddit also rate-limits hard, so calls are throttled and retried.
275+
276+
## 🤖 Ruflo — heavyweight orchestration on demand
277+
278+
[ruvnet/ruflo](https://github.com/ruvnet/ruflo) is an agent meta-harness: multi-agent
279+
**swarms**, **314+ MCP tools**, vector memory, federation, and 30+ plugins. AIOS ships
280+
its skills and plugin catalogue and drives the runtime from npm, so it's always current.
281+
282+
```bash
283+
aios ruflo plugins # the 30+ plugins and their skill counts
284+
aios ruflo init wizard # set it up inside a project
285+
aios ruflo mcp-register # register its MCP server with Claude Code
286+
aios ruflo <anything> # passed through to `npx ruflo@latest`
287+
```
288+
289+
AIOS is the always-on system (services, channels, dashboard, Task Brain); ruflo is a
290+
per-project harness you invoke for a heavy job. They compose.
291+
243292
## 🎨 Agents can redesign the dashboard (safely)
244293

245294
Ask in chat — *"make the accent blue and the text bigger"*, *"hide Automations"*, *"put a

aios.py

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def resolve_root(*candidates) -> Path | None:
269269
"fabric": resolve_root("fabric-main/fabric-main", "fabric-main", "fabric"),
270270
"caveman": resolve_root("caveman-main/caveman-main", "caveman-main", "caveman"),
271271
"ponytail": resolve_root("ponytail-main/ponytail-main", "ponytail-main", "ponytail"),
272+
"ruflo": resolve_root("ruflo-main/ruflo-main", "ruflo-main", "ruflo"),
272273
}
273274

274275

@@ -810,6 +811,7 @@ def cmd_setup(args):
810811
if cfg_get(cfg, "skills.mount", True):
811812
mount_aios_skills(cfg)
812813
mount_agent_modes(cfg) # caveman + ponytail skills → every agent
814+
mount_ruflo(cfg) # ruflo orchestration skill → every agent
813815

814816
# 7) wire openclaw-os plugin (best-effort; needs openclaw runnable)
815817
if cfg_get(cfg, "services.openclaw_os.enabled", True) and not args.skip_wire:
@@ -955,8 +957,31 @@ def _ensure_tools(args):
955957
err(f"{t} still not found — open a new shell or install manually: {cmdline}")
956958

957959

960+
def install_web_deps():
961+
"""yt-dlp powers YouTube transcripts. YouTube now serves its caption endpoint an
962+
empty body without a proof-of-origin token, and yt-dlp is what keeps up with
963+
that — without it AIOS can read video metadata but not what was said."""
964+
try:
965+
import yt_dlp # noqa: F401,PLC0415
966+
ok("yt-dlp present (YouTube transcripts enabled)")
967+
return True
968+
except Exception:
969+
pass
970+
print(f"{C.GRY}$ {sys.executable} -m pip install yt-dlp{C.R}")
971+
r = subprocess.run([sys.executable, "-m", "pip", "install", "--quiet",
972+
"--disable-pip-version-check", "yt-dlp"],
973+
capture_output=True, text=True)
974+
if r.returncode == 0:
975+
ok("installed yt-dlp (YouTube transcripts enabled)")
976+
return True
977+
warn("could not install yt-dlp — YouTube metadata will work, transcripts won't. "
978+
"Install it manually: pip install yt-dlp")
979+
return False
980+
981+
958982
def _install_all(cfg):
959983
head("Installing dependencies")
984+
install_web_deps()
960985
# opencode: --ignore-scripts skips native postinstalls (tree-sitter-powershell,
961986
# node-pty) that need VS Build Tools on Windows and are unused by `serve`.
962987
jobs = [
@@ -1822,6 +1847,108 @@ def cmd_wire(args):
18221847
wire_full_control(load_config())
18231848

18241849

1850+
def mount_ruflo(cfg: dict):
1851+
"""Mount ruflo's entry-point skill so agents know the `npx ruflo` surface and its
1852+
~314 MCP tools. Its 136 plugin skills stay on disk and are listed by
1853+
`aios ruflo plugins` — copying them all into every agent would drown context."""
1854+
src = PROJECTS.get("ruflo") or (ROOT / "ruflo-main")
1855+
sk = src / "SKILL.md"
1856+
if not sk.exists():
1857+
return
1858+
head("Mounting ruflo skill")
1859+
for t in (Path.home() / ".openclaw" / "skills" / "ruflo",
1860+
Path.home() / ".hermes" / "skills" / "ruflo",
1861+
STATE / "skills" / "ruflo"):
1862+
try:
1863+
t.mkdir(parents=True, exist_ok=True)
1864+
shutil.copy(sk, t / "SKILL.md")
1865+
except Exception as e:
1866+
warn(f"could not mount ruflo → {t}: {e}")
1867+
n = len(list((src / "plugins").glob("*/skills/*/SKILL.md"))) if (src / "plugins").exists() else 0
1868+
ok(f"ruflo skill mounted ({n} plugin skills available via `aios ruflo plugins`)")
1869+
1870+
1871+
def cmd_ruflo(args):
1872+
"""Run ruflo (agent meta-harness). Delegates to the npm package, which is the
1873+
supported delivery channel — the vendored copy here is only skills + docs."""
1874+
src = PROJECTS.get("ruflo") or (ROOT / "ruflo-main")
1875+
sub = list(getattr(args, "args", []) or [])
1876+
1877+
if sub and sub[0] == "plugins":
1878+
pdir = src / "plugins"
1879+
if not pdir.exists():
1880+
die("ruflo plugins not found — re-run `aios setup`")
1881+
head("Ruflo plugins")
1882+
for d in sorted(p for p in pdir.iterdir() if p.is_dir()):
1883+
skills = list(d.glob("skills/*/SKILL.md"))
1884+
say(f" {C.CYN}{d.name:<26}{C.R}{C.GRY}{len(skills)} skill(s){C.R}")
1885+
say(f"\n{C.GRY}Enable one inside a project:{C.R} npx ruflo@latest plugin add <name>")
1886+
return
1887+
1888+
if sub and sub[0] == "mcp-register":
1889+
claude = find_tool("claude")
1890+
if not claude:
1891+
die("the `claude` CLI is not installed — see `aios claude-login`")
1892+
cmd = [claude, "mcp", "add", "ruflo", "--", "npx", "ruflo@latest", "mcp", "start"]
1893+
say(f"{C.GRY}$ {' '.join(cmd)}{C.R}")
1894+
rc = subprocess.run(cmd, cwd=str(ROOT)).returncode
1895+
(ok if rc == 0 else warn)("ruflo MCP registered with Claude Code" if rc == 0
1896+
else f"registration exited {rc}")
1897+
return
1898+
1899+
npx = find_tool("npx")
1900+
if not npx:
1901+
die("npx not found — install Node >= 20 (see `aios doctor`)")
1902+
cmd = [npx, "ruflo@latest"] + (sub or ["--help"])
1903+
say(f"{C.GRY}$ {' '.join(cmd)}{C.R}")
1904+
try:
1905+
sys.exit(subprocess.run(cmd, cwd=str(ROOT), env=child_env({})).returncode)
1906+
except KeyboardInterrupt:
1907+
pass
1908+
1909+
1910+
def cmd_web(args):
1911+
"""Read YouTube / Reddit / Hacker News / any page from the terminal."""
1912+
sys.path.insert(0, str(ROOT))
1913+
import aios_web as W
1914+
op, arg = args.op, " ".join(args.arg or [])
1915+
if not arg:
1916+
die(f"usage: aios web {op} <url-or-query>")
1917+
fn = {"youtube": W.youtube, "yt": W.youtube, "reddit": W.reddit,
1918+
"hn": W.hackernews, "search": W.search, "fetch": W.fetch}[op]
1919+
r = fn(arg)
1920+
if not r.get("ok"):
1921+
warn(r.get("error", "failed"))
1922+
sys.exit(1)
1923+
if op in ("youtube", "yt"):
1924+
head(r["title"])
1925+
say(f" {C.GRY}{r['channel']} · {r['duration']} · {r['views']:,} views{C.R}")
1926+
if r.get("chapters"):
1927+
say(f"\n{C.B}Chapters{C.R}")
1928+
for c in r["chapters"][:12]:
1929+
say(f" {C.GRY}{c['ts']:>7}{C.R} {c['title']}")
1930+
if r.get("transcript"):
1931+
auto = " (auto-generated)" if r.get("transcript_auto") else ""
1932+
say(f"\n{C.B}Transcript{C.R} {C.GRY}{r['words']} words{auto}{C.R}\n")
1933+
say(r["transcript_timestamped"] if getattr(args, "timestamps", False)
1934+
else r["transcript_text"])
1935+
else:
1936+
warn(r.get("note", "no transcript"))
1937+
elif op == "reddit" and r.get("kind") == "post":
1938+
head(r["title"])
1939+
if r.get("selftext"):
1940+
say(r["selftext"][:2000])
1941+
say(f"\n{C.B}{r.get('comments_returned', 0)} comments{C.R}")
1942+
for c in r.get("comments", [])[:25]:
1943+
sc = f"[{c['score']}] " if c.get("score") is not None else ""
1944+
body = c["body"][:600].replace("\n", "\n ")
1945+
say(f"\n {C.CYN}{c['author']}{C.R} {C.GRY}{sc}{C.R}\n {body}")
1946+
if r.get("note"):
1947+
say(f"\n{C.GRY}{r['note']}{C.R}")
1948+
else:
1949+
print(json.dumps(r, indent=2)[:12000])
1950+
1951+
18251952
def cmd_updates(args):
18261953
"""Supervised updates: what moved upstream, what the agents think, apply/rollback."""
18271954
sys.path.insert(0, str(ROOT))
@@ -2056,11 +2183,11 @@ def _print_urls(cfg):
20562183
# unreadable once you have 25 subcommands.
20572184
HELP_GROUPS = [
20582185
("Get started", ["setup", "start", "stop", "restart", "status", "url"]),
2059-
("Everyday", ["logs", "doctor", "exec", "channels", "profile", "token"]),
2060-
("Agents & updates", ["updates", "update", "claude-login", "attach", "migrate", "wire"]),
2186+
("Everyday", ["logs", "doctor", "exec", "web", "channels", "profile", "token"]),
2187+
("Agents & updates", ["updates", "update", "claude-login", "ruflo", "attach", "migrate", "wire"]),
20612188
("Advanced", ["test", "debug", "autostart", "install-cli", "bootstrap"]),
20622189
]
2063-
TAGLINE = "Eight open-source AI projects. One operating system."
2190+
TAGLINE = "Nine open-source AI projects. One operating system."
20642191

20652192

20662193
class AiosParser(argparse.ArgumentParser):
@@ -2195,6 +2322,17 @@ def build_parser():
21952322
s.add_argument("--rollback", metavar="PROJECT", help="restore a project's previous version")
21962323
s.set_defaults(func=cmd_updates)
21972324

2325+
s = sub.add_parser("web", help="read a YouTube video, Reddit thread, HN or any page")
2326+
s.add_argument("op", choices=["youtube", "yt", "reddit", "hn", "search", "fetch"])
2327+
s.add_argument("arg", nargs=argparse.REMAINDER)
2328+
s.add_argument("--timestamps", action="store_true", help="keep [mm:ss] on transcripts")
2329+
s.set_defaults(func=cmd_web)
2330+
2331+
s = sub.add_parser("ruflo", help="ruflo meta-harness: 314+ MCP tools, swarms, plugins")
2332+
s.add_argument("args", nargs=argparse.REMAINDER,
2333+
help="passed to `npx ruflo` — or: plugins | mcp-register")
2334+
s.set_defaults(func=cmd_ruflo)
2335+
21982336
s = sub.add_parser("token", help="print the hub token (needed for non-loopback access)")
21992337
s.add_argument("--rotate", action="store_true", help="generate a fresh token")
22002338
s.set_defaults(func=cmd_token)

aios_hub.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import aios_sec as sec # noqa: E402 the gate in front of full-control mode
3535
import aios_tools as tools # noqa: E402 shell + channel/skill catalogs
3636
import aios_updates as updates # noqa: E402 agent-supervised dependency updates
37+
import aios_web as web # noqa: E402 YouTube / Reddit / HN / page readers
3738

3839
PORT = int(os.environ.get("AIOS_HUB_PORT", "8787"))
3940
ROOT = Path(os.environ.get("AIOS_ROOT", Path(__file__).resolve().parent))
@@ -463,6 +464,15 @@ def brain_system_prompt(message: str) -> str:
463464
"Ops: set (vars/css/scale/density) · nav (hidden/order) · panel (add/del/clear, "
464465
"slot top|chat|sidebar, self-contained HTML styled with var(--accent) etc.) · "
465466
"reset (what: all|vars|css|panels|nav). Full reference: the `dashboard-designer` skill.")
467+
sysp += (
468+
"\n\nREADING THE WEB: you can read YouTube videos (full transcripts), Reddit "
469+
"threads, Hacker News and any web page through the hub — use these instead of "
470+
"writing your own scraper, and never claim you cannot open a link:\n"
471+
f" RUN: curl -s -X POST http://127.0.0.1:{PORT}/api/web -H 'Content-Type: application/json' "
472+
"-d '{\"op\":\"youtube\",\"url\":\"<link>\"}'\n"
473+
"ops: youtube (transcript_timestamped + title/channel/chapters) · reddit (post+comments, "
474+
"r/sub, or a search phrase) · hn · search · fetch (any URL -> readable text). "
475+
"Add \"|python -c ...\" or pipe through jq to trim big payloads before reading them.")
466476
sysp += active_recall(message) # Active Memory: relevant context, every turn
467477
sysp += modes_prompt_suffix() # caveman / ponytail overlays when toggled on
468478
return sysp
@@ -1478,6 +1488,26 @@ def do_POST(self):
14781488
f"**{st[hit]['level']}** — {meta['blurb']}. `/{hit} off` to stop.")
14791489
self._send(200, {"target": target, "reply": reply, "ran": []})
14801490
return
1491+
# Inline web reads: /yt <url> · /reddit <target> · /web <url>
1492+
wm = re.match(r"^/(yt|youtube|reddit|web|hn)\s+(.+)$", message.strip(), re.I)
1493+
if wm:
1494+
kind, arg = wm.group(1).lower(), wm.group(2).strip()
1495+
fn = {"yt": web.youtube, "youtube": web.youtube, "reddit": web.reddit,
1496+
"hn": web.hackernews}.get(kind, web.fetch)
1497+
r = fn(arg)
1498+
if not r.get("ok"):
1499+
md = f"⚠️ {r.get('error', 'failed')}"
1500+
elif kind in ("yt", "youtube"):
1501+
md = (f"### {r['title']}\n**{r['channel']}** · {r['duration']} · "
1502+
f"{r['views']:,} views\n\n" +
1503+
(f"**Transcript** ({r.get('words', 0)} words"
1504+
f"{', auto-generated' if r.get('transcript_auto') else ''}):\n\n"
1505+
f"{r.get('transcript_timestamped', '')[:6000]}"
1506+
if r.get("transcript") else f"_{r.get('note', 'No transcript.')}_"))
1507+
else:
1508+
md = "```json\n" + json.dumps(r, indent=1)[:6000] + "\n```"
1509+
self._send(200, {"target": "web", "reply": md, "ran": []})
1510+
return
14811511
# Inline fabric: /p <pattern> <text> or /pattern <name> <text>
14821512
if cmd.startswith(("/p ", "/pattern ")):
14831513
rest = message.split(None, 1)[1] if len(message.split()) > 1 else ""
@@ -1596,6 +1626,30 @@ def do_POST(self):
15961626
st = set_mode(payload.get("mode", ""), payload.get("enabled", False),
15971627
payload.get("level", "full"))
15981628
self._send(200, {"state": st})
1629+
elif self.path == "/api/web":
1630+
# Real reads of YouTube / Reddit / HN / any page. One op per source so
1631+
# an agent never has to hand-roll scraping.
1632+
op = (payload.get("op") or "fetch").lower()
1633+
q = payload.get("url") or payload.get("query") or payload.get("target") or ""
1634+
try:
1635+
if op in ("youtube", "yt"):
1636+
r = web.youtube(q, want_transcript=payload.get("transcript", True),
1637+
lang=payload.get("lang", "en"))
1638+
elif op == "reddit":
1639+
r = web.reddit(q, limit=int(payload.get("limit", 60)),
1640+
sort=payload.get("sort", "top"))
1641+
elif op in ("hn", "hackernews"):
1642+
r = web.hackernews(q, story_id=payload.get("story_id", ""),
1643+
limit=int(payload.get("limit", 20)))
1644+
elif op == "search":
1645+
r = web.search(q, limit=int(payload.get("limit", 10)))
1646+
else:
1647+
r = web.fetch(q, max_chars=int(payload.get("max_chars", 20000)))
1648+
except Exception as e:
1649+
r = {"ok": False, "error": f"{op} failed: {e}"}
1650+
brain.audit(payload.get("by", "agent"), "web." + op,
1651+
f"{q[:120]} -> ok={r.get('ok')}")
1652+
self._send(200, r)
15991653
elif self.path == "/api/dashboard":
16001654
self._send(200, dashboard_update(payload))
16011655
elif self.path == "/api/updates":

aios_updates.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"fabric": {"repo": "danielmiessler/fabric", "dir": "fabric-main", "tier": "content"},
5353
"caveman": {"repo": "JuliusBrussee/caveman", "dir": "caveman-main", "tier": "content"},
5454
"ponytail": {"repo": "DietrichGebert/ponytail", "dir": "ponytail-main", "tier": "content"},
55+
"ruflo": {"repo": "ruvnet/ruflo", "dir": "ruflo-main", "tier": "content"},
5556
}
5657

5758
# What each project is used for — given to the agent so its risk call is grounded
@@ -66,6 +67,8 @@
6667
"fabric": "AIOS reads data/patterns/*/system.md only (255 prompt files). No code from it executes.",
6768
"caveman": "AIOS reads skills/caveman/SKILL.md as a system-prompt overlay and mounts its skills.",
6869
"ponytail": "AIOS reads skills/ponytail/SKILL.md as a system-prompt overlay and mounts its skills.",
70+
"ruflo": ("AIOS mounts its SKILL.md and lists plugins/*/skills. The runtime comes from "
71+
"npm (`npx ruflo@latest`), so upstream code changes here are docs/skills only."),
6972
}
7073

7174

0 commit comments

Comments
 (0)