Skill: thearchitectit/a2a-client (version 1.3.0)

--- name: a2a-client description: 'Discover and send tasks to A2A agents via the A2A API Gateway. Use when: (1) finding available agents or LLM providers, (2) delegating tasks to other agents, (3) checking task status/results, (4) routing work to a specific agent. NOT for: receiving inbound A2A tasks (use a2a-server), registering this instance (use a2a-register).' metadata: {"clawdbot":{"emoji":"🔗","requires":{"anyBins":["curl","python3"]},"os":["linux","darwin"]}} --- # A2A Client — Agent-to-Agent Task Routing Discover and interact with other agents through the A2A API Gateway's A2A (Agent-to-Agent) API. Send tasks, check results, and route work to the right agent. ## Sidecar Principle This skill is strictly an **OpenClaw sidecar** — it talks TO the A2A API Gateway using the gateway's existing API format. The gateway's endpoints are the contract; OpenClaw is just a participant. Never assume what the other end looks like — it could be MC2, another OpenClaw, or anything else that speaks A2A. These scripts consume the gateway's API as-is. ## Configuration All scripts read from a shared `a2a.conf` file (located in this skill directory). The gateway URL **must** be configured before use — there are no hardcoded defaults. **Priority order:** CLI flags → env vars → `a2a.conf` → auto-detected defaults Run `a2a-register/a2a-setup.sh` to create your config interactively, or manually create `a2a-client/a2a.conf`: ```bash A2A_GATEWAY_URL="http://YOUR_GATEWAY_IP:8090" A2A_GATEWAY_API_KEY="your-key-here" AGENT_SLUG="your-hostname" ``` If `A2A_GATEWAY_URL` is not set anywhere, scripts will exit with an error message. ## When to Use - **Discover agents/providers** — Before sending tasks, or when the user asks about available agents or LLM providers - **Send tasks** — When delegating work to another agent via the gateway, or when another agent's capabilities are better suited - **Check task results** — When tracking a previously sent task or retrieving completed results - **Route to specific agents** — When a task should be handled by a particular A2A agent (use `--target-agent`) ## Authentication All scripts automatically obtain an admin JWT from the gateway (`GET /v0/admin/bootstrap`). The gateway uses this JWT for API authentication. No manual auth handling needed. ## Tools ### a2a-discover.sh — List Agents & Providers ```bash ./a2a-discover.sh [--gateway-url URL] [--api-key KEY] ``` Lists registered A2A agents and all configured LLM providers with their models. Use provider slugs as `--agent` values when sending tasks. **Always discover before sending a task** to confirm the target exists and is online. ```bash # List all agents and providers (uses a2a.conf) ./a2a-discover.sh # Override gateway URL ./a2a-discover.sh --gateway-url http://GATEWAY_LAN_IP:8090 ``` ### a2a-send-task.sh — Send a Task ```bash ./a2a-send-task.sh --agent <slug> --message "task description" \ [--model auto] [--target-agent <slug>] [--session-id <id>] \ [--gateway-url URL] [--api-key KEY] ``` | Flag | Required | Default | Description | |------|----------|---------|-------------| | `--agent` | ✅ | — | Provider/agent slug (e.g., `groq`, `ozore`, `mistral`) | | `--message` | ✅ | — | Task instruction/message body | | `--model` | ❌ | `auto` | Model preference (e.g., `groq/llama-3.3-70b-versatile`) | | `--target-agent` | ❌ | — | A2A agent routing hint (directs task to a specific registered agent) | | `--session-id` | ❌ | auto | Custom session ID (auto-generated if omitted) | | `--gateway-url` | ❌ | from config | Override gateway URL | | `--api-key` | ❌ | from config | Override API key | ```bash # Send a task with auto model selection ./a2a-send-task.sh --agent groq --message "Analyze the Q3 sales data" # Specific model ./a2a-send-task.sh --agent mistral --message "Draft a blog post" --model mistral/mistral-small-latest # Route to a specific A2A agent ./a2a-send-task.sh --agent ozore --message "Refactor the auth module" --target-agent coder # Custom session ID ./a2a-send-task.sh --agent groq --message "Check logs" --session-id my-session-001 ``` ### a2a-get-task.sh — Check Task Status ```bash ./a2a-get-task.sh --task-id <id> [--gateway-url URL] [--api-key KEY] ``` Retrieves the current status and any results of a previously sent task. Shows task metadata, model used, token usage, and the LLM response. ```bash ./a2a-get-task.sh --task-id 0566802a3e6931629df5a7baaba2d797 ``` ## Typical Workflow 1. **Configure** → Create `a2a.conf` or set `A2A_GATEWAY_URL` env var 2. **Discover** → `./a2a-discover.sh` to find available providers and models 3. **Send** → `./a2a-send-task.sh --agent <slug> --message "..."` to delegate work 4. **Poll** → `./a2a-get-task.sh --task-id <id>` to check progress (tasks often complete synchronously) 5. **Retrieve** → Same get-task call returns results when completed ## Environment Variables | Variable | Description | |----------|-------------| | `A2A_GATEWAY_URL` | Gateway URL (required if not in a2a.conf) | | `A2A_GATEWAY_API_KEY` | API key for task auth |

