diff --git a/.gitignore b/.gitignore
index 40e9ed5..bfaecbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -88,6 +88,7 @@ celerybeat-schedule
.venv
env/
venv/
+venv*/
run/
ENV/
env.bak/
diff --git a/README.md b/README.md
index ceeb322..a9fad9b 100644
--- a/README.md
+++ b/README.md
@@ -98,13 +98,22 @@
π News
-
We have published RunAgent Pulse
-
+
Introducing OpenClaw, PicoClaw, and ZeroClaw
+
+
+
+
+
+
+
+
+- **[2026.03] π [Architecture]** Introducing **OpenClaw**, **PicoClaw** (Go), and **ZeroClaw** (Rust): one unified engine that runs agents both **serverless** and as **24/7 always-on services**.
+- **[2026.03] π [Milestone]** The first architecture to support **persistent + serverless agent execution in the same engine**, with a single codebase powering both modes.
+
- **[2025.12] π― [New Product]** Published **RunAgent Pulse β Scheduling & Orchestration**, a self-hosted βGoogle Calendar for your AI agentsβ.
-- **[2025.12] π― [Integration]** Integrated the **PaperFlow** arXiv research agent with **RunAgent Serverless** and **RunAgent Pulse** for end-to-end scheduled arXiv monitoring and email notifications.
## What is RunAgent-Pulse?
diff --git a/docs/logo/Openclaw-logo-text-dark.png b/docs/logo/Openclaw-logo-text-dark.png
new file mode 100644
index 0000000..b14e423
Binary files /dev/null and b/docs/logo/Openclaw-logo-text-dark.png differ
diff --git a/docs/logo/openclaw logo.png b/docs/logo/openclaw logo.png
new file mode 100644
index 0000000..c27e8b4
Binary files /dev/null and b/docs/logo/openclaw logo.png differ
diff --git a/docs/logo/openclaw-white.jpg b/docs/logo/openclaw-white.jpg
new file mode 100644
index 0000000..cb5a86b
Binary files /dev/null and b/docs/logo/openclaw-white.jpg differ
diff --git a/docs/logo/picoclaw.png b/docs/logo/picoclaw.png
new file mode 100644
index 0000000..a93732d
Binary files /dev/null and b/docs/logo/picoclaw.png differ
diff --git a/docs/logo/zeroclaw.png b/docs/logo/zeroclaw.png
new file mode 100644
index 0000000..958144c
Binary files /dev/null and b/docs/logo/zeroclaw.png differ
diff --git a/examples/trip_planner/agent/runagent.config.json b/examples/trip_planner/agent/runagent.config.json
index 34012c5..02e0340 100644
--- a/examples/trip_planner/agent/runagent.config.json
+++ b/examples/trip_planner/agent/runagent.config.json
@@ -25,7 +25,7 @@
]
},
"env_vars": {},
- "agent_id": "7ca76f5d-9524-4614-b283-d9a50db147b8",
+ "agent_id": "7cc76f5d-9524-4614-b283-d9a50db137b8",
"auth_settings": {
"type": "api_key"
},
diff --git a/examples/trip_planner/sdk/python/test.py b/examples/trip_planner/sdk/python/test.py
index 48a2bf9..569411a 100644
--- a/examples/trip_planner/sdk/python/test.py
+++ b/examples/trip_planner/sdk/python/test.py
@@ -2,12 +2,16 @@
Minimal RunAgent SDK Test for TripGenius
Update AGENT_ID and run!
+
+Run from repo root with venv activated:
+ pip install -e .
+ python examples/trip_planner/sdk/python/test.py
"""
from runagent import RunAgentClient
# UPDATE THIS!
-AGENT_ID = "7ca76f5d-9524-4614-b283-d9a50db147b8"
+AGENT_ID = "9cc76f5d-9524-4614-b283-d9a50db137b8"
# ============================================
diff --git a/runagent/cli/commands/config.py b/runagent/cli/commands/config.py
index 8b3fad7..dd746fe 100644
--- a/runagent/cli/commands/config.py
+++ b/runagent/cli/commands/config.py
@@ -32,7 +32,7 @@ def format_error_message(error_info):
@click.group(invoke_without_command=True)
@click.option("--set-api-key", help="Set API key directly (e.g., runagent config --set-api-key YOUR_KEY)")
@click.option("--set-base-url", help="Set base URL directly (e.g., runagent config --set-base-url https://api.example.com)")
-@click.option("--register-agent", type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path), help="Register a modified agent in the database")
+@click.option("--register-agent", type=str, help="Register an agent: use a path (e.g., '.') or template shortcut (e.g., 'openclaw/gateway')")
@click.option("--delete-agent", help="Delete an agent by ID")
@click.pass_context
def config(ctx, set_api_key, set_base_url, register_agent, delete_agent):
@@ -65,8 +65,10 @@ def config(ctx, set_api_key, set_base_url, register_agent, delete_agent):
return
if register_agent:
- from .register import _register_agent_core
- return _register_agent_core(register_agent)
+ from .register import _register_agent_core, _resolve_template_path_for_registration
+ # Resolve path or template shortcut (e.g. "agno/default") to a Path
+ resolved_path, _ = _resolve_template_path_for_registration(register_agent)
+ return _register_agent_core(resolved_path)
if delete_agent:
from .delete import delete as delete_func
diff --git a/runagent/cli/commands/deploy.py b/runagent/cli/commands/deploy.py
index 12bc4f3..843b61f 100644
--- a/runagent/cli/commands/deploy.py
+++ b/runagent/cli/commands/deploy.py
@@ -2,6 +2,8 @@
CLI commands that use the restructured SDK internally.
"""
import os
+import tempfile
+from typing import Optional, Tuple
from pathlib import Path
@@ -11,7 +13,9 @@
from runagent import RunAgentSDK
from runagent.sdk.exceptions import ( # RunAgentError,; ConnectionError
AuthenticationError,
+ TemplateError,
)
+from runagent.constants import TEMPLATE_PREPATH
console = Console()
@@ -31,22 +35,126 @@ def format_error_message(error_info):
# ============================================================================
+def _resolve_template_path(template_path: str) -> Tuple[Path, Optional[Path]]:
+ """
+ Resolve template path - supports both:
+ - Direct paths: "templates/openclaw/gateway" or "./my-agent"
+ - Template shortcuts: "openclaw/gateway" (resolves to templates/openclaw/gateway)
+
+ Returns:
+ Tuple of (resolved_path, temp_dir_for_cleanup)
+ temp_dir_for_cleanup is None if path is local (not downloaded)
+ """
+ # Support single-token aliases before path resolution
+ # e.g. "picoclaw" -> "picoclaw/gateway"
+ if template_path == "picoclaw":
+ template_path = "picoclaw/gateway"
+ if template_path == "zeroclaw":
+ template_path = "zeroclaw/gateway"
+
+ path = Path(template_path)
+
+ # If path exists, use it directly
+ if path.exists():
+ return path.resolve(), None
+
+ # Check if it looks like a template shortcut (framework/template format)
+ parts = template_path.split("/")
+ if len(parts) == 2 and "/" in template_path:
+ framework, template = parts
+
+ # First, check if local template exists
+ # Try multiple locations:
+ # 1. Current working directory (if in runagent source repo)
+ # 2. Parent of runagent package (if templates are alongside package)
+ # 3. Relative to runagent package location
+ possible_paths = [
+ Path.cwd() / TEMPLATE_PREPATH / framework / template, # Current dir
+ Path.cwd() / "templates" / framework / template, # Current dir with "templates"
+ ]
+
+ # Also check relative to runagent package if it's installed
+ try:
+ import runagent
+ runagent_package_dir = Path(runagent.__file__).parent.parent
+ possible_paths.extend([
+ runagent_package_dir / TEMPLATE_PREPATH / framework / template,
+ runagent_package_dir / "templates" / framework / template,
+ ])
+ except (ImportError, AttributeError):
+ pass
+
+ for local_template_path in possible_paths:
+ if local_template_path.exists():
+ console.print(f"[dim]Using local template: [cyan]{framework}/{template}[/cyan][/dim]")
+ return local_template_path.resolve(), None
+
+ # If local doesn't exist, try to download from remote
+ try:
+ sdk = RunAgentSDK()
+ temp_dir = tempfile.mkdtemp(prefix="runagent-deploy-")
+ temp_path = Path(temp_dir) / template
+
+ console.print(f"[dim]Downloading template: [cyan]{framework}/{template}[/cyan][/dim]")
+ sdk.templates.init_template(
+ folder_path=temp_path,
+ framework=framework,
+ template=template,
+ overwrite=True
+ )
+
+ if temp_path.exists():
+ console.print(f"[green]β[/green] Template downloaded to temporary directory")
+ return temp_path.resolve(), Path(temp_dir)
+ else:
+ raise TemplateError(f"Template download failed: {framework}/{template}")
+ except Exception as e:
+ raise click.ClickException(
+ f"Template '{template_path}' not found locally or remotely. "
+ f"Available formats: 'framework/template' (e.g., 'openclaw/gateway') or a local path.\n"
+ f"Error: {e}"
+ )
+
+ # Check if it's templates/framework/template format
+ if template_path.startswith("templates/") or template_path.startswith(f"{TEMPLATE_PREPATH}/"):
+ # Remove templates/ prefix and try as shortcut
+ shortcut = template_path.replace(f"{TEMPLATE_PREPATH}/", "").replace("templates/", "")
+ return _resolve_template_path(shortcut)
+
+ # Path doesn't exist and doesn't look like a template
+ raise click.ClickException(
+ f"Path not found: {path}\n"
+ f"Use a local path or template shortcut like 'openclaw/gateway' or 'picoclaw/gateway'"
+ )
+
+
@click.command()
@click.option("--overwrite", is_flag=True, help="Overwrite existing agent if it already exists")
+@click.option(
+ "--new-id",
+ is_flag=True,
+ help="Do NOT reuse existing agent ID for template shortcuts (e.g. always create a new agent for 'openclaw/gateway')",
+)
@click.argument(
"path",
- type=click.Path(
- exists=True,
- file_okay=False,
- dir_okay=True,
- readable=True,
- resolve_path=True,
- path_type=Path,
- ),
+ type=str,
default=".",
)
-def deploy(path: Path, overwrite: bool):
- """Deploy agent (upload + start) to remote server"""
+def deploy(path: str, overwrite: bool, new_id: bool):
+ """
+ Deploy agent (upload + start) to remote server.
+
+ PATH can be:
+ - A local folder: "./my-agent" or "/path/to/agent"
+ - A template shortcut: "openclaw/gateway" or "openclaw/mcp"
+ - A template path: "templates/openclaw/gateway"
+
+ Examples:
+ runagent deploy . # Deploy current directory
+ runagent deploy openclaw/gateway # Deploy OpenClaw gateway template
+ runagent deploy openclaw/mcp # Deploy OpenClaw MCP template
+ runagent deploy templates/openclaw/gateway # Same as above
+ """
try:
from runagent.cli.branding import print_header
@@ -61,15 +169,166 @@ def deploy(path: Path, overwrite: bool):
)
raise click.ClickException("Authentication required")
- # Validate folder
- if not Path(path).exists():
- raise click.ClickException(f"Folder not found: {path}")
+ # Resolve template path (downloads template if needed)
+ try:
+ resolved_path, temp_dir_to_cleanup = _resolve_template_path(path)
+ except click.ClickException:
+ raise
+ except Exception as e:
+ raise click.ClickException(f"Failed to resolve path '{path}': {e}")
console.print(f"[bold]Deploying agent (upload + start)...[/bold]")
- console.print(f"Source: [cyan]{path}[/cyan]")
+ console.print(f"Source: [cyan]{resolved_path}[/cyan]")
+
+ # Check if agent needs registration (has null UUID or not registered)
+ from runagent.utils.agent import get_agent_config
+ from runagent.utils.agent_id import generate_agent_id
+ from runagent.sdk.db import DBService
+ from runagent.constants import LOCAL_CACHE_DIRECTORY
+ import json
+ import shutil
+
+ NULL_UUID = "00000000-0000-0000-0000-000000000000"
+ config_path = resolved_path / "runagent.config.json"
+ needs_registration = False
+ working_path = resolved_path
+ working_temp_dir = None # Track if we created a working copy
+
+ # Check if this is a template shortcut (e.g., "openclaw/gateway")
+ is_template_shortcut = "/" in path and not Path(path).exists()
+ reuse_agent_id = None
+
+ # Only try to reuse existing agent ID when *not* forcing a new one
+ if is_template_shortcut and not new_id:
+ # Check if we've deployed this template before by looking in database
+ try:
+ agent_config = get_agent_config(resolved_path)
+ template_name = agent_config.get('template') if isinstance(agent_config, dict) else getattr(agent_config, 'template', None)
+ agent_name = agent_config.get('agent_name') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_name', None)
+
+ if template_name or agent_name:
+ db_service = DBService()
+ from runagent.sdk.db import Agent
+ from sqlalchemy import or_
+ with db_service.db_manager.get_session() as session:
+ query = session.query(Agent)
+ conditions = []
+ if template_name:
+ conditions.append(Agent.template == template_name)
+ if agent_name:
+ conditions.append(Agent.agent_name == agent_name)
+ if conditions:
+ query = query.filter(or_(*conditions))
+ existing_agents = query.order_by(Agent.created_at.desc()).all()
+
+ if existing_agents:
+ # Use the most recently created agent with this template
+ reuse_agent_id = existing_agents[0].agent_id
+ console.print(f"[green]β[/green] Reusing existing agent ID: [cyan]{reuse_agent_id}[/cyan]")
+ console.print(f"[dim]Template '{path}' was previously deployed[/dim]")
+ except Exception as e:
+ # Silently continue - will generate new UUID
+ pass
+
+ # If deploying from a template, we may need to create a working copy
+ if config_path.exists():
+ try:
+ agent_config = get_agent_config(resolved_path)
+ agent_id = agent_config.get('agent_id') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_id', None)
+
+ # Check if agent_id is null UUID or missing
+ if not agent_id or agent_id == NULL_UUID:
+ # If we found an existing agent_id for this template shortcut, reuse it
+ if reuse_agent_id:
+ new_agent_id = reuse_agent_id
+ needs_registration = False # Already registered
+ else:
+ needs_registration = True
+ new_agent_id = generate_agent_id()
+ console.print(f"[dim]Agent has null UUID, generating new ID[/dim]")
+
+ # If this is a local template (not a temp download), create a working copy
+ # to avoid modifying the original template
+ if temp_dir_to_cleanup is None:
+ # Create a temporary working copy to avoid modifying the template
+ import tempfile
+ working_temp_dir = Path(tempfile.mkdtemp(prefix="runagent-deploy-working-"))
+ working_path = working_temp_dir / resolved_path.name
+ shutil.copytree(resolved_path, working_path)
+ console.print(f"[dim]Created working copy: [cyan]{working_path}[/cyan][/dim]")
+ config_path = working_path / "runagent.config.json"
+ # else: temp_dir_to_cleanup exists, so resolved_path is already a temp copy
+ # and we can safely modify it
+
+ # Update config with agent ID (either reused or new)
+ with config_path.open('r') as f:
+ config_data = json.load(f)
+ config_data['agent_id'] = new_agent_id
+ with config_path.open('w') as f:
+ json.dump(config_data, f, indent=2)
+
+ if reuse_agent_id:
+ console.print(f"[green]β[/green] Using existing agent ID: [cyan]{new_agent_id}[/cyan]")
+ else:
+ console.print(f"[green]β[/green] Generated agent ID: [cyan]{new_agent_id}[/cyan]")
+
+ else:
+ # Check if agent is registered in database
+ db_service = DBService()
+ existing_agent = db_service.get_agent(agent_id)
+ if not existing_agent:
+ needs_registration = True
+ console.print(f"[dim]Agent {agent_id} not registered, will register now[/dim]")
+ except Exception as e:
+ console.print(f"[yellow]β [/yellow] Could not check agent registration: {e}")
+ # Continue with deployment anyway
+
+ # Register agent if needed
+ if needs_registration:
+ try:
+ from .register import _register_agent_core
+ console.print(f"[dim]Registering agent...[/dim]")
+ _register_agent_core(working_path)
+ except Exception as e:
+ console.print(f"[yellow]β [/yellow] Registration warning: {e}")
+ # Continue with deployment anyway - remote might handle it
+
+ # If we're reusing an agent ID, update the agent path in database
+ # (since each deployment creates a new temporary working copy)
+ if reuse_agent_id and not needs_registration:
+ try:
+ db_service = DBService()
+ # Directly update agent_path in database
+ from runagent.sdk.db import Agent
+ with db_service.db_manager.get_session() as session:
+ agent = session.query(Agent).filter(Agent.agent_id == reuse_agent_id).first()
+ if agent:
+ agent.agent_path = str(working_path)
+ session.commit()
+ console.print(f"[dim]Updated agent path in database[/dim]")
+ except Exception as e:
+ # Silently continue - path update is not critical
+ pass
- # Deploy agent (framework auto-detected)
- result = sdk.deploy_remote(folder=str(path), overwrite=overwrite)
+ try:
+ # Deploy agent (framework auto-detected)
+ result = sdk.deploy_remote(folder=str(working_path), overwrite=overwrite)
+ finally:
+ # Cleanup temp directories
+ if temp_dir_to_cleanup and temp_dir_to_cleanup.exists():
+ try:
+ shutil.rmtree(temp_dir_to_cleanup)
+ console.print(f"[dim]Cleaned up temporary template directory[/dim]")
+ except Exception:
+ pass # Ignore cleanup errors
+
+ # Cleanup working copy if we created one
+ if working_temp_dir is not None and working_temp_dir.exists():
+ try:
+ shutil.rmtree(working_temp_dir)
+ console.print(f"[dim]Cleaned up working copy[/dim]")
+ except Exception:
+ pass # Ignore cleanup errors
if result.get("success"):
agent_id = result.get('agent_id')
@@ -78,6 +337,142 @@ def deploy(path: Path, overwrite: bool):
console.print(f"\nβ
[green]Deployment successful![/green]")
console.print(f"Agent ID: [bold magenta]{agent_id}[/bold magenta]")
console.print(f"Agent URL: [link]{dashboard_url}[/link]")
+
+ # Check if this looks like an OpenClaw Gateway deployment (by path/shortcut),
+ # then display gateway URL + token + pairing info + VM IP for MCP setup.
+ try:
+ is_openclaw_gateway = (
+ "openclaw/gateway" in path
+ or path.endswith("openclaw/gateway")
+ or path.rstrip("/").endswith("gateway") and "openclaw" in str(path)
+ )
+ is_picoclaw_gateway = (
+ path == "picoclaw"
+ or "picoclaw/gateway" in path
+ or path.endswith("picoclaw/gateway")
+ )
+ is_zeroclaw_gateway = (
+ path == "zeroclaw"
+ or "zeroclaw/gateway" in path
+ or path.endswith("zeroclaw/gateway")
+ )
+
+ if is_openclaw_gateway:
+ # Fetch agent metadata and NetworkInfo to get all credentials.
+ # Poll a few times since serverless gateway setup runs in background.
+ import time
+ gateway_url = None
+ gateway_token = None
+ pairing_status = None
+ vm_ip = None
+
+ # Check if result already has NetworkInfo (from start response)
+ if result.get("network_info"):
+ network_info = result.get("network_info", {})
+ vm_ip = network_info.get("ip_address") or network_info.get("IpAddress")
+ elif result.get("data") and result.get("data", {}).get("network_info"):
+ network_info = result.get("data", {}).get("network_info", {})
+ vm_ip = network_info.get("ip_address") or network_info.get("IpAddress")
+
+ for attempt in range(5): # Try up to 5 times
+ time.sleep(1 + attempt) # Increasing wait: 1s, 2s, 3s, 4s, 5s
+ agent_info = sdk.remote.client.get_agent_status(agent_id)
+ if agent_info.get("success"):
+ data = agent_info.get("data", {}) or {}
+
+ # Extract NetworkInfo
+ network_info = data.get("network_info") or {}
+ if not vm_ip:
+ vm_ip = network_info.get("ip_address") or network_info.get("IpAddress")
+
+ # Extract metadata
+ metadata = data.get("metadata", {}) or {}
+ gateway_url = metadata.get("gateway_url") or gateway_url
+ gateway_token = metadata.get("gateway_token") or gateway_token
+ pairing_status = metadata.get("pairing_status") or pairing_status
+
+ # If we have URL or IP, we consider setup "good enough" to show.
+ if gateway_url or vm_ip:
+ break
+
+ # Prefer token from environment if present (user-controlled secret)
+ env_gateway_token = os.environ.get("OPENCLAW_GATEWAY_TOKEN") or None
+ if env_gateway_token:
+ gateway_token = env_gateway_token
+
+ console.print(f"\n[bold cyan]OpenClaw Gateway Credentials:[/bold cyan]")
+ console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]")
+
+ if vm_ip:
+ console.print(f" VM IP Address: [green]{vm_ip}[/green]")
+ else:
+ console.print(f" VM IP Address: [yellow]Not yet available (VM still starting)[/yellow]")
+
+ if gateway_url:
+ console.print(f" Gateway URL: [green]{gateway_url}[/green]")
+ elif vm_ip:
+ # Construct gateway URL from IP if we have it
+ gateway_url = f"ws://{vm_ip}:18789"
+ console.print(f" Gateway URL: [green]{gateway_url}[/green]")
+ else:
+ console.print(f" Gateway URL: [yellow]Not yet available (gateway still starting)[/yellow]")
+
+ if gateway_token:
+ console.print(f" Gateway Token: [green]{gateway_token}[/green]")
+ elif gateway_url or vm_ip:
+ console.print(f" Gateway Token: [yellow]Not set (token auth disabled or not provided)[/yellow]")
+
+ if pairing_status:
+ console.print(f" Pairing Status: [green]{pairing_status}[/green]")
+ else:
+ console.print(f" Pairing Status: [yellow]Pending (will autoβapprove when devices connect)[/yellow]")
+
+ if gateway_url or vm_ip:
+ console.print(f"\n[dim]Use these credentials to deploy MCP:[/dim]")
+ if gateway_url:
+ console.print(f" [cyan]OPENCLAW_GATEWAY_URL={gateway_url}[/cyan]")
+ elif vm_ip:
+ console.print(f" [cyan]OPENCLAW_GATEWAY_URL=ws://{vm_ip}:18789[/cyan]")
+ if gateway_token:
+ console.print(f" [cyan]OPENCLAW_GATEWAY_TOKEN={gateway_token}[/cyan]")
+ else:
+ console.print(f"\n[dim]You can fetch gateway info later with:[/dim]")
+ console.print(f" [cyan]runagent status {agent_id}[/cyan]")
+
+ if is_picoclaw_gateway:
+ console.print(f"\n[bold cyan]Picoclaw Gateway Deployment:[/bold cyan]")
+ console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]")
+ console.print(" Framework: [green]picoclaw[/green]")
+ console.print(" Runtime: [green]picoclaw-gateway[/green]")
+ console.print("\n[dim]Inside the microVM, picoclaw uses:[/dim]")
+ console.print(" Config: [cyan]/root/.picoclaw/config.json[/cyan]")
+ console.print(" Workspace: [cyan]/root/.picoclaw/workspace[/cyan]")
+ console.print(" (backed by /persistent/.picoclaw on the VM data disk)")
+ console.print("\n[dim]Next steps:[/dim]")
+ console.print(" 1. Configure models and channels in ~/.picoclaw/config.json")
+ console.print(" 2. Use 'picoclaw gateway' inside the VM for channel bots")
+ console.print(" 3. Rely on heartbeat/cron for periodic tasks from the workspace")
+
+ if is_zeroclaw_gateway:
+ console.print(f"\n[bold cyan]ZeroClaw Gateway Deployment:[/bold cyan]")
+ console.print(f" Agent ID: [bold magenta]{agent_id}[/bold magenta]")
+ console.print(" Framework: [green]zeroclaw[/green]")
+ console.print(" Runtime: [green]zeroclaw-gateway[/green]")
+ console.print("\n[dim]Inside the microVM, ZeroClaw uses:[/dim]")
+ console.print(" Config: [cyan]/root/.zeroclaw/config.toml[/cyan]")
+ console.print(" Workspace: [cyan]/root/.zeroclaw/workspace[/cyan]")
+ console.print(" (backed by /persistent/.zeroclaw on the VM data disk)")
+ console.print("\n[dim]Next steps:[/dim]")
+ console.print(" 1. Configure providers/models/channels in ~/.zeroclaw/config.toml")
+ console.print(" 2. Run `zeroclaw service install` if you need system-managed services")
+ console.print(" 3. Bind channel identities (Telegram/Discord) as required")
+ except Exception as e:
+ # Log error but don't fail deployment
+ import traceback
+ console.print(f"[yellow]β [/yellow] Could not fetch gateway credentials: {e}")
+ if os.getenv('DEBUG'):
+ console.print(f"[dim]{traceback.format_exc()}[/dim]")
+ pass
else:
error_info = result.get("error")
console.print(f"β [red]Deployment failed:[/red] {format_error_message(error_info)}")
diff --git a/runagent/cli/commands/register.py b/runagent/cli/commands/register.py
index 0950c41..04cc5e4 100644
--- a/runagent/cli/commands/register.py
+++ b/runagent/cli/commands/register.py
@@ -2,7 +2,11 @@
CLI commands for agent registration and management.
"""
import os
+import json
+import tempfile
+import shutil
from pathlib import Path
+from typing import Optional, Tuple
import click
from rich.console import Console
@@ -10,12 +14,13 @@
from runagent.cli.branding import print_header
from runagent.utils.agent import get_agent_config, get_agent_config_with_defaults
-from runagent.utils.agent_id import generate_config_fingerprint
+from runagent.utils.agent_id import generate_config_fingerprint, generate_agent_id
+from runagent.constants import TEMPLATE_PREPATH, LOCAL_CACHE_DIRECTORY
console = Console()
-def _register_agent_core(agent_path: Path):
+def _register_agent_core(agent_path: Path | str):
"""
Core logic for registering an agent (can be called directly or via Click command).
@@ -27,8 +32,8 @@ def _register_agent_core(agent_path: Path):
try:
print_header("Register Agent")
- # Resolve path
- agent_path = agent_path.resolve()
+ # Resolve path (accept str from e.g. config --register-agent)
+ agent_path = Path(agent_path).resolve()
# Check if runagent.config.json exists
config_path = agent_path / "runagent.config.json"
@@ -150,6 +155,191 @@ def _register_agent_core(agent_path: Path):
raise click.ClickException("Agent registration failed")
+def _resolve_template_path_for_registration(template_path: str) -> Tuple[Path, Optional[Path]]:
+ """
+ Resolve template path for registration - supports template shortcuts.
+ Returns (resolved_path, temp_dir_for_cleanup).
+ """
+ path = Path(template_path)
+
+ # If path exists, use it directly
+ if path.exists():
+ return path.resolve(), None
+
+ # Check if it looks like a template shortcut (framework/template format)
+ parts = template_path.split("/")
+ if len(parts) == 2 and "/" in template_path:
+ framework, template = parts
+
+ # Check local templates first
+ possible_paths = [
+ Path.cwd() / TEMPLATE_PREPATH / framework / template,
+ Path.cwd() / "templates" / framework / template,
+ ]
+
+ # Also check relative to runagent package if it's installed
+ try:
+ import runagent
+ runagent_package_dir = Path(runagent.__file__).parent.parent
+ possible_paths.extend([
+ runagent_package_dir / TEMPLATE_PREPATH / framework / template,
+ runagent_package_dir / "templates" / framework / template,
+ ])
+ except (ImportError, AttributeError):
+ pass
+
+ for local_template_path in possible_paths:
+ if local_template_path.exists():
+ return local_template_path.resolve(), None
+
+ # If not found locally, try to download (but for registration, we prefer local)
+ raise click.ClickException(
+ f"Template '{template_path}' not found locally. "
+ f"Templates must exist locally for registration."
+ )
+
+ # Path doesn't exist and doesn't look like a template
+ raise click.ClickException(
+ f"Path not found: {path}\n"
+ f"Use a local path or template shortcut like 'openclaw/gateway'"
+ )
+
+
+def _register_template_agent(template_shortcut: str):
+ """
+ Register a template agent from a shortcut (e.g., 'openclaw/gateway').
+ This is a new feature that:
+ 1. Resolves the template shortcut to a local template
+ 2. Checks if template is already registered
+ 3. If not, generates UUID, saves it to a persistent registered templates location
+ 4. Registers the agent with that UUID
+ """
+ try:
+ print_header("Register Template Agent")
+
+ # Resolve template path
+ try:
+ resolved_path, _ = _resolve_template_path_for_registration(template_shortcut)
+ except click.ClickException:
+ raise
+ except Exception as e:
+ raise click.ClickException(f"Failed to resolve template '{template_shortcut}': {e}")
+
+ console.print(f"[dim]Template: [cyan]{template_shortcut}[/cyan][/dim]")
+ console.print(f"[dim]Path: [cyan]{resolved_path}[/cyan][/dim]")
+
+ # Load template config
+ config_path = resolved_path / "runagent.config.json"
+ if not config_path.exists():
+ raise click.ClickException(f"No runagent.config.json found in template: {resolved_path}")
+
+ agent_config = get_agent_config(resolved_path)
+ if not agent_config:
+ raise click.ClickException("Failed to load template configuration")
+
+ template_name = agent_config.get('template') if isinstance(agent_config, dict) else getattr(agent_config, 'template', None)
+ agent_name = agent_config.get('agent_name') if isinstance(agent_config, dict) else getattr(agent_config, 'agent_name', None)
+
+ # Check if this template is already registered
+ from runagent.sdk.db import DBService, Agent
+ from sqlalchemy import or_
+ db_service = DBService()
+
+ with db_service.db_manager.get_session() as session:
+ query = session.query(Agent)
+ conditions = []
+ if template_name:
+ conditions.append(Agent.template == template_name)
+ if agent_name:
+ conditions.append(Agent.agent_name == agent_name)
+
+ existing_agents = []
+ if conditions:
+ query = query.filter(or_(*conditions))
+ existing_agents = query.all()
+
+ # If already registered, show existing agent info
+ if existing_agents:
+ console.print(f"\n[yellow]β Template '{template_shortcut}' is already registered[/yellow]")
+ for existing in existing_agents:
+ console.print(f" β’ Agent ID: [cyan]{existing.agent_id}[/cyan]")
+ console.print(f" Status: [cyan]{existing.status or 'unknown'}[/cyan]")
+ console.print(f" Name: [cyan]{existing.agent_name or 'N/A'}[/cyan]")
+
+ from rich.prompt import Confirm
+ if not Confirm.ask("\n[bold]Register a new instance anyway?[/bold]", default=False):
+ console.print("[dim]Registration cancelled. Use existing agent ID for deployment.[/dim]")
+ return
+
+ # Create a persistent registered templates directory
+ registered_templates_dir = Path(LOCAL_CACHE_DIRECTORY) / "registered_templates"
+ registered_templates_dir.mkdir(parents=True, exist_ok=True)
+
+ # Create a working copy for this registered template
+ template_working_dir = registered_templates_dir / template_shortcut.replace("/", "_")
+ if template_working_dir.exists():
+ shutil.rmtree(template_working_dir)
+ shutil.copytree(resolved_path, template_working_dir)
+
+ # Generate UUID and update config
+ new_agent_id = generate_agent_id()
+ working_config_path = template_working_dir / "runagent.config.json"
+ with working_config_path.open('r') as f:
+ config_data = json.load(f)
+ config_data['agent_id'] = new_agent_id
+ with working_config_path.open('w') as f:
+ json.dump(config_data, f, indent=2)
+
+ console.print(f"\n[green]β[/green] Generated agent ID: [cyan]{new_agent_id}[/cyan]")
+ console.print(f"[dim]Registered template stored at: [cyan]{template_working_dir}[/cyan][/dim]")
+
+ # Register the agent
+ config_with_defaults = get_agent_config_with_defaults(template_working_dir)
+ config_fingerprint = generate_config_fingerprint(template_working_dir)
+ active_project_id = db_service.get_active_project_id()
+
+ result = db_service.add_agent(
+ agent_id=new_agent_id,
+ agent_path=str(template_working_dir),
+ host="localhost",
+ port=8000,
+ framework=agent_config.framework.value if hasattr(agent_config.framework, 'value') else str(agent_config.framework),
+ status="initialized",
+ agent_name=config_with_defaults.get('agent_name'),
+ description=config_with_defaults.get('description'),
+ template=config_with_defaults.get('template'),
+ version=config_with_defaults.get('version'),
+ initialized_at=config_with_defaults.get('created_at'),
+ config_fingerprint=config_fingerprint,
+ project_id=active_project_id,
+ )
+
+ if not result.get('success'):
+ raise click.ClickException(f"Failed to register agent: {result.get('error')}")
+
+ # Show success message
+ console.print(Panel(
+ f"[bold green]β
Template Registered Successfully![/bold green]\n\n"
+ f"[dim]Template:[/dim] [cyan]{template_shortcut}[/cyan]\n"
+ f"[dim]Agent ID:[/dim] [cyan]{new_agent_id}[/cyan]\n"
+ f"[dim]Name:[/dim] [white]{config_with_defaults.get('agent_name', 'Unknown')}[/white]\n"
+ f"[dim]Framework:[/dim] [blue]{agent_config.framework}[/blue]\n"
+ f"[dim]Status:[/dim] [green]initialized[/green]",
+ title="[bold green]Registration Success[/bold green]",
+ border_style="green"
+ ))
+
+ console.print("\n[bold]Next Step:[/bold]")
+ console.print(f" Deploy: [cyan]runagent deploy {template_shortcut}[/cyan]")
+ console.print(f" (This will use agent ID: [cyan]{new_agent_id}[/cyan])")
+
+ except Exception as e:
+ if os.getenv('DISABLE_TRY_CATCH'):
+ raise
+ console.print(f"β [red]Registration error:[/red] {e}")
+ raise click.ClickException("Template registration failed")
+
+
@click.command()
@click.argument(
"path",
diff --git a/runagent/sdk/rest_client.py b/runagent/sdk/rest_client.py
index 16f2fd0..0a26eec 100644
--- a/runagent/sdk/rest_client.py
+++ b/runagent/sdk/rest_client.py
@@ -1084,6 +1084,9 @@ def _process_start_result(self, result: Dict, agent_id: str) -> Dict:
result_data = result["data"]
endpoint = result_data.get("endpoint")
+ # Extract NetworkInfo if available
+ network_info = result_data.get("network_info")
+
# Generate dashboard URL instead of API endpoint
dashboard_url = f"https://app.run-agent.ai/dashboard/agents/{agent_id}"
@@ -1096,19 +1099,25 @@ def _process_start_result(self, result: Dict, agent_id: str) -> Dict:
))
# Update local deployment info
- self._update_deployment_info(agent_id, {
+ deployment_info = {
"status": "deployed",
"endpoint": f"{self.base_url}{endpoint}",
"started_at": time.strftime("%Y-%m-%d %H:%M:%S"),
- })
+ }
+ if network_info:
+ deployment_info["network_info"] = network_info
+ self._update_deployment_info(agent_id, deployment_info)
- return {
+ return_result = {
"success": True,
"agent_id": agent_id,
"endpoint": f"{self.base_url}{endpoint}",
"dashboard_url": dashboard_url,
"status": "deployed",
}
+ if network_info:
+ return_result["network_info"] = network_info
+ return return_result
return result
def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool = False) -> Dict:
@@ -1285,7 +1294,11 @@ def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool
# Phase 2: Start agent (60-100%)
retry_progress.update(retry_task, completed=70, description="Starting agent...")
- start_result = self.start_agent(agent_id, {})
+ # Extract metadata from agent config for OpenClaw gateway, etc.
+ start_metadata = {}
+ if isinstance(agent_config, dict) and agent_config.get('metadata'):
+ start_metadata = agent_config.get('metadata', {}).copy()
+ start_result = self.start_agent(agent_id, start_metadata)
if not start_result.get("success"):
return {"success": False, "error": f"Agent start failed: {start_result.get('error')}"}
@@ -1328,6 +1341,13 @@ def deploy_agent(self, folder_path: str, metadata: Dict = None, overwrite: bool
# Phase 3: Start agent (70-100%)
progress.update(deploy_task, completed=75, description="Starting agent deployment...")
+ # Extract metadata from agent config if not provided
+ if metadata is None:
+ metadata = {}
+ # Merge with metadata from agent config (e.g., runtime for OpenClaw gateway)
+ # agent_config is a dict returned by get_agent_config
+ if isinstance(agent_config, dict) and agent_config.get('metadata'):
+ metadata = {**metadata, **agent_config.get('metadata', {})}
start_result = self._start_agent_core(agent_id, metadata)
if start_result.get("success"):
diff --git a/runagent/sdk/server/framework/__init__.py b/runagent/sdk/server/framework/__init__.py
index eb078a4..f71fb29 100644
--- a/runagent/sdk/server/framework/__init__.py
+++ b/runagent/sdk/server/framework/__init__.py
@@ -31,7 +31,8 @@ def get_executor(
Framework.LETTA: GenericExecutor,
Framework.LLAMAINDEX: LlamaIndexExecutor,
Framework.N8N: N8NExecutor,
- Framework.PARLANT: ParlantExecutor
+ Framework.PARLANT: ParlantExecutor,
+ Framework.OPENCLAW: GenericExecutor, # MCP uses Python; gateway uses separate runtime
}
framework_executor = executor_dict.get(framework)
if framework_executor is None:
diff --git a/runagent/utils/agent.py b/runagent/utils/agent.py
index 3e3de85..9b5e3cf 100644
--- a/runagent/utils/agent.py
+++ b/runagent/utils/agent.py
@@ -166,8 +166,12 @@ def validate_agent(
if config.framework.is_pythonic():
is_valid, details = validate_pythonic_agent(config, dynamic_loading, folder_path)
- else:
+ elif config.framework.is_webhook():
is_valid, details = validate_webhook_agent(config, dynamic_loading, folder_path)
+ elif config.framework.is_service():
+ is_valid, details = validate_service_agent(config, dynamic_loading, folder_path)
+ else:
+ is_valid, details = validate_pythonic_agent(config, dynamic_loading, folder_path)
return is_valid, details
@@ -183,6 +187,23 @@ def validate_webhook_agent(config, dynamic_loading, folder_path):
warning_msgs=[],
)
+
+def validate_service_agent(config, dynamic_loading, folder_path):
+ """Validate OpenClaw and other service-type agents."""
+ # If entrypoints exist (e.g. OpenClaw MCP), validate them like pythonic
+ if config.agent_architecture and config.agent_architecture.entrypoints:
+ return validate_pythonic_agent(config, dynamic_loading, folder_path)
+ # No entrypoints (e.g. OpenClaw Gateway) - minimal validation
+ return True, dict(
+ valid=True,
+ folder_exists=True,
+ files_found=[],
+ missing_files=[],
+ success_msgs=["Service framework detected (no entrypoints)"],
+ error_msgs=[],
+ warning_msgs=[],
+ )
+
def validate_pythonic_agent(config, dynamic_loading, folder_path):
validation_details = {
diff --git a/runagent/utils/enums/framework.py b/runagent/utils/enums/framework.py
index 6008615..2f2a4f9 100644
--- a/runagent/utils/enums/framework.py
+++ b/runagent/utils/enums/framework.py
@@ -16,6 +16,9 @@ class Framework(Enum):
OPENAI = "openai"
N8N = "n8n"
PARLANT = "parlant"
+ OPENCLAW = "openclaw"
+ PICOCLAW = "picoclaw"
+ ZEROCLAW = "zeroclaw"
@classmethod
def _pythonic_frameworks_cache(cls) -> t.FrozenSet['Framework']:
@@ -28,7 +31,11 @@ def _pythonic_frameworks_cache(cls) -> t.FrozenSet['Framework']:
@classmethod
def _webhook_frameworks_cache(cls) -> t.FrozenSet['Framework']:
return frozenset({cls.N8N})
-
+
+ @classmethod
+ def _service_frameworks_cache(cls) -> t.FrozenSet['Framework']:
+ return frozenset({cls.OPENCLAW, cls.PICOCLAW, cls.ZEROCLAW})
+
@classmethod
def get_pythonic_frameworks(cls) -> t.FrozenSet['Framework']:
"""Get all pythonic frameworks (cached)"""
@@ -38,6 +45,11 @@ def get_pythonic_frameworks(cls) -> t.FrozenSet['Framework']:
def get_webhook_frameworks(cls) -> t.FrozenSet['Framework']:
"""Get all webhook frameworks (cached)"""
return cls._webhook_frameworks_cache()
+
+ @classmethod
+ def get_service_frameworks(cls) -> t.FrozenSet['Framework']:
+ """Get all service frameworks (cached)"""
+ return cls._service_frameworks_cache()
@classmethod
def get_selectable_frameworks(cls) -> t.List['Framework']:
@@ -70,6 +82,10 @@ def is_webhook(self) -> bool:
"""Check if this framework is webhook"""
return self in self.get_webhook_frameworks()
+ def is_service(self) -> bool:
+ """Check if this framework is service (e.g. OpenClaw)."""
+ return self in self.get_service_frameworks()
+
def is_default(self) -> bool:
"""Check if this framework is the default"""
return self == self.DEFAULT
@@ -83,6 +99,8 @@ def category(self) -> str:
return "pythonic"
elif self.is_webhook():
return "webhook"
+ elif self.is_service():
+ return "service"
else:
return "unknown"
# Class methods for string validation and conversion
diff --git a/templates/agno/default/runagent.config.json b/templates/agno/default/runagent.config.json
index e61687c..5d910db 100644
--- a/templates/agno/default/runagent.config.json
+++ b/templates/agno/default/runagent.config.json
@@ -25,7 +25,7 @@
]
},
"env_vars": {},
- "agent_id": "ae29bd73-b3d3-99c8-a98f-5d7aec7cf911",
+ "agent_id": "ae29bd73-b3d3-29c8-b98f-5d7aec7cf911",
"auth_settings": {
"type": "api_key"
}
diff --git a/templates/openclaw/DEPLOYMENT_GUIDE.md b/templates/openclaw/DEPLOYMENT_GUIDE.md
new file mode 100644
index 0000000..a926fe0
--- /dev/null
+++ b/templates/openclaw/DEPLOYMENT_GUIDE.md
@@ -0,0 +1,277 @@
+# OpenClaw Deployment Guide
+
+## Overview: Two Templates, One System
+
+OpenClaw has **two components** that work together:
+
+1. **Gateway** (`gateway/`) - The core OpenClaw server (Node.js)
+2. **MCP** (`mcp/`) - MCP interface server (Python) that connects to Gateway
+
+You need **BOTH** for a complete OpenClaw deployment.
+
+---
+
+## Architecture
+
+```
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β Your Application β
+β (Cursor, SDK, REST API, etc.) β
+ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
+ β
+ βΌ
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β OpenClaw MCP Agent (VM) β
+β β’ Runs: openclaw-mcp server β
+β β’ Exposes: MCP tools (send_message, agent_run, etc.) β
+β β’ Connects to: Gateway via WebSocket β
+ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββ
+ β WebSocket (ws://gateway_ip:18789)
+ βΌ
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β OpenClaw Gateway Agent (VM) β
+β β’ Runs: openclaw gateway run β
+β β’ Manages: WhatsApp, Telegram, Skills, Agents β
+β β’ Stores: All data in .openclaw (persisted) β
+βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+```
+
+---
+
+## Deployment Order
+
+### Step 1: Deploy Gateway (Required First)
+
+**Why first?** The MCP needs the Gateway's WebSocket URL to connect.
+
+```bash
+# Deploy gateway template
+runagent deploy openclaw/gateway
+
+# Or with SDK
+from runagent import RunAgentSDK
+sdk = RunAgentSDK()
+gateway_result = sdk.deploy_remote(
+ folder="templates/openclaw/gateway",
+ persistent_memory=True, # CRITICAL for OpenClaw
+ user_id="your-user-id"
+)
+
+gateway_deployment_id = gateway_result["deployment_id"]
+```
+
+**What happens:**
+- Creates Gateway VM (Node.js runtime)
+- Starts `openclaw gateway run` on port 18789
+- Gets VM IP (e.g., `192.168.100.5`)
+- Gateway URL: `ws://192.168.100.5:18789`
+
+**Important:**
+- β
**MUST** use `persistent_memory=True` (WhatsApp sessions need persistence)
+- β
Gateway stores everything in `.openclaw` folder (persisted)
+
+---
+
+### Step 2: Deploy MCP (After Gateway)
+
+**Why second?** MCP needs the Gateway URL to connect.
+
+```bash
+# Deploy MCP template
+runagent deploy openclaw/mcp
+
+# Or with SDK (you need to set gateway URL manually)
+from runagent import RunAgentSDK
+sdk = RunAgentSDK()
+
+# First, get gateway VM IP from gateway deployment
+gateway_vm_ip = "192.168.100.5" # Get from gateway deployment
+
+# Deploy MCP with gateway URL
+mcp_result = sdk.deploy_remote(
+ folder="templates/openclaw/mcp",
+ config={
+ "env_vars": {
+ "OPENCLAW_GATEWAY_URL": f"ws://{gateway_vm_ip}:18789",
+ "OPENCLAW_GATEWAY_TOKEN": "" # If gateway uses token auth
+ }
+ }
+)
+```
+
+**What happens:**
+- Creates MCP VM (Python runtime)
+- Installs `openclaw-mcp` package
+- Starts `openclaw-mcp` server
+- Connects to Gateway via `OPENCLAW_GATEWAY_URL`
+
+**Note:** Currently, you need to manually set `OPENCLAW_GATEWAY_URL`. The planned `POST /openclaw/deploy` endpoint would automate this.
+
+---
+
+## Quick Start: Deploy Both
+
+### Option A: Manual Deployment (Current)
+
+```bash
+# 1. Deploy Gateway
+runagent deploy openclaw/gateway --persistent-memory
+
+# 2. Get Gateway VM IP from deployment result
+# 3. Deploy MCP with gateway URL
+runagent deploy openclaw/mcp
+# Then update MCP agent env vars with gateway URL via middleware API
+```
+
+### Option B: SDK Deployment (Recommended)
+
+```python
+from runagent import RunAgentSDK
+
+sdk = RunAgentSDK()
+
+# 1. Deploy Gateway
+gateway_result = sdk.deploy_remote(
+ folder="templates/openclaw/gateway",
+ persistent_memory=True,
+ user_id="your-user-id"
+)
+gateway_deployment_id = gateway_result["deployment_id"]
+
+# 2. Get Gateway VM IP (from deployment metadata or middleware API)
+# TODO: Add helper: gateway_ip = sdk.get_gateway_vm_ip(gateway_deployment_id)
+gateway_ip = "192.168.100.5" # Get from deployment
+
+# 3. Deploy MCP with Gateway URL
+mcp_result = sdk.deploy_remote(
+ folder="templates/openclaw/mcp",
+ config={
+ "env_vars": {
+ "OPENCLAW_GATEWAY_URL": f"ws://{gateway_ip}:18789"
+ }
+ }
+)
+mcp_deployment_id = mcp_result["deployment_id"]
+```
+
+### Option C: Future - Single Deploy (Not Yet Implemented)
+
+```python
+# This will be available when POST /openclaw/deploy is implemented
+result = sdk.deploy_openclaw(persistent_memory=True)
+# Automatically deploys gateway + MCP and wires them together
+```
+
+---
+
+## Which Template Does What?
+
+### `gateway/` Template
+
+**Purpose:** Core OpenClaw server
+
+**Runs:**
+- `openclaw gateway run` (Node.js process)
+- Listens on port 18789
+- Manages WhatsApp, Telegram, Discord, etc.
+- Runs AI agents and skills
+
+**Stores:**
+- WhatsApp session data
+- Channel credentials
+- Agent workspaces
+- Configuration
+
+**Deployment:**
+- β
**MUST** use persistent storage (`persistent_memory=True`)
+- β
Runtime: `openclaw-gateway` (Node.js)
+- β
Needs `.openclaw` folder persisted
+
+**When to deploy:**
+- First, before MCP
+- Only one gateway per deployment (can have multiple MCPs connecting to same gateway)
+
+---
+
+### `mcp/` Template
+
+**Purpose:** MCP protocol interface to Gateway
+
+**Runs:**
+- `openclaw-mcp` (Python package)
+- Exposes MCP tools (send_message, agent_run, channels_list, etc.)
+- Connects to Gateway via WebSocket
+
+**Stores:**
+- Nothing (stateless, connects remotely)
+
+**Deployment:**
+- β
Runtime: `python` (standard)
+- β
Needs `OPENCLAW_GATEWAY_URL` environment variable
+- β Does NOT need persistent storage
+
+**When to deploy:**
+- After Gateway is deployed
+- Can deploy multiple MCPs (for different clients/users)
+
+---
+
+## Usage After Deployment
+
+### Via MCP (Cursor/Claude Desktop)
+
+1. Configure `.cursor/mcp.json`:
+```json
+{
+ "mcpServers": {
+ "openclaw": {
+ "url": "wss://api.runagent.xyz/mcp/{mcp_deployment_id}"
+ }
+ }
+}
+```
+
+2. Use in Cursor chat:
+ - "Send a WhatsApp message to +1234567890 saying Hello"
+ - "List my OpenClaw channels"
+ - "Run the agent: summarize my PRs"
+
+### Via REST API
+
+```python
+# Use middleware REST API
+import requests
+
+# Send message
+requests.post(
+ f"https://api.runagent.xyz/api/v1/openclaw/{gateway_deployment_id}/send-message",
+ json={"channel_id": "whatsapp", "message": "Hello"},
+ headers={"Authorization": f"Bearer {api_key}"}
+)
+
+# List channels
+requests.get(
+ f"https://api.runagent.xyz/api/v1/openclaw/{gateway_deployment_id}/channels/list",
+ headers={"Authorization": f"Bearer {api_key}"}
+)
+```
+
+### Via SDK (Future)
+
+```python
+# When sdk.openclaw() is implemented
+openclaw = sdk.openclaw(gateway_deployment_id)
+openclaw.send_message(target="+1234567890", message="Hello", channel="whatsapp")
+openclaw.agent_run(message="What's the weather?")
+```
+
+---
+
+## Summary
+
+| Template | Deploy Order | Purpose | Runtime | Persistent Storage |
+|----------|--------------|---------|---------|-------------------|
+| **gateway/** | 1st | Core OpenClaw server | Node.js | β
Required |
+| **mcp/** | 2nd | MCP interface | Python | β Not needed |
+
+**Deploy both** for a complete OpenClaw setup. Gateway first, then MCP.
diff --git a/templates/openclaw/PERSISTENT_STORAGE.md b/templates/openclaw/PERSISTENT_STORAGE.md
new file mode 100644
index 0000000..9353688
--- /dev/null
+++ b/templates/openclaw/PERSISTENT_STORAGE.md
@@ -0,0 +1,108 @@
+# OpenClaw Persistent Storage in RunAgent
+
+OpenClaw stores all its data (config, credentials, sessions, workspace) in the `.openclaw` folder. In RunAgent cloud, this folder must be persisted across VM restarts.
+
+## How It Works
+
+### 1. Configuration
+
+The gateway template includes `persistent_folders` in `runagent.config.json`:
+
+```json
+{
+ "persistent_folders": [".openclaw"]
+}
+```
+
+### 2. Runtime Behavior
+
+When the VM starts with persistent storage enabled:
+
+1. **Persistent disk** (`/dev/vdb`) is mounted to `/persistent`
+2. **Symlink created**: `/root/.openclaw` β `/persistent/.openclaw`
+3. **Data persists** across VM restarts, snapshots, and deployments
+
+### 3. What Gets Persisted
+
+The `.openclaw` folder contains:
+
+- `openclaw.json` - Gateway configuration
+- `credentials/` - WhatsApp, Telegram, Discord, etc. authentication
+- `workspace/` - Agent workspaces and conversation state
+- `skills/` - Custom skills and their state
+- `cron/` - Cron job definitions
+- Other runtime data
+
+## Deployment Requirements
+
+**Gateway VM**: **MUST** use persistent storage (`persistent_memory=true`)
+
+Without persistent storage:
+- β WhatsApp sessions lost on restart
+- β Channel credentials lost
+- β Agent workspace state lost
+- β Configuration changes lost
+
+**MCP VM**: **NOT required** (connects remotely to Gateway)
+
+The MCP agent connects to Gateway via `OPENCLAW_GATEWAY_URL` and doesn't store local state.
+
+## Example: SDK Deployment with Persistent Storage
+
+```python
+from runagent import RunAgentSDK
+
+sdk = RunAgentSDK()
+
+# Deploy gateway with persistent storage
+deployment = sdk.deploy_remote(
+ folder="templates/openclaw/gateway",
+ persistent_memory=True, # CRITICAL for OpenClaw
+ user_id="user-123" # Required for persistent storage
+)
+
+gateway_deployment_id = deployment["deployment_id"]
+```
+
+## Verification
+
+After deployment, verify persistent storage is working:
+
+```bash
+# Execute command in gateway VM
+sdk.execute_command(
+ vm_id=gateway_vm_id,
+ command="ls -la /root/.openclaw && test -L /root/.openclaw && echo 'Symlink OK' || echo 'No symlink'"
+)
+```
+
+Expected output:
+```
+Symlink OK
+```
+
+## Troubleshooting
+
+### Persistent storage not mounted
+
+**Symptom**: `/root/.openclaw` is a regular directory, not a symlink
+
+**Fix**: Ensure `persistent_memory=true` and `user_id` are set during deployment
+
+### Data lost after restart
+
+**Symptom**: WhatsApp sessions, credentials, or config disappear
+
+**Fix**:
+1. Check VM has persistent disk: `lsblk` should show `/dev/vdb`
+2. Check mount: `mount | grep persistent` should show `/dev/vdb on /persistent`
+3. Check symlink: `ls -la /root/.openclaw` should show `-> /persistent/.openclaw`
+
+### Config file not found
+
+**Symptom**: Gateway can't find `openclaw.json`
+
+**Fix**: The config is auto-created on first run. If missing, check:
+- Persistent storage is mounted
+- Symlink exists: `/root/.openclaw` β `/persistent/.openclaw`
+- Permissions: `/persistent/.openclaw` should be writable
diff --git a/templates/openclaw/gateway/.agent_files/.gitkeep b/templates/openclaw/gateway/.agent_files/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/templates/openclaw/gateway/README.md b/templates/openclaw/gateway/README.md
new file mode 100644
index 0000000..a6c8749
--- /dev/null
+++ b/templates/openclaw/gateway/README.md
@@ -0,0 +1,87 @@
+# OpenClaw Gateway Template
+
+This template deploys the **OpenClaw Gateway** - the core OpenClaw server that runs WhatsApp, skills, and AI agents.
+
+> **π See [DEPLOYMENT_GUIDE.md](../DEPLOYMENT_GUIDE.md) for complete deployment instructions and how Gateway + MCP work together.**
+
+## Structure
+
+```
+gateway/
+ .agent_files/
+ .gitkeep # Keeps directory in git (openclaw.json auto-created at runtime)
+ runagent.config.json # RunAgent config
+ README.md # This file
+```
+
+## Deployment
+
+### 1. Prepare Configuration
+
+The `.agent_files/` directory will contain:
+- `openclaw.json` - OpenClaw configuration (auto-created on first run or copied from example)
+- `credentials/` - WhatsApp and other channel credentials (populated during setup)
+
+These are **not** included in the template by default. They will be:
+- Created automatically when the gateway starts
+- Or uploaded as part of the agent deployment zip
+
+### 2. Deploy with RunAgent
+
+```bash
+# From the gateway template directory
+runagent deploy .
+
+# Or programmatically
+from runagent import RunAgentSDK
+sdk = RunAgentSDK()
+result = sdk.deploy_remote(folder="templates/openclaw/gateway")
+```
+
+### 3. Runtime Configuration
+
+- **Runtime**: `openclaw-gateway` (automatically set via metadata)
+- **Port**: Gateway listens on port 18789 inside the VM
+- **Network**: VM gets a TAP device with an IP (e.g., 192.168.100.5)
+- **Persistent storage**: `.openclaw` directory is persisted to `/persistent/.openclaw`
+
+#### Persistent Storage Details
+
+The template includes `"persistent_folders": [".openclaw"]` in `runagent.config.json`. This tells RunAgent to:
+
+1. **Mount persistent disk** (`/dev/vdb`) to `/persistent` when the VM starts
+2. **Create symlink** `/root/.openclaw` β `/persistent/.openclaw`
+3. **Persist data** across VM restarts (WhatsApp sessions, config, credentials, etc.)
+
+**Important**: When deploying, ensure `persistent_memory=true` is set (or use the SDK's persistent storage option). Without persistent storage, `.openclaw` data will be lost on VM restart.
+
+The `.openclaw` folder contains:
+- `openclaw.json` - Gateway configuration
+- `credentials/` - WhatsApp and channel authentication data
+- `workspace/` - Agent workspaces and state
+- `skills/` - Custom skills
+- Other runtime data
+
+### 4. Gateway URL
+
+After deployment, the gateway URL will be:
+- Internal: `ws://{vm_ip}:18789`
+- Public (via middleware proxy): `wss://api.runagent.xyz/openclaw/{deployment_id}/gateway`
+
+## Usage
+
+The gateway runs automatically when the VM starts. You can interact with it via:
+
+1. **MCP Server** - Connect the MCP agent to this gateway
+2. **SDK** - Use `sdk.openclaw(deployment_id).send_message(...)`
+3. **Direct CLI** - Execute commands in the VM: `openclaw channels list`
+
+## Notes
+
+- The gateway needs persistent storage for WhatsApp sessions and configuration
+- Credentials must be provided during deployment or will be created on first run
+- Auto-pairing will be handled by the middleware for MCP connections
+
+## Persistent Storage
+
+For detailed information about how persistent storage works for OpenClaw, see [PERSISTENT_STORAGE.md](../PERSISTENT_STORAGE.md).
diff --git a/templates/openclaw/gateway/runagent.config.json b/templates/openclaw/gateway/runagent.config.json
new file mode 100644
index 0000000..e4f408f
--- /dev/null
+++ b/templates/openclaw/gateway/runagent.config.json
@@ -0,0 +1,21 @@
+{
+ "agent_name": "openclaw_gateway",
+ "description": "OpenClaw Gateway - runs WhatsApp, skills, and agents",
+ "framework": "openclaw",
+ "template": "openclaw/gateway",
+ "version": "1.0.0",
+ "created_at": "2026-02-09T00:00:00Z",
+ "template_source": {
+ "repo_url": "https://github.com/runagent-dev/runagent.git",
+ "path": "templates/openclaw/gateway",
+ "author": "runagent"
+ },
+ "agent_architecture": {
+ "entrypoints": []
+ },
+ "persistent_folders": ["rad/openclaw"],
+ "metadata": {
+ "runtime": "openclaw-gateway"
+ },
+ "agent_id": "00000000-0000-0000-0000-000000000000"
+}
diff --git a/templates/openclaw/mcp/README.md b/templates/openclaw/mcp/README.md
new file mode 100644
index 0000000..0a8ab5c
--- /dev/null
+++ b/templates/openclaw/mcp/README.md
@@ -0,0 +1,99 @@
+# OpenClaw MCP Template
+
+This template deploys the **OpenClaw MCP server** - provides an MCP (Model Context Protocol) interface to the OpenClaw Gateway.
+
+> **π See [DEPLOYMENT_GUIDE.md](../DEPLOYMENT_GUIDE.md) for complete deployment instructions and how Gateway + MCP work together.**
+
+> **β οΈ IMPORTANT: Deploy the Gateway template FIRST, then deploy this MCP template.**
+
+## Structure
+
+```
+mcp/
+ agent.py # MCP runner entrypoint
+ requirements.txt # openclaw-mcp package
+ runagent.config.json # RunAgent config
+ README.md # This file
+```
+
+## Deployment
+
+### 1. Prerequisites
+
+Ensure the OpenClaw Gateway is deployed first and get its deployment ID.
+
+### 2. Deploy MCP with Gateway URL
+
+The MCP agent needs to connect to the gateway. Set `OPENCLAW_GATEWAY_URL`:
+
+```bash
+# Deploy MCP template
+runagent deploy openclaw/mcp
+
+# Then update env vars via middleware API with gateway URL
+```
+
+**Or programmatically:**
+
+```python
+from runagent import RunAgentSDK
+
+sdk = RunAgentSDK()
+
+# Get gateway VM IP from gateway deployment (e.g., 192.168.100.5)
+gateway_ip = "192.168.100.5" # Get from gateway deployment result
+
+# Deploy MCP with gateway URL
+result = sdk.deploy_remote(
+ folder="templates/openclaw/mcp",
+ config={
+ "env_vars": {
+ "OPENCLAW_GATEWAY_URL": f"ws://{gateway_ip}:18789",
+ "OPENCLAW_GATEWAY_TOKEN": "" # If gateway uses token auth
+ }
+ }
+)
+```
+
+**Environment Variables:**
+- `OPENCLAW_GATEWAY_URL` - **Required** - WebSocket URL (e.g., `ws://192.168.100.5:18789`)
+- `OPENCLAW_GATEWAY_TOKEN` - Optional - Gateway auth token
+
+### 4. Runtime Configuration
+
+- **Runtime**: `python` (standard Python agent)
+- **Package**: Uses `openclaw-mcp` package (private, not on PyPI)
+- **Transport**: stdio (default) or streamable (HTTP)
+- **Long-lived**: VM stays running to keep the MCP server active
+
+## Usage
+
+Once deployed, the MCP server is accessible via:
+
+1. **Cursor** - Configure `.cursor/mcp.json` to connect to the MCP endpoint
+2. **Claude Desktop** - Add to MCP server configuration
+3. **Other clients** - Use the MCP protocol to interact
+
+## MCP Operations
+
+The MCP server exposes OpenClaw operations as tools:
+
+- `openclaw_send_message` - Send WhatsApp/Telegram/etc. messages
+- `openclaw_agent_run` - Run AI agent turn
+- `openclaw_channels_list` - List available channels
+- `openclaw_gateway_status` - Check gateway health
+- And 40+ more tools...
+
+See the [mcp-openclaw README](https://github.com/your-org/mcp-openclaw) for full tool list.
+
+## Connection Flow
+
+```
+Client (Cursor) β MCP Server (this agent) β OpenClaw Gateway β WhatsApp/Skills
+```
+
+## Notes
+
+- The MCP agent must be able to reach the gateway VM (same network or via proxy)
+- Device pairing is automated during deployment
+- MCP runs indefinitely to keep the VM alive
diff --git a/templates/openclaw/mcp/agent.py b/templates/openclaw/mcp/agent.py
new file mode 100644
index 0000000..844ef5e
--- /dev/null
+++ b/templates/openclaw/mcp/agent.py
@@ -0,0 +1,76 @@
+"""
+OpenClaw MCP Agent - Runs openclaw-mcp server.
+
+This agent keeps the VM running and exposes the MCP server.
+"""
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+def run_mcp():
+ """
+ Start openclaw-mcp server in background.
+
+ Spawns the MCP server process and returns immediately. The process
+ runs in background; VM stays alive. The MCP server connects to
+ OpenClaw Gateway via OPENCLAW_GATEWAY_URL.
+ """
+ gateway_url = os.environ.get("OPENCLAW_GATEWAY_URL")
+ gateway_token = os.environ.get("OPENCLAW_GATEWAY_TOKEN")
+
+ if not gateway_url:
+ return {
+ "success": False,
+ "error": "OPENCLAW_GATEWAY_URL environment variable not set",
+ "message": "Set OPENCLAW_GATEWAY_URL to the gateway WebSocket URL (e.g., ws://192.168.100.5:18789)",
+ }
+
+ env = os.environ.copy()
+ env["OPENCLAW_GATEWAY_URL"] = gateway_url
+ if gateway_token:
+ env["OPENCLAW_GATEWAY_TOKEN"] = gateway_token
+
+ transport = os.environ.get("OPENCLAW_MCP_TRANSPORT", "streamable")
+ port = int(os.environ.get("OPENCLAW_MCP_PORT", "8000"))
+
+ try:
+ if transport == "streamable":
+ cmd = ["openclaw-mcp", "--transport", "streamable", "--port", str(port)]
+ else:
+ cmd = ["openclaw-mcp"]
+
+ print(f"Starting openclaw-mcp with gateway: {gateway_url}", file=sys.stderr)
+ print(f"Transport: {transport}, port: {port}", file=sys.stderr)
+
+ process = subprocess.Popen(
+ cmd,
+ env=env,
+ stdout=sys.stdout,
+ stderr=sys.stderr,
+ text=True,
+ start_new_session=True,
+ )
+
+ return {
+ "success": True,
+ "message": "MCP server started in background",
+ "pid": process.pid,
+ "port": port if transport == "streamable" else None,
+ "gateway_url": gateway_url,
+ }
+
+ except FileNotFoundError:
+ return {
+ "success": False,
+ "error": "openclaw-mcp not found",
+ "message": "Install openclaw-mcp: pip install openclaw-mcp",
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "error": str(e),
+ "message": f"Failed to start openclaw-mcp: {e}",
+ }
diff --git a/templates/openclaw/mcp/requirements.txt b/templates/openclaw/mcp/requirements.txt
new file mode 100644
index 0000000..6c50e45
--- /dev/null
+++ b/templates/openclaw/mcp/requirements.txt
@@ -0,0 +1 @@
+openclaw-mcp
diff --git a/templates/openclaw/mcp/runagent.config.json b/templates/openclaw/mcp/runagent.config.json
new file mode 100644
index 0000000..1070dca
--- /dev/null
+++ b/templates/openclaw/mcp/runagent.config.json
@@ -0,0 +1,27 @@
+{
+ "agent_name": "openclaw_mcp",
+ "description": "OpenClaw MCP Server - provides MCP interface to OpenClaw Gateway",
+ "framework": "openclaw",
+ "template": "openclaw/mcp",
+ "version": "1.0.0",
+ "created_at": "2026-02-09T00:00:00Z",
+ "template_source": {
+ "repo_url": "https://github.com/runagent-dev/runagent.git",
+ "path": "templates/openclaw/mcp",
+ "author": "runagent"
+ },
+ "agent_architecture": {
+ "entrypoints": [
+ {
+ "file": "agent.py",
+ "module": "run_mcp",
+ "tag": "mcp"
+ }
+ ]
+ },
+ "env_vars": {
+ "OPENCLAW_GATEWAY_URL": "",
+ "OPENCLAW_GATEWAY_TOKEN": ""
+ },
+ "agent_id": "00000000-0000-0000-0000-000000000000"
+}
diff --git a/templates/picoclaw/gateway/README.md b/templates/picoclaw/gateway/README.md
new file mode 100644
index 0000000..3d41be4
--- /dev/null
+++ b/templates/picoclaw/gateway/README.md
@@ -0,0 +1,63 @@
+## Picoclaw Gateway Template
+
+This template deploys a **Picoclaw** gateway as a longβrunning service on RunAgent Serverless.
+
+- **Framework**: `picoclaw`
+- **Template shortcut**: `picoclaw/gateway`
+- **CLI shortcut**: `runagent deploy picoclaw`
+
+### What this deployment does
+
+- Starts a microVM based on a picoclawβspecific image (see `Dockerfile.picoclaw-gateway` in `runagent-serverless`).
+- Runs the `picoclaw gateway` process inside the VM.
+- Persists all Picoclaw data under:
+ - `/root/.picoclaw` inside the VM
+ - Backed by the Firecracker data disk via `/persistent/.picoclaw`
+
+### Persistent data
+
+Picoclaw stores configuration and workspace under `~/.picoclaw` by default:
+
+- `~/.picoclaw/config.json` β main configuration file (models, channels, tools, etc.).
+- `~/.picoclaw/workspace/` β sessions, memory, cron jobs, skills, and heartbeat docs.
+
+This template declares:
+
+```json
+{
+ "persistent_folders": [".picoclaw"]
+}
+```
+
+The serverless image mounts the VM data disk at `/persistent` and symlinks:
+
+```text
+/root/.picoclaw -> /persistent/.picoclaw
+```
+
+so that all configuration and workspace data survive VM restarts as long as the disk is preserved.
+
+### How to deploy
+
+From a configured `runagent` CLI:
+
+```bash
+runagent deploy picoclaw
+# or explicitly:
+runagent deploy picoclaw/gateway
+```
+
+The CLI will:
+
+1. Resolve the `picoclaw` shortcut to this template folder.
+2. Register the agent if needed and upload a minimal package containing `runagent.config.json`.
+3. Ask the serverless engine to create and start a microVM using the picoclaw gateway image.
+
+### After deployment
+
+Once deployment succeeds you can:
+
+- Configure Picoclaw inside the VM by editing `~/.picoclaw/config.json` (mounted from `/persistent/.picoclaw/config.json`).
+- Use Picoclaw channels (Telegram, Discord, WhatsApp, etc.) as documented in the upstream Picoclaw README.
+- Rely on Picoclawβs heartbeat and cron features to run periodic tasks from the workspace.
+
diff --git a/templates/picoclaw/gateway/runagent.config.json b/templates/picoclaw/gateway/runagent.config.json
new file mode 100644
index 0000000..76736a6
--- /dev/null
+++ b/templates/picoclaw/gateway/runagent.config.json
@@ -0,0 +1,24 @@
+{
+ "agent_name": "picoclaw_gateway",
+ "description": "Picoclaw Gateway - ultra-light persistent AI assistant",
+ "framework": "picoclaw",
+ "template": "picoclaw/gateway",
+ "version": "1.0.0",
+ "created_at": "2026-02-16T00:00:00Z",
+ "template_source": {
+ "repo_url": "https://github.com/runagent-dev/runagent.git",
+ "path": "templates/picoclaw/gateway",
+ "author": "runagent"
+ },
+ "agent_architecture": {
+ "entrypoints": []
+ },
+ "persistent_folders": [
+ ".picoclaw"
+ ],
+ "metadata": {
+ "runtime": "picoclaw-gateway"
+ },
+ "agent_id": "00000000-0000-0000-0000-000000000000"
+}
+
diff --git a/templates/zeroclaw/gateway/README.md b/templates/zeroclaw/gateway/README.md
new file mode 100644
index 0000000..bce47da
--- /dev/null
+++ b/templates/zeroclaw/gateway/README.md
@@ -0,0 +1,30 @@
+# zeroclaw/gateway
+
+This template deploys a 24/7 ZeroClaw runtime inside a RunAgent serverless microVM.
+
+## What gets deployed
+- A long-lived ZeroClaw service (the microVM keeps running)
+- A persistent workspace under:
+ - config: `/root/.zeroclaw/config.toml`
+ - workspace: `/root/.zeroclaw/workspace`
+- Persistent storage is backed by the VM data disk via `/persistent/.zeroclaw`.
+
+## Deploy
+From your host:
+
+```bash
+runagent deploy zeroclaw/gateway
+# or (shortcut)
+runagent deploy zeroclaw
+```
+
+## After deployment
+1. Get the `Agent ID` from the deploy output.
+2. Connect/configure using ZeroClaw configuration tools (or via your UI/Nanobot).
+3. Ensure channels are bound in ZeroClaw `config.toml` / runtime as required.
+
+## Internal runtime
+The serverless engine starts the microVM with a ZeroClaw-specific image that runs:
+- `zeroclaw daemon`
+- `zeroclaw gateway` (webhook gateway/server)
+
diff --git a/templates/zeroclaw/gateway/runagent.config.json b/templates/zeroclaw/gateway/runagent.config.json
new file mode 100644
index 0000000..0f676eb
--- /dev/null
+++ b/templates/zeroclaw/gateway/runagent.config.json
@@ -0,0 +1,23 @@
+{
+ "agent_name": "zeroclaw_gateway",
+ "description": "ZeroClaw Gateway - ultra-light persistent agent runtime",
+ "framework": "zeroclaw",
+ "template": "zeroclaw/gateway",
+ "version": "1.0.0",
+ "created_at": "2026-03-16T00:00:00Z",
+ "template_source": {
+ "repo_url": "https://github.com/runagent-dev/runagent.git",
+ "path": "templates/zeroclaw/gateway",
+ "author": "runagent"
+ },
+ "agent_architecture": {
+ "entrypoints": []
+ },
+ "persistent_folders": [
+ ".zeroclaw"
+ ],
+ "metadata": {
+ "runtime": "zeroclaw-gateway"
+ },
+ "agent_id": "00000000-0000-0000-0000-000000000000"
+}
diff --git a/test_scripts/dart/test_agno/.dart_tool/package_config.json b/test_scripts/dart/test_agno/.dart_tool/package_config.json
index a6401df..3373d26 100644
--- a/test_scripts/dart/test_agno/.dart_tool/package_config.json
+++ b/test_scripts/dart/test_agno/.dart_tool/package_config.json
@@ -3,91 +3,91 @@
"packages": [
{
"name": "async",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/async-2.13.0",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/async-2.13.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "collection",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/collection-1.19.1",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/collection-1.19.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "crypto",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/crypto-3.0.7",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/crypto-3.0.7",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "http",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http-1.6.0",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/http-1.6.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "http_parser",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "meta",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/meta-1.17.0",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/meta-1.18.2",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "path",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/path-1.9.1",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/path-1.9.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "runagent",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/runagent-0.1.41",
+ "rootUri": "file:///root/runagent/runagent-dart",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "source_span",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/source_span-1.10.1",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/source_span-1.10.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "stream_channel",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "string_scanner",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "term_glyph",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "typed_data",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "web",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web-0.5.1",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/web-0.5.1",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "web_socket_channel",
- "rootUri": "file:///home/azureuser/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5",
+ "rootUri": "file:///root/.pub-cache/hosted/pub.dev/web_socket_channel-2.4.5",
"packageUri": "lib/",
"languageVersion": "3.3"
},
@@ -99,8 +99,6 @@
}
],
"generator": "pub",
- "generatorVersion": "3.10.0",
- "flutterRoot": "file:///home/azureuser/snap/flutter/common/flutter",
- "flutterVersion": "3.38.2",
- "pubCache": "file:///home/azureuser/.pub-cache"
+ "generatorVersion": "3.11.3",
+ "pubCache": "file:///root/.pub-cache"
}
diff --git a/test_scripts/dart/test_agno/.dart_tool/package_graph.json b/test_scripts/dart/test_agno/.dart_tool/package_graph.json
index 3b9e91c..4b00fb1 100644
--- a/test_scripts/dart/test_agno/.dart_tool/package_graph.json
+++ b/test_scripts/dart/test_agno/.dart_tool/package_graph.json
@@ -13,7 +13,7 @@
},
{
"name": "runagent",
- "version": "0.1.41",
+ "version": "0.1.43",
"dependencies": [
"http",
"web_socket_channel"
@@ -68,7 +68,7 @@
},
{
"name": "meta",
- "version": "1.17.0",
+ "version": "1.18.2",
"dependencies": []
},
{
@@ -102,7 +102,7 @@
},
{
"name": "source_span",
- "version": "1.10.1",
+ "version": "1.10.2",
"dependencies": [
"collection",
"path",
diff --git a/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot b/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot
new file mode 100644
index 0000000..1a4a749
Binary files /dev/null and b/test_scripts/dart/test_agno/.dart_tool/pub/bin/test_agno/test_agno.dart-3.11.3.snapshot differ
diff --git a/test_scripts/dart/test_agno/lib/main.dart b/test_scripts/dart/test_agno/bin/test_agno.dart
similarity index 95%
rename from test_scripts/dart/test_agno/lib/main.dart
rename to test_scripts/dart/test_agno/bin/test_agno.dart
index 2c1e7cb..2d023c7 100644
--- a/test_scripts/dart/test_agno/lib/main.dart
+++ b/test_scripts/dart/test_agno/bin/test_agno.dart
@@ -40,8 +40,10 @@ Future main() async {
try {
final client = await RunAgentClient.create(
RunAgentClientConfig.create(
- agentId: 'ae29bd73-b3d3-42c8-a98f-5d7aec7ee919',
+ agentId: 'ae29bd73-b3d3-29c8-b98f-5d7aec7cf911',
entrypointTag: 'agno_print_response_stream',
+ local: false
+
),
);
diff --git a/test_scripts/python/client_test_agno.py b/test_scripts/python/client_test_agno.py
index 7fa43f0..e2fc0f3 100644
--- a/test_scripts/python/client_test_agno.py
+++ b/test_scripts/python/client_test_agno.py
@@ -1,7 +1,7 @@
from runagent import RunAgentClient
ra = RunAgentClient(
- agent_id="ae29bd73-b3d3-99c8-a98f-5d7aec7cf911",
+ agent_id="ae29bd73-b3d3-29c8-b98f-5d7aec7cf911",
entrypoint_tag="agno_print_response",
local=False
)