feat(ruflo-agent): rename ruflo-wasm → ruflo-agent + Claude Managed Agents (cloud) runtime — ADR-115 (proposed→accepted, implemented) (#1929)

* docs(adr): ADR-115 — Claude Managed Agents as a cloud backend for rvagent (explored + validated)

Records the proposal to add `runtime: "managed"` to ruflo's rvagent
interface (ruflo-wasm / @ruvector/rvagent-wasm / wasm_agent_* MCP tools),
backed by Anthropic's Claude Managed Agents API (beta), as a second backend
alongside the local WASM sandbox — mirroring the ConsensusTransport
LocalTransport|FederationTransport pattern (ADR-095 G2 / ADR-104). The
agent/environment/session/events model maps ~1:1 to wasm_agent_create/
prompt/files/terminate + the gallery.

Validated live against api.anthropic.com/v1 with the org's Anthropic key
(GCP claude-flow/anthropic-api-key): created an agent + environment + session,
sent a bash task, observed the full event log (status_running → user.message
→ tool_use(bash) → tool_result → agent.message("Done.") → status_idle,
stop_reason end_turn), cleaned up. ~7s for a trivial Haiku task.

High-value combos noted: ruflo's own MCP server as a Managed Agent tool
source (cloud-hosted ruflo, zero local infra); Managed Agent session as a
third task_assign executor (closes #1916 items 3/4); long-running/async work;
skills + define-outcomes parity. Constraints: opt-in/off-by-default, needs
ANTHROPIC_API_KEY + beta access, plugin-level @anthropic-ai/sdk dep,
cost-tracking + orphaned-session GC, beta churn, branding.

Status: proposed. Registered in AgentDB (adr-ADR-115) + adr-patterns.

Co-Authored-By: RuFlo <ruv@ruv.net>

* refactor(plugins): rename ruflo-wasm → ruflo-agent (agent-runtimes home; ADR-115 prep)

`ruflo-wasm` only covered local WASM-sandboxed agents (rvagent —
@ruvector/rvagent-wasm, the wasm_agent_*/wasm_gallery_* MCP tools). It's
becoming the home for *all* ruflo agent runtimes: the existing local-WASM
backend plus Anthropic's Claude Managed Agents as a cloud backend (ADR-115),
behind one interface — same local-vs-cloud pattern as ConsensusTransport.

- git mv plugins/ruflo-wasm → plugins/ruflo-agent
- plugin.json: name ruflo-wasm → ruflo-agent; description/keywords now span
  both runtimes (rvagent/wasm + managed-agents/anthropic/cloud)
- .claude-plugin/marketplace.json entry renamed
- README / plugins/README / docs/index / cli README / ruflo README /
  discover-plugins SKILL / verification/inventory.json / ADR-115 — refs updated
- wasm_agent_* / wasm_gallery_* MCP tool names are unchanged (they stay; the
  plugin is just renamed). managed_agent_* tools land next per ADR-115.

(Left `ruflo-wasm-mcp` in ruvocal/workerClient.ts alone — unrelated worker MCP name.)

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(cli): managed_agent_* MCP tools — Claude Managed Agents cloud runtime (ADR-115)

Six new MCP tools wrapping Anthropic's Claude Managed Agents REST API (beta,
`anthropic-beta: managed-agents-2026-04-01`) via plain fetch — no new SDK dep:

  managed_agent_create    agents.create + environments.create + sessions.create   ↔ wasm_agent_create (CLOUD counterpart)
  managed_agent_prompt    events.send(user.message) + poll events until idle       ↔ wasm_agent_prompt
  managed_agent_status    GET /v1/sessions/{id}
  managed_agent_events    GET /v1/sessions/{id}/events  (full transcript)           ↔ wasm_agent_files
  managed_agent_list      GET /v1/sessions
  managed_agent_terminate DELETE /v1/sessions/{id} (+ optionally the env)           ↔ wasm_agent_terminate

Needs ANTHROPIC_API_KEY (or CLAUDE_API_KEY); every tool degrades gracefully
with a structured "use wasm_agent_* for a local no-key runtime" error when the
key is absent — the CLI/MCP server stays up. ADR-112-compliant descriptions
(each says when wasm_agent_* / native is the right call instead). Registered
in mcp-client.ts. Lives conceptually in the `ruflo-agent` plugin (the
`ruflo-wasm`→`ruflo-agent` rename in the prior commit).

Validated live against api.anthropic.com with the org's Anthropic key (GCP
claude-flow/anthropic-api-key) via `ruflo mcp exec -t managed_agent_*`:
create (haiku) → prompt ("echo … > /tmp/r.txt && cat") → got finished:true,
status:idle, stopReason:end_turn, assistantText:"Done.", toolUses:[bash],
13 events → terminate (sessionDeleted+environmentDeleted). No-key path returns
the graceful error. audit-cli-mcp-tools 311 registered/0 dangling;
audit-tool-descriptions 0 no-guidance; cli build clean.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(ruflo-agent): wire the managed-agent (cloud) runtime into the plugin + CI smoke

- skills/managed-agent/SKILL.md — drives the cloud runtime: managed_agent_create
  / prompt / status / events / list / terminate, with the WASM-vs-managed
  decision table, the ANTHROPIC_API_KEY prereq + graceful fallback, the
  mcpServers-reachability caveat, and the cost/cleanup discipline (a cloud
  session bills until terminated). Explicit allowed-tools (no wildcard).
- commands/managed-agent.md — quick "list cloud sessions / status / transcript
  / clean up" command (the cloud sibling of /wasm).
- plugin.json — kept rvagent-wasm + ruvllm-wasm keywords (still built on those);
  added managed-agents/anthropic/cloud.
- smoke.sh: step 1 now also asserts name=ruflo-agent + the managed-agents
  keywords; step 10 expects ADR-0001 status `Accepted` (was stale `Proposed`,
  pre-existing red); new step 12 — managed-agent skill+command exist + all 6
  managed_agent_* tools referenced + the skill keeps an explicit allowed-tools
  list + offers the wasm_agent_create fallback. 12 passed / 0 failed.
- .github/workflows/ruflo-agent-smoke.yml — new CI job running smoke.sh on any
  plugins/ruflo-agent/** change (the plugin had no CI gate before).

Validated: `claude --plugin-dir plugins/ruflo-agent` loads the plugin and lists
its skills (managed-agent, wasm-agent, wasm-gallery); plugin.json + all skill
frontmatters parse; smoke.sh 12/0.

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(adr): ADR-115 → Accepted + Implementation section (managed_agent_* landed)

Status Proposed → Accepted. Added an Implementation (2026-05-12) section:
the ruflo-wasm→ruflo-agent rename, the 6 managed_agent_* MCP tools (plain
fetch, no new SDK dep, graceful no-key degradation, ADR-112 descriptions),
the managed-agent skill+command, the smoke.sh/ruflo-agent-smoke.yml CI guard,
the live e2e validation (create→prompt→terminate against api.anthropic.com
with the GCP anthropic-api-key) and `claude --plugin-dir` validation, and the
called-out follow-ups (runtime:"managed" unification, deployed HTTP ruflo MCP
server for the cloud-hosted-ruflo combo, task_assign/federation dispatch to a
Managed Agent session, cost-tracking + orphaned-session GC, multiagent/outcomes).
Tracking issue: #1931.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(cli): no-network guard for managed_agent_* tools (ADR-115)

__tests__/managed-agent-tools.test.ts (10 cases): the 6 managed_agent_*
tools are registered; each has an object inputSchema + a ≥80-char,
ADR-112-ish, unique description that names the wasm_agent_*/native fallback;
and — crucially — every handler degrades gracefully with no ANTHROPIC_API_KEY/
CLAUDE_API_KEY: no throw, structured error mentioning the key + the
wasm_agent_create fallback, no network call (so the CLI/MCP server stays up
offline). Plus managed_agent_prompt's missing-sessionId/message validation.

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(adr): ADR-115 — add the third runtime (Claude Agent SDK) to the picture

ADR-115 adds the *cloud* runtime (Managed Agents); add a section noting the
natural *third* runtime — the Claude Agent SDK (@anthropic-ai/claude-agent-sdk,
"Claude Code as a library": query()/ClaudeSDKClient, hooks, subagents, MCP,
permission modes, sessions, loads .claude/ config + plugins; runs in-process,
full host trust). ruflo-agent → three runtimes behind one model:

  WASM (rvagent)   local sandbox, no host fs/net   untrusted/portable/offline
  SDK  (future)    your process, your filesystem    a real Claude agent loop on the local repo — the in-process `claude -p`
  Managed          Anthropic cloud container        long-running/async, no local infra

