Skip to content

Commit bef3e5a

Browse files
ZD Studiosclaude
andcommitted
feat: 10 unique multi-agent features (auto-router, arena, council, pipelines, memory, prompts, voice, palette, usage, export)
Features only a multi-agent hub can offer: 1. Auto-router (/api/route_auto): classifies the request and picks the best agent automatically. 2. Agent Arena (/api/arena): same prompt to 2 agents side-by-side to compare. 3. Council (/api/council): N agents + a synthesized consensus with agreement/disagreement. 4. Pipelines (/api/pipeline): chain agents, each step's output feeds the next. 5. Shared memory (/api/memory): facts injected into every agent's context (route() brain). 6. Prompt library (/api/prompts) + /name slash-command expansion in the composer. 7. Voice in/out: Web Speech STT mic + TTS 'speak replies' toggle. 8. Command palette (Cmd/Ctrl+K): jump anywhere / ask any agent. 9. Usage meter (/api/usage): per-agent request counts + 'free via subscription' (claudecode). 10. Export conversation to Markdown; conversations persist (pinned/today/earlier). New chat modes as chips (Auto/Arena/Council/Pipeline); Memory & prompts view; palette overlay. Verified on Windows: memory/prompts/arena/usage endpoints round-trip (usage shows free_requests); dashboard renders 10 chips + memory view + palette with no console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a83d3da commit bef3e5a

3 files changed

Lines changed: 226 additions & 7 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,21 @@ Plus two integrations that glue it together:
172172

173173
**Auto-updates:** `aios start` now auto-runs `git pull` + reinstalls changed deps when the repo has updates (`updates.auto_update: true`). Turn it off in `aios.config.yaml`.
174174

175+
## ✨ 10 things no single-agent tool can do
176+
177+
Because The AI OS runs **six agents wired through one hub**, it can do multi-agent things nothing else can:
178+
179+
1. **🎯 Auto-router** — the hub reads your message and *automatically picks the best agent* (code → opencode, research → CrewAI…), and tells you which it chose.
180+
2. **⚖️ Agent Arena** — send one prompt to **two agents side-by-side** and compare their answers.
181+
3. **🏛️ Council** — ask several agents, then a chair agent **synthesizes a consensus** and flags where they disagree.
182+
4. **⛓ Pipelines****chain agents**: research (CrewAI) → build (opencode) → summarize (Brain), each step feeding the next.
183+
5. **🧠 Shared memory** — facts you save are injected into **every** agent's context, so they all know you.
184+
6. **📚 Prompt library + slash commands** — save prompts, type `/name` in chat to expand them.
185+
7. **🎙️ Voice in/out** — talk to the hub and have replies read aloud (browser-native, no cloud).
186+
8. **⌘K command palette** — jump anywhere or ask any agent instantly.
187+
9. **📊 Usage + subscription-savings meter** — counts requests per agent and shows how many ran **free on your Claude subscription**.
188+
10. **⬇️ Export & saved conversations** — full conversation history (pinned/today/earlier), export any chat to Markdown.
189+
175190
**AIOS API (OpenAI-compatible):** the hub is itself an API you can POST to. Point any OpenAI client/SDK at `http://<host>:8787/v1`. The "models" are the agents/targets: `brain`, `team`, `opencode`, `crewai`, `claudecode`, `all`.
176191