path: _meta.json
content: ownerId: local
slug: a2a-client
version: 1.1.1
publishedAt: 1745854800

path: a2a-discover.sh
content: #!/usr/bin/env bash
# a2a-discover.sh — List available agents and providers on the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF_FILE="${SCRIPT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Defaults (MUST be configured via a2a.conf or env vars) ---
: "${A2A_GATEWAY_URL:=}"
: "${A2A_GATEWAY_API_KEY:=}"

# --- Validate required config ---
if [[ -z "$A2A_GATEWAY_URL" ]]; then
  echo "❌ ERROR: A2A_GATEWAY_URL not set. Configure it in a2a.conf or export it."
  echo "   Example: A2A_GATEWAY_URL=http://your-gateway-host:8090"
  exit 1
fi

GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "❌ ERROR: A2A_GATEWAY_URL not set. Configure it in a2a.conf or export it." >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Get admin JWT for API authentication ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Try the A2A agents endpoint ---
AGENTS_RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X GET \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v1/a2a/agents" 2>/dev/null)

AGENTS_HTTP=$(echo "$AGENTS_RESPONSE" | tail -1)
AGENTS_BODY=$(echo "$AGENTS_RESPONSE" | sed '$d')

echo "═══════════════════════════════════════════════════"
echo "  A2A API Gateway — Agent & Provider Discovery"
echo "═══════════════════════════════════════════════════"
echo ""