The SDK runtime's killer combo: `mcpServers: { ruflo: { command: "npx", args: ["ruflo","mcp","start"] } }` — a *local* stdio MCP server, so the agent gets ruflo's 314 tools with zero deployment (vs Managed Agents needing a reachable URL). Anthropic positions SDK→Managed as prototype→production. Costs: heavy dep (bundles a Claude Code binary), full host trust (no sandbox — least isolated), needs an API key. Implementing it = a follow-up, likely ADR-116 (tracked in #1931).

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
This commit is contained in:
rUvandReuven authored and GitHub committed 2026-05-12 10:24:03 -04:00
1 parent 965ec669d9
commit ef73a1616f
24 files changed
+783 -51

No files matched your search

+3 -3
View File
@@ -72,9 +72,9 @@
"description": "Advanced git workflows with diff analysis, risk scoring, and reviewer recommendations"
},
{
"name": "ruflo-wasm",
"source": "./plugins/ruflo-wasm",
"description": "Sandboxed WASM agent creation, execution, and gallery sharing"
"name": "ruflo-agent",
"source": "./plugins/ruflo-agent",
"description": "Agent runtimes — local WASM-sandboxed agents (rvagent) + Anthropic Claude Managed Agents (cloud); one interface, local-vs-cloud backends"
},
{
"name": "ruflo-workflows",
+30
View File
@@ -0,0 +1,30 @@
# Structural smoke for the ruflo-agent plugin (local WASM runtime — rvagent —
# plus the Anthropic Managed Agents cloud runtime, ADR-115). No network: it's
# a static check of the plugin manifest, skills, commands, MCP-tool references,
# and ADR cross-references. Triggers on changes to the plugin or this workflow.
name: ruflo-agent-smoke
on:
push:
branches: [main, develop, v3]
paths:
- 'plugins/ruflo-agent/**'
- '.github/workflows/ruflo-agent-smoke.yml'
pull_request:
branches: [main]
paths:
- 'plugins/ruflo-agent/**'
- '.github/workflows/ruflo-agent-smoke.yml'
workflow_dispatch:
jobs:
smoke:
name: ruflo-agent structural smoke
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Run plugin smoke
run: bash plugins/ruflo-agent/scripts/smoke.sh
+1 -1
View File
@@ -139,7 +139,7 @@ This adds slash commands and agent definitions only. The Ruflo MCP server is NOT
| Plugin | What it does |
|--------|-------------|
| **ruflo-wasm** | Run sandboxed WebAssembly agents |
| **ruflo-agent** | Run sandboxed WebAssembly agents |
| **ruflo-plugin-creator** | Scaffold, validate, and publish your own plugins |
#### Domain-Specific
+1 -1
View File
@@ -39,7 +39,7 @@ RuFlo provides native Claude Code plugins for multi-agent orchestration, /loop w
| **ruflo-aidefence** | AI safety scanning, PII detection, prompt defense | `/plugin install ruflo-aidefence@ruflo` |
| **ruflo-browser** | Playwright browser automation, testing, scraping | `/plugin install ruflo-browser@ruflo` |
| **ruflo-jujutsu** | Git diff analysis, risk scoring, reviewer recs | `/plugin install ruflo-jujutsu@ruflo` |
| **ruflo-wasm** | Sandboxed WASM agents and gallery sharing | `/plugin install ruflo-wasm@ruflo` |
| **ruflo-agent** | Sandboxed WASM agents and gallery sharing | `/plugin install ruflo-agent@ruflo` |
| **ruflo-workflows** | Workflow templates, orchestration, lifecycle | `/plugin install ruflo-workflows@ruflo` |
| **ruflo-daa** | Dynamic Agentic Architecture, cognitive patterns | `/plugin install ruflo-daa@ruflo` |
| **ruflo-ruvllm** | Local LLM inference, MicroLoRA, chat formatting | `/plugin install ruflo-ruvllm@ruflo` |
+1 -1
View File
@@ -60,7 +60,7 @@ claude $(ls -d plugins/ruflo-*/ | sed 's|^|--plugin-dir |' | tr '\n' ' ')
| [ruflo-jujutsu](ruflo-jujutsu/) | Diff analysis, risk scoring, reviewer recommendations |
| [ruflo-docs](ruflo-docs/) | Doc generation, drift detection, API docs |
| [ruflo-ruvllm](ruflo-ruvllm/) | Local LLM inference, MicroLoRA, chat formatting |
| [ruflo-wasm](ruflo-wasm/) | WASM agent sandboxing and gallery |
| [ruflo-agent](ruflo-agent/) | WASM agent sandboxing and gallery |
| [ruflo-plugin-creator](ruflo-plugin-creator/) | Scaffold and validate new plugins |
| [ruflo-migrations](ruflo-migrations/) | Database schema migration management |
| [ruflo-observability](ruflo-observability/) | Structured logging, tracing, metrics correlation |
@@ -0,0 +1,25 @@
{
"name": "ruflo-agent",
"description": "Agent runtimes for ruflo \u2014 local WASM-sandboxed agents (rvagent: 10 wasm_agent_*/wasm_gallery_* MCP tools, built on @ruvector/rvagent-wasm + @ruvector/ruvllm-wasm per ADR-070) plus Anthropic Claude Managed Agents as a cloud backend (managed_agent_* MCP tools per ADR-115). One interface, local-vs-cloud runtimes.",
"version": "0.2.0",
"author": {
"name": "ruvnet",
"url": "https://github.com/ruvnet"
},
"homepage": "https://github.com/ruvnet/ruflo",
"license": "MIT",
"keywords": [
"ruflo",
"agent",
"rvagent",
"rvagent-wasm",
"ruvllm-wasm",
"wasm",
"sandbox",
"managed-agents",
"anthropic",
"cloud",
"mcp",
"gallery"
]
}
@@ -1,4 +1,4 @@
# ruflo-wasm
# ruflo-agent
Sandboxed WASM agent creation, execution, and gallery sharing.
@@ -6,7 +6,7 @@ Sandboxed WASM agent creation, execution, and gallery sharing.
```
/plugin marketplace add ruvnet/ruflo
/plugin install ruflo-wasm@ruflo
/plugin install ruflo-agent@ruflo
```
## Features
@@ -29,7 +29,7 @@ Sandboxed WASM agent creation, execution, and gallery sharing.
- **CLI:** pinned to `@claude-flow/cli` v3.6 major+minor.
- **WASM runtime:** built on `@ruvector/rvagent-wasm` + `@ruvector/ruvllm-wasm`. Both are declared in `@claude-flow/cli`'s `optionalDependencies` per [ADR-070 (Implemented)](../../v3/implementation/adrs/ADR-070-rvagent-wasm-completion.md). Without those packages, runtime falls through to the graceful-degradation path and the MCP tools no-op.
- **Verification:** `bash plugins/ruflo-wasm/scripts/smoke.sh` is the contract.
- **Verification:** `bash plugins/ruflo-agent/scripts/smoke.sh` is the contract.
## MCP surface (10 tools)
@@ -70,13 +70,13 @@ This plugin owns the `wasm-gallery` AgentDB namespace (kebab-case, follows the c
## Verification
```bash
bash plugins/ruflo-wasm/scripts/smoke.sh
bash plugins/ruflo-agent/scripts/smoke.sh
# Expected: "11 passed, 0 failed"
```
## Architecture Decisions
- [`ADR-0001` — ruflo-wasm plugin contract (10-tool MCP surface, ADR-070 integration cross-reference, sandbox isolation, smoke as contract)](./docs/adrs/0001-wasm-contract.md)
- [`ADR-0001` — ruflo-agent plugin contract (10-tool MCP surface, ADR-070 integration cross-reference, sandbox isolation, smoke as contract)](./docs/adrs/0001-wasm-contract.md)
## Related Plugins
@@ -0,0 +1,13 @@
---
name: managed-agent
description: Anthropic Claude Managed Agents (cloud runtime) — list cloud sessions, check status, fetch a transcript, clean up
---
Manage Claude Managed Agent sessions (the cloud agent runtime; the local WASM runtime is the `/wasm` command).
1. Call `mcp__claude-flow__managed_agent_list` to show every Managed Agent session on this org (id, status, title) — note which are still `running` (i.e. still billing).
2. For a specific session: `mcp__claude-flow__managed_agent_status` `{ sessionId }` (idle/running/error) and `mcp__claude-flow__managed_agent_events` `{ sessionId }` (full transcript + a summary: assistantText, toolUses).
3. To run new work: `mcp__claude-flow__managed_agent_create` then `mcp__claude-flow__managed_agent_prompt` (see the `managed-agent` skill for the full flow + the cost/cleanup notes).
4. Clean up stale sessions: `mcp__claude-flow__managed_agent_terminate` `{ sessionId, environmentId? }` — a cloud session bills until deleted.
If `managed_agent_*` returns "needs ANTHROPIC_API_KEY" → no key/beta access; use the local `/wasm` runtime instead.
File renamed without changes.
@@ -1,6 +1,6 @@
---
id: ADR-0001
title: ruflo-wasm plugin contract — pinning, namespace coordination, ADR-070 integration cross-reference, smoke as contract
title: ruflo-agent plugin contract — pinning, namespace coordination, ADR-070 integration cross-reference, smoke as contract
status: Accepted
date: 2026-05-04
updated: 2026-05-09
@@ -11,7 +11,7 @@ tags: [plugin, wasm, sandbox, gallery, namespace, smoke-test]
## Context
`ruflo-wasm` (v0.1.0) — sandboxed WASM agent creation, execution, and gallery sharing. 1 agent (`wasm-specialist`), 2 skills (`wasm-agent`, `wasm-gallery`), 1 command (`/wasm`).
`ruflo-agent` (v0.1.0) — sandboxed WASM agent creation, execution, and gallery sharing. 1 agent (`wasm-specialist`), 2 skills (`wasm-agent`, `wasm-gallery`), 1 command (`/wasm`).
Wraps **10 `wasm_*` MCP tools** at `v3/@claude-flow/cli/src/mcp-tools/wasm-agent-tools.ts:18, 51, 74, 103, 117, 138, 160, 181, 195, 216`:
@@ -48,7 +48,7 @@ Wraps **10 `wasm_*` MCP tools** at `v3/@claude-flow/cli/src/mcp-tools/wasm-agent
## Verification
```bash
bash plugins/ruflo-wasm/scripts/smoke.sh
bash plugins/ruflo-agent/scripts/smoke.sh
# Expected: "11 passed, 0 failed"
```
@@ -61,4 +61,4 @@ bash plugins/ruflo-wasm/scripts/smoke.sh
## Implementation status
Plugin version v0.2.0 shipped and listed in marketplace.json. Source exists at `plugins/ruflo-wasm/`. Contract elements implemented: all 10 `wasm_*` MCP tools covered; ADR-070 (rvagent WASM completion) cross-referenced; 3-gate pattern alignment with ruflo-aidefence documented for sandboxed prompt-injection defense; namespace `wasm-agents` claimed; smoke-as-contract gate defined in `scripts/smoke.sh` (11 checks).
Plugin version v0.2.0 shipped and listed in marketplace.json. Source exists at `plugins/ruflo-agent/`. Contract elements implemented: all 10 `wasm_*` MCP tools covered; ADR-070 (rvagent WASM completion) cross-referenced; 3-gate pattern alignment with ruflo-aidefence documented for sandboxed prompt-injection defense; namespace `wasm-agents` claimed; smoke-as-contract gate defined in `scripts/smoke.sh` (11 checks).
@@ -7,12 +7,17 @@ step() { printf "→ %s ... " "$1"; }
ok() { printf "PASS\n"; PASS=$((PASS+1)); }
bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
step "1. plugin.json declares 0.2.0 with new keywords"
v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
if [[ "$v" != "0.2.0" ]]; then bad "expected 0.2.0, got '$v'"; else
step "1. plugin.json is ruflo-agent 0.2.0 with both-runtime keywords"
P="$ROOT/.claude-plugin/plugin.json"
v=$(grep -E '"version"' "$P" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
n=$(grep -E '"name"' "$P" | grep -oE 'ruflo-agent' | head -1)
if [[ "$v" != "0.2.0" ]]; then bad "expected version 0.2.0, got '$v'";
elif [[ "$n" != "ruflo-agent" ]]; then bad "expected name ruflo-agent";
else
miss=""
for k in mcp rvagent-wasm ruvllm-wasm; do
grep -q "\"$k\"" "$ROOT/.claude-plugin/plugin.json" || miss="$miss $k"
# local WASM runtime (rvagent) + cloud runtime (managed-agents) keywords
for k in mcp rvagent-wasm ruvllm-wasm managed-agents anthropic; do
grep -q "\"$k\"" "$P" || miss="$miss $k"
done
[[ -z "$miss" ]] && ok || bad "missing keywords:$miss"
fi
@@ -71,10 +76,10 @@ grep -q "ruflo-aidefence" "$F" \
&& grep -qE "3-gate|3 gates" "$F" \
&& ok || bad "AIDefence 3-gate cross-reference missing"
step "10. ADR-0001 exists with status Proposed"
step "10. ADR-0001 exists with status Accepted"
ADR="$ROOT/docs/adrs/0001-wasm-contract.md"
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Proposed" "$ADR" \
&& ok || bad "ADR missing or status != Proposed"
[[ -f "$ADR" ]] && grep -qE "^status:[[:space:]]*Accepted" "$ADR" \
&& ok || bad "ADR missing or status != Accepted"
step "11. no wildcard tool grants in skills"
bad_skills=""
@@ -83,5 +88,17 @@ for f in "$ROOT"/skills/*/SKILL.md; do
done
[[ -z "$bad_skills" ]] && ok || bad "wildcard:$bad_skills"
step "12. managed-agent (cloud runtime) skill + command + all 6 managed_agent_* tools referenced (ADR-115)"
miss=""
[[ -f "$ROOT/skills/managed-agent/SKILL.md" ]] || miss="$miss missing-skill"
[[ -f "$ROOT/commands/managed-agent.md" ]] || miss="$miss missing-command"
for t in managed_agent_create managed_agent_prompt managed_agent_status managed_agent_events managed_agent_list managed_agent_terminate; do
grep -rq "$t" "$ROOT/skills" "$ROOT/commands" || miss="$miss no-ref-$t"
done
# the cloud-runtime skill must keep an explicit allowed-tools list (no wildcard) and offer the wasm fallback
grep -q '^allowed-tools:[[:space:]]*\*' "$ROOT/skills/managed-agent/SKILL.md" 2>/dev/null && miss="$miss wildcard"
grep -q 'wasm_agent_create' "$ROOT/skills/managed-agent/SKILL.md" 2>/dev/null || miss="$miss no-wasm-fallback-ref"
[[ -z "$miss" ]] && ok || bad "$miss"
printf "\n%s passed, %s failed\n" "$PASS" "$FAIL"
[[ $FAIL -eq 0 ]] || exit 1
@@ -0,0 +1,56 @@
---
name: managed-agent
description: Run an Anthropic Claude Managed Agent — a cloud agent harness (container + filesystem + tools), the cloud counterpart of the local wasm-agent runtime
argument-hint: "<create|prompt|status|events|list|terminate> [options]"
allowed-tools: mcp__claude-flow__managed_agent_create mcp__claude-flow__managed_agent_prompt mcp__claude-flow__managed_agent_status mcp__claude-flow__managed_agent_events mcp__claude-flow__managed_agent_list mcp__claude-flow__managed_agent_terminate mcp__claude-flow__wasm_agent_create Bash
---
# Managed Agent (Anthropic cloud runtime)
`ruflo-agent` has two agent runtimes behind one mental model:
| Runtime | Tools | Use it when |
|---|---|---|
| **WASM** (local, `rvagent`) | `wasm_agent_*` / `wasm_gallery_*` | fast, free, ephemeral, offline, untrusted code in a sandbox |
| **Managed** (Anthropic cloud) | `managed_agent_*` (this skill) | long-running / async work (minutes–hours), a real cloud container with pre-installed packages + network, persistent filesystem + transcript across turns |
This skill drives the **managed** runtime — Anthropic's [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) (beta). The model: **Agent** (model + system + tools + MCP servers + skills) → **Environment** (container template) → **Session** (running instance) → **Events** (turns / tool-use / status, persisted server-side). See `docs/adr/0001-wasm-contract.md` and project ADR-115.
## Prerequisites
- `ANTHROPIC_API_KEY` (or `CLAUDE_API_KEY`) in the environment, with Claude Managed Agents beta access.
- If absent, every `managed_agent_*` tool returns a structured "use `wasm_agent_create` for a local no-key runtime" error — fall back to the WASM skill.
## Steps
1. **Create** — `mcp__claude-flow__managed_agent_create`
`{ model?, system?, name?, networking?, packages?, initScript?, mcpServers?, skills? }`
→ `{ sessionId, agentId, environmentId, status }`. Provisions Agent + Environment + Session. Save the three ids.
- `mcpServers`: `[{type:"url", url, name, authorization_token?}]` — the cloud agent must be able to *reach* the URL. A local `ruflo mcp start` is **not** reachable from Anthropic's cloud; deploy/tunnel an HTTP ruflo MCP server first if you want the cloud agent to have ruflo's tools.
- `packages`: `{pip?:[], npm?:[], apt?:[], cargo?:[], gem?:[], go?:[]}` — installed in the container.
2. **Prompt** — `mcp__claude-flow__managed_agent_prompt`
`{ sessionId, message, maxWaitMs? }` → sends a user turn, polls the event log until the session goes idle (default 180s, capped 600s) → `{ finished, status, stopReason, assistantText, toolUses[], eventCount }`. For very long tasks, raise `maxWaitMs` or follow up with `managed_agent_events`.
3. **Inspect** — `mcp__claude-flow__managed_agent_status` `{ sessionId }` (idle/running/error) · `mcp__claude-flow__managed_agent_events` `{ sessionId, raw? }` (full transcript: user turns, agent thinking, tool_use, tool_result, status — the cloud counterpart of `wasm_agent_files`).
4. **List** — `mcp__claude-flow__managed_agent_list` `{ limit? }` — every session on the org (so you can see which are still running / billing).
5. **Terminate** — `mcp__claude-flow__managed_agent_terminate` `{ sessionId, environmentId? }` — **always do this when done**: a cloud session keeps billing container time + tokens until deleted. Pass `environmentId` to also delete the environment ruflo created.
## Cost & safety
- Managed Agents bill per session (LM tokens + container time) and are rate-limited per org. Estimate before a long run; record completed sessions to the `cost-tracking` namespace.
- Treat orphaned sessions like leaked resources — `managed_agent_list` then `managed_agent_terminate` anything stale.
- Beta API (`managed-agents-2026-04-01`); `multiagent` / `define-outcomes` on the agent config are research preview.
## Quick example
```
managed_agent_create { "model": "claude-haiku-4-5-20251001", "system": "Terse. Do exactly what is asked.", "name": "scratch" }
→ { sessionId: "sesn_…", agentId: "agent_…", environmentId: "env_…", status: "idle" }
managed_agent_prompt { "sessionId": "sesn_…", "message": "echo hello > /tmp/x && cat /tmp/x — then stop." , "maxWaitMs": 60000 }
→ { finished: true, status: "idle", stopReason: "end_turn", assistantText: "Done.", toolUses: [{name:"bash", input:{command:"echo hello > /tmp/x && cat /tmp/x"}}] }
managed_agent_terminate { "sessionId": "sesn_…", "environmentId": "env_…" }
→ { sessionDeleted: true, environmentDeleted: true }
```
@@ -70,7 +70,7 @@ When starting a new project, exploring ruflo capabilities, or wondering which pl
| **ruflo-jujutsu** | PR review, merge decisions, diff risk scoring | Diff analysis, risk classification, reviewer recommendations |
| **ruflo-docs** | After API changes, before releases | Doc generation, drift detection, API documentation |
| **ruflo-ruvllm** | Local LLM inference, custom model configs | RuVLLM integration, MicroLoRA fine-tuning, chat formatting |
| **ruflo-wasm** | Sandboxed code execution, untrusted workloads | WASM agent sandboxing, community gallery |
| **ruflo-agent** | Sandboxed code execution, untrusted workloads | WASM agent sandboxing, community gallery |
| **ruflo-plugin-creator** | Building new ruflo plugins | Scaffold structure, validate frontmatter, test MCP references |
| **ruflo-migrations** | Database schema changes | Sequential migration numbering, up/down pairs, dry-run, rollback validation |
| **ruflo-observability** | Logging, tracing, metrics correlation | Structured JSON logging, distributed tracing, agent-to-app telemetry correlation |
@@ -1,21 +0,0 @@
{
"name": "ruflo-wasm",
"description": "Sandboxed WASM agent creation, execution, and gallery sharing — wraps 10 wasm_* MCP tools (agent_create/prompt/tool/list/terminate/files/export + gallery_list/search/create); built on @ruvector/rvagent-wasm + @ruvector/ruvllm-wasm per ADR-070 (Implemented)",
"version": "0.2.0",
"author": {
"name": "ruvnet",
"url": "https://github.com/ruvnet"
},
"homepage": "https://github.com/ruvnet/ruflo",
"license": "MIT",
"keywords": [
"ruflo",
"wasm",
"sandbox",
"agents",
"gallery",
"mcp",
"rvagent-wasm",
"ruvllm-wasm"
]
}
+1 -1
View File
@@ -139,7 +139,7 @@ This adds slash commands and agent definitions only. The Ruflo MCP server is NOT
| Plugin | What it does |
|--------|-------------|
| **ruflo-wasm** | Run sandboxed WebAssembly agents |
| **ruflo-agent** | Run sandboxed WebAssembly agents |
| **ruflo-plugin-creator** | Scaffold, validate, and publish your own plugins |
#### Domain-Specific
+1 -1
View File
@@ -139,7 +139,7 @@ This adds slash commands and agent definitions only. The Ruflo MCP server is NOT
| Plugin | What it does |
|--------|-------------|
| **ruflo-wasm** | Run sandboxed WebAssembly agents |
| **ruflo-agent** | Run sandboxed WebAssembly agents |
| **ruflo-plugin-creator** | Scaffold, validate, and publish your own plugins |
#### Domain-Specific
@@ -0,0 +1,92 @@
/**
* Guard for ADR-115 — the managed_agent_* MCP tools (Claude Managed Agents
* cloud runtime, in the `ruflo-agent` plugin). No-network: every handler must
* short-circuit with a structured "needs ANTHROPIC_API_KEY → use wasm_agent_*"
* error when no key is set, so the CLI/MCP server stays up offline.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { managedAgentTools } from '../src/mcp-tools/managed-agent-tools.js';
const TOOL_NAMES = [
'managed_agent_create',
'managed_agent_prompt',
'managed_agent_status',
'managed_agent_events',
'managed_agent_list',
'managed_agent_terminate',
] as const;
const tool = (name: string) => {
const t = managedAgentTools.find(t => t.name === name);
if (!t) throw new Error(`managed-agent tool not registered: ${name}`);
return t;
};
describe('ADR-115 — managed_agent_* MCP tools', () => {
let prevAnth: string | undefined;
let prevClaude: string | undefined;
beforeEach(() => {
prevAnth = process.env.ANTHROPIC_API_KEY;
prevClaude = process.env.CLAUDE_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
delete process.env.CLAUDE_API_KEY;
});
afterEach(() => {
if (prevAnth === undefined) delete process.env.ANTHROPIC_API_KEY; else process.env.ANTHROPIC_API_KEY = prevAnth;
if (prevClaude === undefined) delete process.env.CLAUDE_API_KEY; else process.env.CLAUDE_API_KEY = prevClaude;
});
it('registers exactly the six managed_agent_* tools', () => {
const names = managedAgentTools.map(t => t.name).sort();
expect(names).toEqual([...TOOL_NAMES].sort());
});
it('every tool has an inputSchema (object) and an ADR-112-ish description that mentions the wasm_agent_* / native fallback', () => {
for (const t of managedAgentTools) {
expect(t.inputSchema?.type).toBe('object');
expect(typeof t.description).toBe('string');
expect(t.description.length).toBeGreaterThanOrEqual(80);
// each description should orient the caller toward wasm_agent_* / native when that's the right call
expect(t.description).toMatch(/wasm_agent_|native/);
expect(t.category).toBe('agent');
}
});
it('descriptions are unique (no copy-paste)', () => {
const descs = managedAgentTools.map(t => t.description);
expect(new Set(descs).size).toBe(descs.length);
});
for (const name of TOOL_NAMES) {
it(`${name} degrades gracefully with no API key (no throw, structured error, no network)`, async () => {
// minimal valid-ish input per tool's required fields
const input: Record<string, unknown> =
name === 'managed_agent_prompt' ? { sessionId: 's', message: 'm' }
: name === 'managed_agent_create' ? {}
: name === 'managed_agent_list' ? {}
: { sessionId: 's' };
const res = (await (tool(name).handler as any)(input)) as Record<string, unknown>;
expect(res).toBeTruthy();
expect(String(res.error ?? '')).toMatch(/ANTHROPIC_API_KEY/);
expect(String(res.error ?? '')).toMatch(/wasm_agent_create/);
// tools that report success should report it false
if ('success' in res) expect(res.success).toBe(false);
// list-shaped tools still return an empty list
if (name === 'managed_agent_list') {
expect(res.sessions).toEqual([]);
expect(res.total).toBe(0);
}
});
}
it('managed_agent_prompt rejects missing sessionId/message (with a key set, but no network call made)', async () => {
process.env.ANTHROPIC_API_KEY = 'sk-ant-test-not-real';
const noSession = await (tool('managed_agent_prompt').handler as any)({ message: 'hi' });
expect(noSession.success).toBe(false);
expect(String(noSession.error)).toMatch(/sessionId/);
const noMessage = await (tool('managed_agent_prompt').handler as any)({ sessionId: 's' });
expect(noMessage.success).toBe(false);
expect(String(noMessage.error)).toMatch(/message/);
});
});
+6
View File
@@ -42,6 +42,10 @@ import { agentdbTools } from './mcp-tools/agentdb-tools.js';
// RuVector WASM tools
import { ruvllmWasmTools } from './mcp-tools/ruvllm-tools.js';
import { wasmAgentTools } from './mcp-tools/wasm-agent-tools.js';
// ADR-115: Anthropic Claude Managed Agents — a cloud agent runtime alongside
// the local WASM-sandboxed `wasm_agent_*` (rvagent) tools. Lives in the
// `ruflo-agent` plugin.
import { managedAgentTools } from './mcp-tools/managed-agent-tools.js';
import { guidanceTools } from './mcp-tools/guidance-tools.js';
import { autopilotTools } from './mcp-tools/autopilot-tools.js';
// #1916: coverage-aware routing tools — defined in ruvector/coverage-tools.ts
@@ -118,6 +122,8 @@ registerTools([
// RuVector WASM tools
...ruvllmWasmTools,
...wasmAgentTools,
// ADR-115: Anthropic Claude Managed Agents (cloud agent runtime)
...managedAgentTools,
// Guidance & discovery tools
...guidanceTools,
// Autopilot persistent completion tools
@@ -0,0 +1,359 @@
/**
* Managed Agent MCP tools — Anthropic Claude Managed Agents as a *cloud*
* agent runtime alongside ruflo's local WASM-sandboxed agents (`rvagent` /
* `wasm_agent_*`). See ADR-115.
*
* Wraps the Managed Agents REST API (beta, `anthropic-beta:
* managed-agents-2026-04-01`) with plain `fetch` — no new SDK dependency.
* Needs `ANTHROPIC_API_KEY` (or `CLAUDE_API_KEY`); every tool degrades
* gracefully with a structured error when the key is absent so the CLI/MCP
* server stays up.
*
* Lifecycle (mirrors `wasm_agent_*`):
* managed_agent_create → agents.create + environments.create + sessions.create ↔ wasm_agent_create
* managed_agent_prompt → events.send(user.message) + poll events until idle ↔ wasm_agent_prompt
* managed_agent_status → GET /v1/sessions/{id}
* managed_agent_events → GET /v1/sessions/{id}/events (full transcript) ↔ wasm_agent_files (artifacts/log)
* managed_agent_list → GET /v1/sessions
* managed_agent_terminate→ DELETE /v1/sessions/{id} (+ optionally the env) ↔ wasm_agent_terminate
*/
import type { MCPTool } from './types.js';
const API_BASE = process.env.ANTHROPIC_BASE_URL?.replace(/\/$/, '') || 'https://api.anthropic.com';
const BETA_HEADER = 'managed-agents-2026-04-01';
const ANTHROPIC_VERSION = '2023-06-01';
const DEFAULT_MODEL = 'claude-sonnet-4-6';
const POLL_INTERVAL_MS = 1500;
const DEFAULT_MAX_WAIT_MS = 180_000; // 3 min — long enough for a real task, bounded so a tool call never hangs forever
function apiKey(): string | null {
return process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_API_KEY || null;
}
function headers(key: string): Record<string, string> {
return {
'x-api-key': key,
'anthropic-version': ANTHROPIC_VERSION,
'anthropic-beta': BETA_HEADER,
'content-type': 'application/json',
};
}
const NEEDS_KEY = {
error:
'managed-agent runtime needs ANTHROPIC_API_KEY (or CLAUDE_API_KEY) and Claude Managed Agents beta access. ' +
'For a local, no-key agent runtime use wasm_agent_create instead (rvagent / WASM sandbox).',
};
async function maRequest<T = unknown>(
method: 'GET' | 'POST' | 'DELETE',
path: string,
body?: unknown,
): Promise<{ ok: true; status: number; data: T } | { ok: false; status: number; error: string; body?: unknown }> {
const key = apiKey();
if (!key) return { ok: false, status: 0, error: NEEDS_KEY.error };
let res: Response;
try {
res = await fetch(`${API_BASE}/v1${path}`, {
method,
headers: headers(key),
body: body !== undefined ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30_000),
});
} catch (e) {
return { ok: false, status: 0, error: `network error calling ${method} /v1${path}: ${(e as Error).message}` };
}
const text = await res.text();
let parsed: unknown = undefined;
try { parsed = text ? JSON.parse(text) : undefined; } catch { /* leave undefined */ }
if (!res.ok) {
const apiErr = (parsed as { error?: { message?: string }; message?: string })?.error?.message
?? (parsed as { message?: string })?.message
?? text.slice(0, 300);
return { ok: false, status: res.status, error: `Managed Agents API ${res.status}: ${apiErr}`, body: parsed };
}
return { ok: true, status: res.status, data: parsed as T };
}
// ---- shapes (only the fields we touch) -----------------------------------
interface MaAgent { id: string; version?: number; name?: string; model?: { id: string } }
interface MaEnvironment { id: string; name?: string; config?: unknown }
interface MaSession { id: string; status?: string; title?: string; error?: string | null; agent?: MaAgent; environment_id?: string }
interface MaEvent { type: string; content?: Array<{ type: string; text?: string }>; name?: string; input?: unknown; id?: string; processed_at?: string; stop_reason?: { type: string } }
// ---- helpers --------------------------------------------------------------
function summarizeEvents(events: MaEvent[]): {
assistantText: string;
toolUses: Array<{ name: string; input: unknown }>;
status: string;
stopReason: string | null;
eventCount: number;
} {
let assistantText = '';
const toolUses: Array<{ name: string; input: unknown }> = [];
let status = 'unknown';
let stopReason: string | null = null;
for (const e of events) {
if (e.type === 'agent.message' && Array.isArray(e.content)) {
assistantText += e.content.filter(b => b.type === 'text').map(b => b.text ?? '').join('');
} else if (e.type === 'agent.tool_use') {
toolUses.push({ name: e.name ?? '?', input: e.input });
} else if (e.type === 'session.status_running') {
status = 'running';
} else if (e.type === 'session.status_idle') {
status = 'idle';
stopReason = e.stop_reason?.type ?? null;
} else if (e.type === 'session.status_error' || e.type === 'session.status_failed') {
status = 'error';
}
}
return { assistantText, toolUses, status, stopReason, eventCount: events.length };
}
async function fetchSessionEvents(sessionId: string): Promise<MaEvent[]> {
const r = await maRequest<{ data?: MaEvent[]; events?: MaEvent[] }>('GET', `/sessions/${encodeURIComponent(sessionId)}/events`);
if (!r.ok) return [];
const d = r.data;
return (d.data ?? d.events ?? []) as MaEvent[];
}
// Wait until the session is no longer "running": poll the event log for a
// terminal session.status_* event. Bounded by maxWaitMs.
async function waitForIdle(sessionId: string, maxWaitMs: number): Promise<{ done: boolean; events: MaEvent[] }> {
const deadline = Date.now() + Math.max(1_000, Math.min(maxWaitMs, 600_000));
let events: MaEvent[] = [];
while (Date.now() < deadline) {
events = await fetchSessionEvents(sessionId);
const terminal = events.some(e => e.type === 'session.status_idle' || e.type === 'session.status_error' || e.type === 'session.status_failed');
// Also stop early if the session record itself reports a non-running status
if (terminal) return { done: true, events };
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
}
return { done: false, events };
}
function maName(prefix: string, given?: unknown): string {
if (typeof given === 'string' && given.trim()) return given.trim().slice(0, 80);
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
}
// ---------------------------------------------------------------------------
export const managedAgentTools: MCPTool[] = [
{
name: 'managed_agent_create',
description:
'Spin up an Anthropic-managed cloud agent (Agent + Environment + Session) — the CLOUD counterpart of wasm_agent_create. Use when wasm_agent_create (local WASM sandbox) is wrong because the task is long-running/async (minutes-hours), needs a real cloud container with pre-installed packages + network, or persistent filesystem + transcript across turns. For a fast, free, ephemeral, offline agent use wasm_agent_create (rvagent). Needs ANTHROPIC_API_KEY + Managed Agents beta access. Returns {sessionId, agentId, environmentId}; pair with managed_agent_prompt.',
category: 'agent',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Agent name (default: auto)' },
model: { type: 'string', description: 'Model id (default: claude-sonnet-4-6)' },
system: { type: 'string', description: 'System prompt' },
title: { type: 'string', description: 'Session title' },
mcpServers: {
type: 'array',
description: 'MCP servers to expose to the agent — each {type:"url", url, name, authorization_token?}. NOTE: the cloud agent must be able to *reach* the URL (a local `ruflo mcp start` is not reachable from Anthropic\'s cloud — deploy/tunnel it).',
items: { type: 'object' },
},
skills: { type: 'array', description: 'Skills to attach to the agent', items: { type: 'object' } },
packages: { type: 'object', description: 'Environment packages: {pip?:[], npm?:[], apt?:[], cargo?:[], gem?:[], go?:[]}' },
networking: { type: 'string', enum: ['unrestricted', 'restricted', 'none'], description: 'Environment networking (default: unrestricted)' },
initScript: { type: 'string', description: 'Environment init script (bash, run at container start)' },
},
},
handler: async (input) => {
if (!apiKey()) return { success: false, ...NEEDS_KEY };
// 1. Agent
const agentBody: Record<string, unknown> = {
name: maName('ruflo-managed', input.name),
model: typeof input.model === 'string' && input.model ? input.model : DEFAULT_MODEL,
tools: [{ type: 'agent_toolset_20260401' }],
};
if (typeof input.system === 'string') agentBody.system = input.system;
if (Array.isArray(input.mcpServers) && input.mcpServers.length) agentBody.mcp_servers = input.mcpServers;
if (Array.isArray(input.skills) && input.skills.length) agentBody.skills = input.skills;
const a = await maRequest<MaAgent>('POST', '/agents', agentBody);
if (!a.ok) return { success: false, stage: 'agent', error: a.error };
// 2. Environment
const net = (input.networking as string) || 'unrestricted';
const envBody: Record<string, unknown> = {
name: maName('ruflo-managed-env', input.name),
config: { type: 'cloud', networking: { type: net } },
};
if (input.packages && typeof input.packages === 'object') (envBody.config as Record<string, unknown>).packages = { type: 'packages', ...(input.packages as object) };
if (typeof input.initScript === 'string' && input.initScript) (envBody.config as Record<string, unknown>).init_script = input.initScript;
const e = await maRequest<MaEnvironment>('POST', '/environments', envBody);
if (!e.ok) return { success: false, stage: 'environment', agentId: a.data.id, error: e.error };
// 3. Session
const s = await maRequest<MaSession>('POST', '/sessions', {
agent: a.data.id,
environment_id: e.data.id,
title: maName('ruflo-managed session', input.title),
});
if (!s.ok) return { success: false, stage: 'session', agentId: a.data.id, environmentId: e.data.id, error: s.error };
return {
success: true,
runtime: 'managed',
sessionId: s.data.id,
agentId: a.data.id,
agentVersion: a.data.version,
environmentId: e.data.id,
status: s.data.status ?? 'idle',
model: agentBody.model,
note: 'Cloud agent provisioned. Send work with managed_agent_prompt({sessionId, message}); inspect with managed_agent_events; clean up with managed_agent_terminate (it bills container time + tokens until you do).',
};
},
},
{
name: 'managed_agent_prompt',
description:
'Send a user turn to a managed cloud-agent session and wait for it to go idle, returning the assistant text + a tool-use trace — the CLOUD counterpart of wasm_agent_prompt. Use when wasm_agent_prompt (local WASM) is wrong because the work is long-running, needs the cloud container, or must persist across turns. Polls the session event log up to maxWaitMs (default 180s); for very long tasks raise maxWaitMs or follow up with managed_agent_events. Pair with managed_agent_create (for sessionId).',
category: 'agent',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Session id from managed_agent_create' },
message: { type: 'string', description: 'The user turn / task for the agent' },
maxWaitMs: { type: 'number', description: 'Max ms to wait for the session to go idle (default 180000, capped at 600000)' },
},
required: ['sessionId', 'message'],
},
handler: async (input) => {
if (!apiKey()) return { success: false, ...NEEDS_KEY };
const sessionId = String(input.sessionId ?? '');
const message = String(input.message ?? '');
if (!sessionId) return { success: false, error: 'sessionId is required' };
if (!message) return { success: false, error: 'message is required' };
const send = await maRequest('POST', `/sessions/${encodeURIComponent(sessionId)}/events`, {
events: [{ type: 'user.message', content: [{ type: 'text', text: message }] }],
});
if (!send.ok) return { success: false, stage: 'send', sessionId, error: send.error };
const maxWait = typeof input.maxWaitMs === 'number' && input.maxWaitMs > 0 ? input.maxWaitMs : DEFAULT_MAX_WAIT_MS;
const { done, events } = await waitForIdle(sessionId, maxWait);
const sum = summarizeEvents(events);
return {
success: true,
runtime: 'managed',
sessionId,
finished: done,
status: sum.status,
stopReason: sum.stopReason,
assistantText: sum.assistantText,
toolUses: sum.toolUses,
eventCount: sum.eventCount,
note: done
? undefined
: `Session still running after ${maxWait}ms — call managed_agent_events({sessionId}) to keep watching, or managed_agent_prompt again to steer.`,
};
},
},
{
name: 'managed_agent_status',
description:
'Get the lifecycle state of a managed cloud-agent session: idle/running/error, title, last error. Use when native conversation memory is wrong because you need the cloud session\'s server-side status across turns rather than guessing. For a local WASM agent use wasm_agent_list. Pair with managed_agent_events for the full transcript.',
category: 'agent',
inputSchema: { type: 'object', properties: { sessionId: { type: 'string', description: 'Session id' } }, required: ['sessionId'] },
handler: async (input) => {
if (!apiKey()) return { ...NEEDS_KEY };
const sessionId = String(input.sessionId ?? '');
if (!sessionId) return { error: 'sessionId is required' };
const r = await maRequest<MaSession>('GET', `/sessions/${encodeURIComponent(sessionId)}`);
if (!r.ok) return { sessionId, error: r.error };
return { runtime: 'managed', sessionId: r.data.id, status: r.data.status, title: r.data.title, error: r.data.error ?? null, environmentId: r.data.environment_id };
},
},
{
name: 'managed_agent_events',
description:
'Fetch the full server-persisted event log of a managed cloud-agent session (user turns, agent thinking, tool_use, tool_result, status) — the transcript/artifact view, the CLOUD counterpart of wasm_agent_files. Use when native Read is wrong because the work happened in Anthropic\'s cloud container, not on disk. For a local WASM agent\'s filesystem use wasm_agent_files. Returns the events plus a summary (assistantText, toolUses).',
category: 'agent',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Session id' },
raw: { type: 'boolean', description: 'Include the full raw event objects (default: summary + compact list)' },
},
required: ['sessionId'],
},
handler: async (input) => {
if (!apiKey()) return { ...NEEDS_KEY };
const sessionId = String(input.sessionId ?? '');
if (!sessionId) return { error: 'sessionId is required' };
const events = await fetchSessionEvents(sessionId);
const sum = summarizeEvents(events);
const compact = events.map(e => {
if (e.type === 'agent.message') return { type: e.type, text: (e.content ?? []).filter(b => b.type === 'text').map(b => b.text ?? '').join('').slice(0, 500) };
if (e.type === 'agent.tool_use') return { type: e.type, name: e.name, input: e.input };
if (e.type === 'agent.tool_result') return { type: e.type, content: JSON.stringify(e.content ?? {}).slice(0, 500) };
return { type: e.type, ...(e.stop_reason ? { stop_reason: e.stop_reason } : {}) };
});
return {
runtime: 'managed',
sessionId,
status: sum.status,
stopReason: sum.stopReason,
assistantText: sum.assistantText,
toolUses: sum.toolUses,
eventCount: sum.eventCount,
events: input.raw ? events : compact,
};
},
},
{
name: 'managed_agent_list',
description:
'List managed cloud-agent sessions on this Anthropic org (id, status, title) — the CLOUD counterpart of wasm_agent_list. Use when native conversation memory is wrong because you need to see which cloud sessions exist (and which are still running / billing) across turns. For local WASM agents use wasm_agent_list. Pair with managed_agent_terminate to clean up idle sessions.',
category: 'agent',
inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Max sessions to return (default 50)' } } },
handler: async (input) => {
if (!apiKey()) return { ...NEEDS_KEY, sessions: [], total: 0 };
const limit = typeof input.limit === 'number' && input.limit > 0 ? Math.min(Math.floor(input.limit), 200) : 50;
const r = await maRequest<{ data?: MaSession[]; sessions?: MaSession[] }>('GET', `/sessions?limit=${limit}`);
if (!r.ok) return { sessions: [], total: 0, error: r.error };
const list = (r.data.data ?? r.data.sessions ?? []) as MaSession[];
return {
runtime: 'managed',
sessions: list.map(s => ({ sessionId: s.id, status: s.status, title: s.title, environmentId: s.environment_id })),
total: list.length,
};
},
},
{
name: 'managed_agent_terminate',
description:
'Delete a managed cloud-agent session (stops billing for it) — the CLOUD counterpart of wasm_agent_terminate. Use when native nothing applies because a cloud session keeps billing container time + tokens until deleted. For a local WASM agent use wasm_agent_terminate. Optionally also deletes the session\'s environment. Always call this when done with a managed agent.',
category: 'agent',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Session id to delete' },
environmentId: { type: 'string', description: 'Optional: also delete this environment (the one returned by managed_agent_create)' },
},
required: ['sessionId'],
},
handler: async (input) => {
if (!apiKey()) return { success: false, ...NEEDS_KEY };
const sessionId = String(input.sessionId ?? '');
if (!sessionId) return { success: false, error: 'sessionId is required' };
const s = await maRequest('DELETE', `/sessions/${encodeURIComponent(sessionId)}`);
const result: Record<string, unknown> = { runtime: 'managed', sessionId, sessionDeleted: s.ok };
if (!s.ok) result.error = s.error;
if (typeof input.environmentId === 'string' && input.environmentId) {
const e = await maRequest('DELETE', `/environments/${encodeURIComponent(String(input.environmentId))}`);
result.environmentDeleted = e.ok;
if (!e.ok) result.environmentError = e.error;
}
result.success = s.ok;
return result;
},
},
];
@@ -0,0 +1,155 @@
# ADR-115 — Claude Managed Agents as a cloud backend for `rvagent`
**Status**: Accepted (2026-05-12) — implemented as the `managed_agent_*` MCP tools in the `ruflo-agent` plugin (see Implementation below)
**Date**: 2026-05-12
**Authors**: claude (drafted with rUv)
**Related**: `ruflo-agent` plugin (renamed from `ruflo-wasm`) / `wasm_agent_*` MCP tools (`@ruvector/rvagent-wasm`) · ADR-026 (3-tier model routing) · ADR-095 G2 / ADR-104 (pluggable `ConsensusTransport` / `FederationTransport`) · ADR-097 (federation peers) · ADR-112 (MCP tool discoverability) · [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)
**Supersedes**: nothing
## Context
Ruflo's `rvagent` capability — the `ruflo-agent` plugin, wrapping `@ruvector/rvagent-wasm` — is a *local, WASM-sandboxed* agent harness. Its MCP surface:
| `rvagent` tool | What it does |
|---|---|
| `wasm_agent_create` | spin up a WASM-sandboxed agent (its own filesystem, tools) |
| `wasm_agent_prompt` | send the agent a user turn |
| `wasm_agent_tool` | invoke a tool inside the sandbox |
| `wasm_agent_files` | read the sandbox filesystem |
| `wasm_agent_export` | export the session as an RVF container (portable, replayable) |
| `wasm_agent_terminate` | stop the agent |
| `wasm_gallery_*` | publish / discover reusable agent templates |
[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) (Anthropic, beta — `anthropic-beta: managed-agents-2026-04-01`) is a *cloud, Anthropic-managed* agent harness with the **same conceptual model**:
| Managed Agents concept | API |
|---|---|
| **Agent** — model + system + tools + MCP servers + skills | `POST /v1/agents` → `{id, version}` (reusable by id, versioned) |
| **Environment** — container template (apt/pip/npm/cargo/gem/go packages, networking, init script, env vars) | `POST /v1/environments` → `{id}` |
| **Session** — a running agent instance in an environment | `POST /v1/sessions` `{agent, environment_id, title}` → `{id, status}` |
| **Events** — user turns / tool use / tool results / status, SSE-streamed and persisted | `POST /v1/sessions/{id}/events`, `GET /v1/sessions/{id}/stream` (SSE), `GET /v1/sessions/{id}/events` (full history) |
| Built-in toolset (`agent_toolset_20260401`): bash, file ops (read/write/edit/glob/grep), web search/fetch, MCP servers | per-tool config + `permission_policy` |
It also exposes `mcp_servers`, `skills`, `multiagent` (research preview), and **define-outcomes** (research preview) on the agent config — i.e. Anthropic is converging on the same primitives ruflo already has (agents, MCP servers, skills, multi-agent swarms, success criteria).
The mapping to `rvagent` is essentially 1:1:
| `rvagent` (WASM, local) | Managed Agents (cloud) |
|---|---|
| `wasm_agent_create` (sandbox) | `agents.create` + `environments.create` + `sessions.create` |
| `wasm_agent_prompt` | `events.send(user.message)` + `events.stream` |
| (tools run inside the WASM sandbox) | the agent autonomously calls `agent_toolset_20260401` tools in the cloud container |
| `wasm_agent_files` | bash/file tools in the container; full event history is `GET /sessions/{id}/events` |
| `wasm_agent_export` (RVF container) | session + event history is persisted server-side; export = the event log |
| `wasm_agent_terminate` | `DELETE /v1/sessions/{id}` (or the session goes idle) |
| WASM sandbox (local, no network unless granted) | cloud container (`environments`, networking rules) |
| `wasm_gallery_*` (share agent templates) | agents are reusable-by-id; a "gallery" entry = the agent-config blob |
### Validation (this exploration)
A live smoke against `https://api.anthropic.com/v1` with the org's Anthropic key (sourced from the GCP `claude-flow/anthropic-api-key` secret) confirmed the flow end-to-end:
```
POST /v1/agents {name, model:"claude-haiku-4-5-20251001", system, tools:[{type:"agent_toolset_20260401"}]} → agent_… (version 1)
POST /v1/environments {name, config:{type:"cloud", networking:{type:"unrestricted"}}} → env_…
POST /v1/sessions {agent, environment_id, title} → sesn_… (status:"idle")
POST /v1/sessions/{id}/events {events:[{type:"user.message", content:[{type:"text", text:"echo … > /tmp/x && cat /tmp/x"}]}]}
GET /v1/sessions/{id}/events → 13 events: session.status_running → user.message → agent.thinking → agent.tool_use(bash) →
agent.tool_result("ruflo-managed-agents-smoke OK\n") → agent.message("Done.") → session.status_idle (stop_reason:end_turn)
DELETE /v1/sessions/{id}, DELETE /v1/environments/{id} (cleanup)
```
Roughly ~7 s wall-clock for a trivial bash task on Haiku. The container provisioned, ran the agent loop, executed the tool, and went idle without intervention. Headers required on every request: `x-api-key`, `anthropic-version: 2023-06-01`, `anthropic-beta: managed-agents-2026-04-01`. The SSE stream returns nothing if you attach it *after* a fast session already went idle — `GET /sessions/{id}/events` is the reliable transcript; attach the stream before/while sending the user event for real-time.
## Decision
**Treat `rvagent` as the *interface* and add Claude Managed Agents as a second *backend* behind it — a `runtime: "managed"` (vs the default `"wasm"`) on the `wasm_agent_*` MCP tools** (and/or a thin `managed_agent_*` alias toolset), wrapping the Managed Agents REST API via `@anthropic-ai/sdk`'s `client.beta.agents|environments|sessions` (which sets the beta header automatically). This mirrors the established ruflo pattern of one interface over a local-vs-cloud transport (`ConsensusTransport`: `LocalTransport` | `FederationTransport` — ADR-095 G2 / ADR-104).
### Surface
- `wasm_agent_create({ runtime: "managed", model?, system?, mcpServers?, skills?, env? })` — creates (or reuses) a Managed Agent + environment + session; returns a handle that looks like a WASM-agent handle (`agentId` ↔ session id, plus the underlying `agent`/`environment` ids) so downstream tools don't care which backend it is.
- `wasm_agent_prompt({ agentId, runtime: "managed", message })` — `events.send(user.message)` then drain `events.stream` (or poll `events`) until `session.status_idle`; returns the assistant text + a tool-use trace.
- `wasm_agent_files({ agentId, runtime: "managed" })` — fetch the session's file artifacts / event history.
- `wasm_agent_terminate({ agentId, runtime: "managed" })` — `DELETE /v1/sessions/{id}` (and optionally the env).
- `wasm_gallery_*` — store/retrieve Managed-Agent **config blobs** (`{model, system, tools, mcp_servers, skills}`) alongside the existing WASM-agent templates; a gallery entry that targets `runtime: "managed"` materializes a real Managed Agent on use.
### The high-value combinations
1. **Ruflo's own MCP server as a Managed Agent tool source.** Managed Agents' agent config accepts `mcp_servers`. A ruflo Managed Agent can be wired to `npx ruflo mcp start` (HTTP transport) → the cloud agent gets ruflo's 314 MCP tools (memory, swarm, hooks, agentdb, federation, …) running in the cloud container. That is: a cloud-hosted ruflo agent, no local infra.
2. **Managed Agents as a federation peer (ADR-097/104).** A Managed Agent session is, operationally, a remote autonomous executor with persisted state — the same shape as a federated peer. The `FederationTransport` / `task_assign` dispatch could target a Managed Agent session as one of its executors (closing the #1916 "task-execution dispatch" gap with a *third* executor option: local WASM | federated peer | Anthropic Managed).
3. **Long-running / async work.** `rvagent`-WASM is in-process and ephemeral; Managed Agents persist a filesystem + event history across interactions and run for minutes/hours — the right backend for long agent tasks where the WASM sandbox isn't.
4. **Skills + outcomes parity.** Managed Agents accept ruflo's skills (Markdown skill files) and (research-preview) `define-outcomes` — a Managed-Agent backed `rvagent` can carry the same skill set and success criteria a local one does.
### Constraints / boundaries
- **Optional, off by default.** `runtime: "wasm"` stays the default; `runtime: "managed"` requires `ANTHROPIC_API_KEY` (or the GCP `anthropic-api-key` secret in CI/ops) and the beta header — degrade gracefully (`{ error: "managed runtime needs ANTHROPIC_API_KEY + managed-agents beta access" }`) when absent. No core package gains a hard dependency on `@anthropic-ai/sdk` for this — it's a plugin-level dep (`ruflo-agent` / a new `ruflo-managed-agents`), lazily imported.
- **Cost & rate limits.** Managed Agents bill per session (LM tokens + container time) and are rate-limited per org (create: 300/min, read: 600/min) plus tier spend limits. The tool descriptions must say so (ADR-112), and `cost-tracking` should record Managed-Agent sessions the same way it records LM calls. `wasm_agent_prompt({runtime:"managed"})` should surface an estimated cost before a long run.
- **Beta churn.** The API is beta (`managed-agents-2026-04-01`); `multiagent` and `define-outcomes` are research preview. Pin the beta header in one place; treat the latter two as feature-flagged.
- **Branding.** Anthropic's branding guidelines forbid presenting a Managed-Agents integration as "Claude Code"/"Claude Cowork" — ruflo keeps its own branding; surface it as "ruflo agent (Anthropic Managed runtime)" not "Claude Code agent".
- **Cleanup discipline.** A Managed Agent leaves an `agent` + `environment` + `session` on the org account; `wasm_agent_terminate` must delete the session (and env, if ruflo created it), and there should be a `managed_agent_gc` / doctor check for orphaned sessions.
This ADR records the *intent to build*; the plugin's internals (exact handle shape, stream-vs-poll, gallery storage, federation wiring) are a follow-up implementation PR.
### Future: a third runtime — the Claude Agent SDK (`@anthropic-ai/claude-agent-sdk`)
This ADR adds the *cloud* runtime (Managed Agents). There's a natural *third* runtime that completes the spectrum: the **[Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview)** (`@anthropic-ai/claude-agent-sdk` for TS, `claude-agent-sdk` for Python) — "Claude Code as a library": `query({prompt, options})` / `ClaudeSDKClient`, with built-in tools (Read/Write/Edit/Bash/Glob/Grep/WebSearch/WebFetch/Monitor/AskUserQuestion), hooks (`PreToolUse`/`PostToolUse`/`Stop`/`SessionStart`/…), subagents (`agents` option + the `Agent` tool), MCP servers (`mcpServers` option), permission modes (`permissionMode`/`allowedTools`), sessions (resume/fork), and it loads Claude Code's filesystem config (`.claude/skills/*/SKILL.md`, `.claude/commands/*.md`, `CLAUDE.md`, plugins). It bundles a native Claude Code binary. **Runs in your process, on your filesystem** — full host trust.
So `ruflo-agent` would expose three runtimes behind one mental model:
| Runtime | Package | Runs on | Trust | Best for |
|---|---|---|---|---|
| **WASM** (`rvagent`) | `@ruvector/rvagent-wasm` | local WASM sandbox | sandboxed (no host fs/net) | untrusted code; portable/replayable RVF containers; fast, free, offline |
| **SDK** (`sdk_agent_*`, future) | `@anthropic-ai/claude-agent-sdk` | your process / your filesystem | full host trust | a real Claude agent loop (hooks, subagents, MCP, sessions, skills) on the local repo/services — the programmatic, in-process version of `claude -p` |
| **Managed** (`managed_agent_*`, this ADR) | Managed Agents REST API | Anthropic cloud container | cloud-isolated | long-running/async; managed infra; no local setup; persistent server-side session |
Why the SDK runtime is worth adding (a follow-up — likely its own ADR-116):
- It **is** Claude Code's machinery, exposed as a library — and ruflo already loads `.claude/skills`, `.claude/commands`, `CLAUDE.md`, plugins, and shells out to `claude -p`. The SDK is the in-process version: full control over the message stream, hooks (ruflo's own hooks), subagents (ruflo's 16 roles), MCP servers, permission modes, sessions.
- The "give the agent ruflo's 314 tools" combo is **trivial** here (unlike Managed Agents, which needs a publicly reachable URL): pass `mcpServers: { ruflo: { command: "npx", args: ["ruflo", "mcp", "start"] } }` — a *local* stdio MCP server, no deployment.
- Anthropic positions it as "prototype with the Agent SDK locally → move to Managed Agents for production" — having both in `ruflo-agent` gives ruflo exactly that path.
Costs/constraints (so it stays opt-in, plugin-level, off by default — `wasm_agent_*` remains the safe default): a real, heavy dependency (bundles a Claude Code binary as an optional dep); **full host trust, no sandbox** — the least-isolated of the three (a WASM agent can't touch your fs; an SDK agent can); needs `ANTHROPIC_API_KEY` (or Bedrock/Vertex/Azure creds). Branding rules (not "Claude Code").
## Consequences
### Positive
- **One mental model, two runtimes.** Authors think "rvagent"; ruflo picks local WASM (fast, free, ephemeral, no network) or Anthropic Managed (cloud, persistent, long-running, no local infra) per task — like it already picks `LocalTransport` vs `FederationTransport`.
- **Closes a real gap.** A cloud autonomous executor with persisted state and a full toolset is exactly what `task_assign` / hive workers (#1916 items 3/4) and long-running federation tasks need; Managed Agents is a turnkey option.
- **Ruflo-tools-in-the-cloud.** Wiring `npx ruflo mcp start` as a Managed Agent's MCP server gives a cloud-hosted ruflo with zero local setup — a strong onboarding/demo story and a real ops capability.
- **First-party, low integration cost.** It's Anthropic's own API; `@anthropic-ai/sdk` is already in the dep tree somewhere; the conceptual mapping is 1:1 so the adapter is thin. Validated working today.
- **Convergent primitives.** Managed Agents' agent/environment/session/events/skills/mcp_servers/multiagent/outcomes are the same primitives ruflo has — the integration is "speak the same nouns to a different harness", not an impedance mismatch.
### Negative
- **Cost surface.** Easy to rack up container-time + token spend; needs cost-tracking integration + pre-run estimates + a GC for orphaned sessions, or it becomes a footgun.
- **Beta exposure.** Wire shapes may shift; `multiagent`/`outcomes` are research-preview (gated). Maintenance burden until GA.
- **Two backends to keep behaviorally aligned.** `runtime: "wasm"` and `runtime: "managed"` must return compatible handle/result shapes; divergence (e.g. `wasm_agent_files` semantics) is a latent bug source — code review and a parity smoke (run the same trivial task on both runtimes, diff the result shape) are needed.
- **Network/credential dependency.** `managed` runtime can't work offline or without an Anthropic key + beta access; the WASM runtime is the floor.
### Neutral
- Opt-in; users who don't use `managed_agent_*` see no change. Worst case: an unused code path behind an env-var.
- This is "adopt an external standard" (Anthropic's harness), not invent one — same posture as ADR-114 (DSPy.ts).
## Implementation (2026-05-12)
Landed as a **separate `managed_agent_*` toolset** (rather than a `runtime:` flag on the existing `wasm_agent_*` tools — cleaner, zero risk to the WASM path; both toolsets now live in the renamed `ruflo-agent` plugin):
- `plugins/ruflo-wasm/` → **`plugins/ruflo-agent/`** (`git mv`; `plugin.json` name/description/keywords now span both runtimes; `.claude-plugin/marketplace.json` entry + READMEs + `discover-plugins` skill + `verification/inventory.json` updated). The `wasm_agent_*` / `wasm_gallery_*` tool *names* are unchanged.
- **`v3/@claude-flow/cli/src/mcp-tools/managed-agent-tools.ts`** — 6 new MCP tools, registered in `mcp-client.ts`, wrapping the Managed Agents REST API with plain `fetch` (no new SDK dep): `managed_agent_create` (`agents.create` + `environments.create` + `sessions.create`), `managed_agent_prompt` (`events.send(user.message)` + poll `GET /events` until a terminal `session.status_*`, default 180 s / cap 600 s), `managed_agent_status`, `managed_agent_events` (full transcript + a summary), `managed_agent_list`, `managed_agent_terminate` (`DELETE /sessions/{id}` ± the env). Key from `ANTHROPIC_API_KEY` / `CLAUDE_API_KEY`; every tool degrades gracefully with a structured "use `wasm_agent_create` for a local no-key runtime" error when absent. ADR-112-compliant descriptions (each names when `wasm_agent_*` / native is the right call instead). `mcpServers` / `skills` / `packages` / `networking` / `initScript` pass through to the agent/environment config.
- **`plugins/ruflo-agent/skills/managed-agent/SKILL.md`** + **`commands/managed-agent.md`** — the cloud-runtime skill/command (the WASM-vs-managed decision table, the key prereq + fallback, the `mcpServers`-reachability caveat, the cost/cleanup discipline).
- **`plugins/ruflo-agent/scripts/smoke.sh`** + **`.github/workflows/ruflo-agent-smoke.yml`** — structural CI guard (12 checks now: manifest is `ruflo-agent` with both-runtime keywords; all 10 `wasm_*` + all 6 `managed_agent_*` referenced; managed-agent skill keeps an explicit `allowed-tools` + offers the `wasm_agent_create` fallback; ADR cross-refs). Also wired into the `audit-cli-mcp-tools` / `audit-tool-descriptions` guards (311 registered / 0 dangling / 0 no-guidance).
**Validated live** against `api.anthropic.com` with the org's Anthropic key (GCP `claude-flow/anthropic-api-key`), via `ruflo mcp exec -t managed_agent_*`: `create` (haiku, custom system) → `{sessionId, agentId, environmentId, status:"idle", model:"claude-haiku-4-5-20251001"}`; `prompt` ("echo … > /tmp/r.txt && cat …") → `{finished:true, status:"idle", stopReason:"end_turn", assistantText:"Done.", toolUses:[{name:"bash", input:{command:"echo … && cat …"}}], eventCount:13}`; `terminate` → `{sessionDeleted:true, environmentDeleted:true}`. No-key path returns the graceful error. `claude --plugin-dir plugins/ruflo-agent` loads the plugin and lists its skills (`managed-agent`, `wasm-agent`, `wasm-gallery`). `smoke.sh` 12/0; cli build clean.
**Not yet done (follow-ups, called out above):** `runtime:"managed"` as a flag on `wasm_agent_*` (unification); a deployed/tunneled HTTP ruflo MCP server so the cloud agent gets ruflo's 314 tools (the combo needs a reachable URL); `task_assign` / federation dispatch to a Managed Agent session (#1916 items 3/4 — a third executor); cost-tracking integration + a `doctor`/GC check for orphaned sessions; `multiagent` / `define-outcomes` (research-preview, feature-flagged).
## Links
- Claude Managed Agents — overview: https://platform.claude.com/docs/en/managed-agents/overview · quickstart: https://platform.claude.com/docs/en/managed-agents/quickstart · sessions API: https://platform.claude.com/docs/en/managed-agents/sessions · tools: https://platform.claude.com/docs/en/managed-agents/tools
- `@ruvector/rvagent-wasm` (the `rvagent` backend) — dep of `@claude-flow/cli`; surfaced via the `ruflo-agent` plugin's `wasm_agent_*` MCP tools
- ADR-095 G2 / ADR-104 — pluggable `ConsensusTransport` / `FederationTransport` (the local-vs-cloud pattern this reuses)
- ADR-097 — federation peers (a Managed Agent session ≈ a federated executor)
- ADR-026 — 3-tier model routing (Managed Agent `model` selection should route through this)
- ADR-112 — MCP tool discoverability (every new `*_agent_*({runtime:"managed"})` tool description must comply; must state the cost/beta caveats)
- #1916 — hive task-execution dispatch (Managed Agents as a third executor option)
+4 -4
View File
@@ -1955,10 +1955,10 @@
"sourceFile": "plugins\\ruflo-testgen\\.claude-plugin\\plugin.json"
},
{
"name": "ruflo-wasm",
"name": "ruflo-agent",
"version": "0.2.0",
"description": "Sandboxed WASM agent creation, execution, and gallery sharing — wraps 10 wasm_* MCP tools (agent_create/prompt/tool/list/terminate/files/export + gallery_list/search/create); built on @ruvector/rvagent-wasm + @ruvector/ruvllm-wasm per ADR-070 (Implemented)",
"sourceFile": "plugins\\ruflo-wasm\\.claude-plugin\\plugin.json"
"sourceFile": "plugins\\ruflo-agent\\.claude-plugin\\plugin.json"
},
{
"name": "ruflo-workflows",
@@ -2217,8 +2217,8 @@
{
"name": "wasm-specialist",
"description": "WASM sandbox specialist for creating, managing, and sharing isolated agent environments",
"plugin": "ruflo-wasm",
"sourceFile": "plugins\\ruflo-wasm\\agents\\wasm-specialist.md"
"plugin": "ruflo-agent",
"sourceFile": "plugins\\ruflo-agent\\agents\\wasm-specialist.md"
},
{
"name": "witness-auditor",