177192
```bash

aios_hub.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,33 @@ def read_system_prompt() -> str:
106106
# Chat "targets" the AIOS API exposes as OpenAI-style models.
107107
TARGETS = ["brain", "team", "opencode", "crewai", "claudecode", "all"]
108108

109+
MEMORY_FILE = ROOT / ".aios" / "memory.json"
110+
USAGE_FILE = ROOT / ".aios" / "usage.json"
111+
PROMPTS_FILE = ROOT / ".aios" / "prompts.json"
112+
113+
114+
def _load_json(p, default):
115+
try:
116+
return json.loads(p.read_text(encoding="utf-8"))
117+
except Exception:
118+
return default
119+
120+
121+
def _save_json(p, data):
122+
p.parent.mkdir(parents=True, exist_ok=True)
123+
p.write_text(json.dumps(data, indent=2), encoding="utf-8")
124+
125+
126+
def read_memory() -> list:
127+
return _load_json(MEMORY_FILE, [])
128+
129+
130+
def bump_usage(target: str):
131+
u = _load_json(USAGE_FILE, {})
132+
u[target] = u.get(target, 0) + 1
133+
# claude-code (subscription) and brain-on-subscription cost $0 in API terms.
134+
_save_json(USAGE_FILE, u)
135+
109136

110137
def _llm_key() -> str:
111138
return (os.environ.get("AIOS_LLM_API_KEY") or os.environ.get("OPENROUTER_API_KEY")
@@ -230,13 +257,18 @@ def ask_claudecode(message: str, history: list | None = None) -> str:
230257
def route(target: str, message: str, history: list | None = None) -> str:
231258
history = history or []
232259
target = (target or "brain").lower()
260+
bump_usage(target)
233261
if target in ("brain", "aios", "hub"):
234262
sys = read_system_prompt() or (
235263
"You are the AIOS Brain — the orchestrator of The AI OS, which unifies six agents: "
236264
"opencode (coding), hermes (autonomous), openclaw (channels), CrewAI (multi-agent crews), "
237265
"claude-code (Claude Code API), and LifeOS (shared skills). Be concise and helpful. Suggest "
238266
"which agent is best for a task. When a visual answer helps (charts, tables, forms, dashboards), "
239267
"you may emit OpenUI Lang (https://www.openui.com) which the openclaw-os dashboard renders live.")
268+
mem = read_memory()
269+
if mem: # shared cross-agent memory — every brain reply knows these facts
270+
sys += "\n\nKnown facts / preferences about the user (remember these):\n" + \
271+
"\n".join("- " + str(m) for m in mem[:40])
240272
return llm_chat(history + [{"role": "user", "content": message}], system=sys)
241273
if target == "crewai":
242274
return ask_crewai(message)
@@ -254,6 +286,45 @@ def route(target: str, message: str, history: list | None = None) -> str:
254286
return f"⚠️ Unknown target '{target}'. Use brain | team | crewai | opencode | claudecode | all."
255287

256288

289+
# --------------------------------------------------------------------------- #
290+
# Unique features: auto-router, arena (A/B), council (consensus), pipeline #
291+
# --------------------------------------------------------------------------- #
292+
def auto_route(message: str, history: list | None = None) -> dict:
293+
"""Pick the best agent for the message automatically, then answer with it."""
294+
sysp = ("Classify which AI OS agent should handle the user's request. Reply with ONLY one word: "
295+
"opencode (writing/running code), crewai (multi-step research/workflows), "
296+
"claudecode (coding via Claude Code), or brain (general/orchestration). Request:")
297+
pick = (llm_chat([{"role": "user", "content": message}], system=sysp) or "brain").strip().lower()
298+
pick = next((t for t in ("opencode", "crewai", "claudecode", "brain") if t in pick), "brain")
299+
return {"chosen": pick, "reply": route(pick, message, history)}
300+
301+
302+
def run_arena(message: str, targets: list, history: list | None = None) -> dict:
303+
"""Same prompt to 2+ agents/models, side by side, to compare."""
304+
return {"results": [{"target": t, "reply": route(t, message, history)} for t in targets[:4]]}
305+
306+
307+
def run_council(message: str, targets: list, history: list | None = None) -> dict:
308+
"""Ask several agents, then synthesize a consensus noting agreement/disagreement."""
309+
results = [{"target": t, "reply": route(t, message, history)} for t in targets[:4]]
310+
joined = "\n\n".join(f"[{r['target']}]\n{r['reply']}" for r in results)
311+
consensus = llm_chat([{"role": "user", "content":
312+
f"Question: {message}\n\nAnswers from the council:\n{joined}\n\n"
313+
"Give one best answer. Note where they agree and flag any disagreement."}],
314+
system="You are the council chair for The AI OS. Be decisive and concise.")
315+
return {"results": results, "consensus": consensus}
316+
317+
318+
def run_pipeline(message: str, steps: list, history: list | None = None) -> dict:
319+
"""Chain agents: each step's output feeds the next (research → code → summarize)."""
320+
out, cur = [], message
321+
for t in steps[:6]:
322+
r = route(t, cur, [])
323+
out.append({"target": t, "output": r})
324+
cur = r
325+
return {"steps": out, "final": cur}
326+
327+
257328
# --------------------------------------------------------------------------- #
258329
# "Team" — a single agent that orchestrates the others (the practical merge) #
259330
# --------------------------------------------------------------------------- #
@@ -412,6 +483,14 @@ def do_GET(self):
412483
self._send(200, {"prompt": read_system_prompt()})
413484
elif self.path == "/api/models":
414485
self._send(200, fetch_provider_models())
486+
elif self.path == "/api/memory":
487+
self._send(200, {"memory": read_memory()})
488+
elif self.path == "/api/prompts":
489+
self._send(200, {"prompts": _load_json(PROMPTS_FILE, [])})
490+
elif self.path == "/api/usage":
491+
u = _load_json(USAGE_FILE, {})
492+
free = u.get("claudecode", 0)
493+
self._send(200, {"usage": u, "total": sum(u.values()), "free_requests": free})
415494
elif self.path in ("/v1/models", "/api/v1/models"):
416495
# AIOS as an OpenAI-compatible API: its "models" are the chat targets.
417496
now = int(time.time())
@@ -501,6 +580,40 @@ def do_POST(self):
501580
"Pointed at claude-code, but the Claude CLI isn't logged in yet. Run "
502581
"`aios claude-login` (or `claude setup-token`) in a terminal to authorize your "
503582
"Pro/Max account, then chat.")})
583+
elif self.path == "/api/route_auto":
584+
self._send(200, auto_route(payload.get("message", ""), payload.get("history", [])))
585+
elif self.path == "/api/arena":
586+
self._send(200, run_arena(payload.get("message", ""),
587+
payload.get("targets", ["brain", "claudecode"]),
588+
payload.get("history", [])))
589+
elif self.path == "/api/council":
590+
self._send(200, run_council(payload.get("message", ""),
591+
payload.get("targets", ["brain", "crewai", "claudecode"]),
592+
payload.get("history", [])))
593+
elif self.path == "/api/pipeline":
594+
self._send(200, run_pipeline(payload.get("message", ""),
595+
payload.get("steps", ["crewai", "opencode", "brain"]),
596+
payload.get("history", [])))
597+
elif self.path == "/api/memory":
598+
mem = read_memory()
599+
op = payload.get("op", "add")
600+
if op == "add" and payload.get("text"):
601+
mem.append(payload["text"])
602+
elif op == "delete":
603+
mem = [m for i, m in enumerate(mem) if i != payload.get("index")]
604+
elif op == "clear":
605+
mem = []
606+
_save_json(MEMORY_FILE, mem)
607+
self._send(200, {"ok": True, "memory": mem})
608+
elif self.path == "/api/prompts":
609+
items = _load_json(PROMPTS_FILE, [])
610+
op = payload.get("op", "add")
611+
if op == "add":
612+
items.append({"name": payload.get("name", "prompt"), "text": payload.get("text", "")})
613+
elif op == "delete":
614+
items = [p for p in items if p.get("name") != payload.get("name")]
615+
_save_json(PROMPTS_FILE, items)
616+
self._send(200, {"ok": True, "prompts": items})
504617
elif self.path == "/api/schedules":
505618
items = load_schedules()
506619
op = payload.get("op", "add")

0 commit comments

Comments
 (0)