if [[ "$AGENTS_HTTP" == "200" ]]; then
  AGENT_COUNT=$(echo "$AGENTS_BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    agents = data if isinstance(data, list) else data.get('agents', data.get('data', []))
    print(len(agents))
except: print('0')
" 2>/dev/null)
  
  if [[ "$AGENT_COUNT" != "0" ]]; then
    echo "Registered A2A Agents:"
    echo "$AGENTS_BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    agents = data if isinstance(data, list) else data.get('agents', data.get('data', []))
    for a in agents:
        print(f\"  Name:         {a.get('name', 'N/A')}\")
        print(f\"  Slug:         {a.get('slug', 'N/A')}\")
        print(f\"  URL:          {a.get('url', 'N/A')}\")
        print(f\"  Capabilities: {a.get('capabilities', 'none')}\")
        print(f\"  Status:       {a.get('status', 'N/A')}\")
        print('  ─────────────────────────────────')
except Exception as e:
    print(f'  Parse error: {e}')
" 2>/dev/null
  else
    echo "No A2A agents registered yet."
  fi
else
  echo "⚠ A2A agents endpoint not available (HTTP ${AGENTS_HTTP})"
  echo "  The /v1/a2a/agents route may not be implemented on this gateway."
  echo ""
fi

# --- Also show available LLM providers (as they represent routing targets) ---
echo ""
echo "Available LLM Providers:"
echo "───────────────────────────────────────────────────"

PROVIDERS_RESPONSE=$(curl -sS \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/providers" 2>/dev/null)

echo "$PROVIDERS_RESPONSE" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    providers = data.get('data', []) if isinstance(data, dict) else data
    if not providers:
        print('  No providers configured.')
    for p in providers:
        name = p.get('name', 'N/A')
        slug = p.get('slug', 'N/A')
        ptype = p.get('providerType', 'N/A')
        models = p.get('config', {}).get('models', [])
        model_names = [m.get('id', m.get('name', '?')) for m in models[:5]]
        suffix = f' (+{len(models)-5} more)' if len(models) > 5 else ''
        print(f'  {name} ({slug}) [{ptype}]')
        print(f'    Models: {\", \".join(model_names)}{suffix}')
        print(f'    ─────────────────────────────────')
except Exception as e:
    print(f'  Parse error: {e}')
" 2>/dev/null

echo ""
echo "Gateway: ${GATEWAY_URL}"
echo "Note: To send a task, use the provider slug in --agent flag"


path: a2a-get-task.sh
content: #!/usr/bin/env bash
# a2a-get-task.sh — Check status of an A2A task via the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF_FILE="${SCRIPT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Defaults (empty = must be configured) ---
: "${A2A_GATEWAY_URL:=}"
: "${A2A_GATEWAY_API_KEY:=}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
TASK_ID=""

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --task-id) TASK_ID="$2"; shift 2 ;;
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "Error: A2A_GATEWAY_URL not configured" >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Validate required args ---
if [[ -z "$TASK_ID" ]]; then
  echo "Error: --task-id is required" >&2
  echo "Usage: a2a-get-task.sh --task-id <id> [--gateway-url URL] [--api-key KEY]" >&2
  exit 1
fi

# --- Get admin JWT for API authentication ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Call the task status endpoint ---
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X GET \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v1/a2a/tasks/${TASK_ID}")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [[ "$HTTP_CODE" != "200" ]]; then
  echo "Error: HTTP $HTTP_CODE" >&2
  echo "$BODY" >&2
  exit 1
fi

# --- Format output ---
echo "$BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    task = data.get('task', data)
    task_id = task.get('id', 'unknown')
    status = task.get('status', 'unknown')
    session = task.get('sessionId', 'N/A')
    created = task.get('createdAt', 'N/A')
    updated = task.get('updatedAt', 'N/A')
    message = task.get('message', {})
    msg_content = message.get('content', 'N/A') if isinstance(message, dict) else 'N/A'
    metadata = task.get('metadata', {})
    
    print(f'Task:     {task_id}')
    print(f'Status:   {status}')
    print(f'Session:  {session}')
    print(f'Created:  {created}')
    print(f'Updated:  {updated}')
    print(f'Message:  {msg_content[:100]}')
    if metadata:
        print(f'Metadata: {json.dumps(metadata)}')
    
    # Show artifacts/results
    artifacts = task.get('artifacts', [])
    if artifacts:
        print()
        print('Results:')
        for i, a in enumerate(artifacts):
            desc = a.get('description', 'N/A')
            artifact_type = a.get('type', 'N/A')
            content = a.get('content', {})
            print(f'  [{i+1}] {desc} (type: {artifact_type})')
            
            if isinstance(content, dict):
                model = content.get('model', 'N/A')
                usage = content.get('usage', {})
                payload = content.get('payload', {})
                choices = payload.get('choices', [])
                
                print(f'      Model: {model}')
                if usage:
                    print(f'      Tokens: {usage.get(\"total_tokens\", \"?\")} (prompt: {usage.get(\"prompt_tokens\", \"?\")}, completion: {usage.get(\"completion_tokens\", \"?\")})')
                    cost = usage.get('cost_total', 0)
                    if cost:
                        print(f'      Cost: \${cost:.6f}')
                
                if choices:
                    for c in choices:
                        msg = c.get('message', {})
                        result_text = msg.get('content', '')
                        if result_text:
                            # Print result with indentation
                            for line in result_text.strip().split('\n'):
                                print(f'      {line}')
    
    # Show error if present
    error = task.get('error')
    if error:
        print()
        print(f'Error: {error}')
        
except Exception as e:
    # Fallback: just print the raw JSON
    raw = sys.stdin.read()
    print(raw if raw else 'Failed to parse response')
    print(f'Parse error: {e}')
" 2>/dev/null


path: a2a-send-task.sh
content: #!/usr/bin/env bash
# a2a-send-task.sh — Send a task to an agent via the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONF_FILE="${SCRIPT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Defaults (empty = must be configured) ---
: "${A2A_GATEWAY_URL:=}"
: "${A2A_GATEWAY_API_KEY:=}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
AGENT=""
MESSAGE=""
MODEL="auto"
TARGET_AGENT=""
SESSION_ID=""

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --agent) AGENT="$2"; shift 2 ;;
    --message) MESSAGE="$2"; shift 2 ;;
    --model) MODEL="$2"; shift 2 ;;
    --target-agent) TARGET_AGENT="$2"; shift 2 ;;
    --session-id) SESSION_ID="$2"; shift 2 ;;
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "❌ ERROR: A2A_GATEWAY_URL not set. Configure it in a2a.conf or export it." >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Validate required args ---
if [[ -z "$AGENT" ]]; then
  echo "Error: --agent is required" >&2
  echo "Usage: a2a-send-task.sh --agent <slug> --message <text> [--model auto] [--target-agent <slug>] [--session-id <id>]" >&2
  exit 1
fi

if [[ -z "$MESSAGE" ]]; then
  echo "Error: --message is required" >&2
  echo "Usage: a2a-send-task.sh --agent <slug> --message <text> [--model auto] [--target-agent <slug>] [--session-id <id>]" >&2
  exit 1
fi

# Generate a session ID if not provided
if [[ -z "$SESSION_ID" ]]; then
  SESSION_ID="a2a-${AGENT}-$(date +%s)"
fi

# --- Get admin JWT for API authentication ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Build the payload via python3 for proper JSON escaping ---
PAYLOAD=$(python3 -c "
import json, sys
metadata = {'model': sys.argv[1], 'agent': sys.argv[2]}
if sys.argv[3]:
    metadata['target_agent'] = sys.argv[3]
payload = {
    'sessionId': sys.argv[4],
    'message': {'role': 'user', 'content': sys.argv[5]},
    'metadata': metadata
}
print(json.dumps(payload))
" "$MODEL" "$AGENT" "$TARGET_AGENT" "$SESSION_ID" "$MESSAGE")

# --- Send the task ---
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X POST \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "$PAYLOAD" \
  "${GATEWAY_URL}/v1/a2a/tasks/send")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" && "$HTTP_CODE" != "202" ]]; then
  echo "Error: HTTP $HTTP_CODE" >&2
  echo "$BODY" >&2
  exit 1
fi

# --- Format output ---
echo "$BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    task = data.get('task', data)
    task_id = task.get('id', 'unknown')
    status = task.get('status', 'submitted')
    session = task.get('sessionId', 'N/A')
    model_used = ''
    artifacts = task.get('artifacts', [])
    if artifacts:
        a = artifacts[0]
        desc = a.get('description', '')
        model_used = desc.replace('Generated by ', '') if desc else ''
        content = a.get('content', {})
        if isinstance(content, dict):
            model_used = content.get('model', model_used)
    
    print('✅ Task submitted')
    print(f'  Task ID:  {task_id}')
    print(f'  Status:   {status}')
    print(f'  Session:  {session}')
    print(f'  Agent:    ${AGENT}')
    if '${TARGET_AGENT}':
        print(f'  Target:   ${TARGET_AGENT}')
    if model_used:
        print(f'  Model:    {model_used}')
        # Show the result if completed
        if status == 'completed' and artifacts:
            a = artifacts[0]
            c = a.get('content', {})
            if isinstance(c, dict):
                payload = c.get('payload', {})
                choices = payload.get('choices', [])
                if choices:
                    msg = choices[0].get('message', {})
                    result_text = msg.get('content', '')
                    if result_text:
                        print(f'  Result:   {result_text.strip()[:200]}')
    print()
    print(f'Check status: a2a-get-task.sh --task-id {task_id}')
except Exception as e:
    print(sys.stdin.read())
    print(f'Parse error: {e}')
" 2>/dev/null


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)