Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions examples/01-exec-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Lesson 1 — build an A2A server from a script

The first step in learning the `a2a` CLI is to stand up a server you can send messages to and get replies from. The CLI gives you two ready-made server modes, no A2A-specific code required.

The simplest is `--echo`, which sends your message straight back. It is a "ping" for A2A: perfect for a first connection test. The more advanced is `--exec`: point it at any script that reads input and prints output, and it becomes a working A2A server. `--exec` is where the fun is — it turns any program into an agent for demos, testing, and small jobs.

> `--echo` and `--exec` are built for learning, demos, and testing, not for production use.

## What you'll learn

- How to start the simplest server with `--echo`
- How `--exec` wraps an ordinary script as an A2A server
- How to send a one-shot message and read the reply
- How to stream a reply piece by piece

## How `--exec` works

The CLI hands your script the message on **stdin** and turns whatever the script prints on **stdout** into the response. The exit code sets the result: `0` succeeds, non-zero fails. Anything on **stderr** is logged and shown in the failure message.

This example ships two small scripts:

| File | What it does |
|---|---|
| `content-generator.sh` | Uppercases the message and adds a word count. Returns one response. |
| `a2a_unaware_agent.py` | Prints one numbered line per word, with a short delay — handy for streaming. |

## Prerequisites

Install the CLI (see the [repo README](../../README.md)):

```bash
go install github.com/a2aproject/a2a-cli@latest
```

## Step 1 — warm up with the echo server

Start the simplest possible server in **terminal A**:

```bash
a2a server --echo --port 8080
```

Send it a message from **terminal B** and get the same text back:

```bash
a2a send -a http://localhost:8080 "hello world from A2A"
```

That is a full A2A round trip. Stop the echo server (Ctrl-C) and move on to `--exec` for something more useful.

## Step 2 — run the scripts on their own

Before the CLI is involved, confirm each script works on a plain pipe:

```bash
echo "1 2 3 4 5 helloworld" | bash content-generator.sh
echo "5 4 3 2 1 helloworld" | python3 a2a_unaware_agent.py
```

## Step 3 — start a server from a script (terminal A)

Wrap one of the scripts in a server:

```bash
# Bash script — returns the whole output as one response
a2a server --exec "bash content-generator.sh" --port 8080

# Python script — streams one piece per line.
# -u keeps output unbuffered so pieces arrive promptly; --chunk splits on newline.
a2a server --exec "python3 -u a2a_unaware_agent.py" --chunk=$'\n' --port 8080
```

Leave the server running.

## Step 4 — send a message (terminal B)

```bash
# Fetch the agent card to confirm the server is up
a2a card get -a http://localhost:8080 -o json

# One-shot response
a2a send -a http://localhost:8080 "hello world from A2A"

# Watch pieces arrive live (pair with the --chunk server above)
a2a send -a http://localhost:8080 --stream "one two three four"
```

## Test

`test.sh` checks both scripts on a plain pipe — no server needed. Because `--exec` only pipes the message to stdin and reads stdout, this exercises the same path the server runs:

```bash
bash test.sh
```

## Next

You have a running agent. In [lesson 2](../02-card-and-send/) you learn the client side properly: reading the agent card, saving it, and setting it once through config so you can drop the `-a` flag from every command.

## Learn more

These scripts scratch the surface. The `a2a` CLI also does agent-card discovery, multi-part messages, async and streaming sends, task management, and echo and proxy server modes. Read the [a2a-cli specification](../../specification/SPEC.md) to explore everything the tool offers.
31 changes: 31 additions & 0 deletions examples/01-exec-demo/a2a_unaware_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
"""An A2A-unaware agent.

`a2a server --exec` pipes the incoming message text to stdin and turns
stdout into the response artifact. Exit 0 => completed, non-zero => failed.
stderr is logged by the CLI (and attached to the failure status on error).

Run streaming chunks with: a2a server --exec "python3 -u a2a_unaware_agent.py" --chunk=$'\n'
Use `python3 -u` so stdout is unbuffered and chunks stream promptly.
"""

import sys
import time


def main() -> int:
message = sys.stdin.read().strip()
if not message:
print("error: empty message", file=sys.stderr)
return 1

# Do the "work". Here: stream one line per word so --chunk can split on \n.
for i, word in enumerate(message.split(), start=1):
print(f"{i}. {word}")
sys.stdout.flush()
time.sleep(0.3) # visible streaming when run with --chunk=$'\n'
return 0


if __name__ == "__main__":
sys.exit(main())
17 changes: 17 additions & 0 deletions examples/01-exec-demo/content-generator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# An A2A-unaware agent. The a2a CLI (server --exec) feeds the incoming
# message text on stdin and turns whatever we print on stdout into the
# response artifact. Exit 0 => completed, non-zero => failed.
set -euo pipefail

# Read the whole message from stdin.
message="$(cat)"

if [[ -z "${message// }" ]]; then
echo "error: empty message" >&2 # stderr is logged; shows up in the failure status
exit 1
fi

# Do the "work". Here: shout it back with a word count.
words=$(echo "$message" | wc -w | tr -d ' ')
echo "You said (${words} words): ${message^^}"
38 changes: 38 additions & 0 deletions examples/01-exec-demo/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Smoke-test the demo scripts without starting a server.
# --exec just pipes stdin -> stdout and checks the exit code, so testing the
# scripts on a plain pipe tests exactly what the server would run.
set -uo pipefail
cd "$(dirname "$0")"

fails=0
check() { # check <name> <expected-exit> <expected-substring> -- output actual-exit
local name=$1 want_code=$2 want_text=$3 got_code=$5 out=$4
if [[ "$got_code" != "$want_code" ]]; then
echo "FAIL: $name — exit $got_code, want $want_code"; ((fails++)); return
fi
if [[ -n "$want_text" && "$out" != *"$want_text"* ]]; then
echo "FAIL: $name — output missing '$want_text'"; ((fails++)); return
fi
echo "ok: $name"
}

# content-generator.sh: uppercases and counts words, exit 0.
out=$(echo "hello world" | bash content-generator.sh); code=$?
check "bash: happy path" 0 "HELLO WORLD" "$out" "$code"

# content-generator.sh: empty input fails with exit 1.
out=$(echo "" | bash content-generator.sh 2>/dev/null); code=$?
check "bash: empty input fails" 1 "" "$out" "$code"

# a2a_unaware_agent.py: one numbered line per word, exit 0.
out=$(echo "one two" | python3 a2a_unaware_agent.py); code=$?
check "python: happy path" 0 "1. one" "$out" "$code"

# a2a_unaware_agent.py: empty input fails with exit 1.
out=$(echo "" | python3 a2a_unaware_agent.py 2>/dev/null); code=$?
check "python: empty input fails" 1 "" "$out" "$code"

echo
if ((fails)); then echo "$fails test(s) failed"; exit 1; fi
echo "all tests passed"
3 changes: 3 additions & 0 deletions examples/02-card-and-send/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Copy to .env so the a2a CLI picks it up automatically.
# With the agent card set here, you can drop the -a flag on every command.
A2ACLI_AGENT_CARD=http://localhost:8090
115 changes: 115 additions & 0 deletions examples/02-card-and-send/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Lesson 2 — discover and talk to an agent

In lesson 1 you started a server. Now learn the client side: read an agent's card, save it, set it once through config, and send a message.

## What you'll learn

- What an **agent card** is and how to fetch it, plain and as JSON
- How to export a card to a file
- How to set the agent through a `.env` file so you can drop the `-a` flag
- How to send a simple text message

## Prerequisites

The `a2a` CLI installed (see the [repo README](../../README.md)). This lesson uses the built-in **echo** server, so there is nothing to write and you do not need to have finished lesson 1 first.

## Start the agent (terminal A)

The echo server sends your message straight back — a simple partner for learning the client:

```bash
a2a server --echo --port 8090 --name "Echo Agent"
```

Leave it running. Do everything below in **terminal B**.

## Step 1 — get the agent card

Every A2A agent publishes an **agent card** that describes who it is and how to reach it. Fetch it:

```bash
a2a card get http://localhost:8090
```

```text
Echo Agent
URL: http://localhost:8090
Version: 1.0.0
```

Add `-o json` for the raw card — useful for scripts and for saving it:

```bash
a2a card get http://localhost:8090 -o json
```

## Step 2 — export the card

Save the card to a file so you can inspect it or serve it later:

```bash
a2a card get http://localhost:8090 -o json > agent-card.json
```

## Step 3 — set the card through config

Typing `-a http://localhost:8090` on every command gets old. Put it in a `.env` file instead:

```bash
cp .env.example .env
```

This `.env` contains the following:

```dotenv
A2ACLI_AGENT_CARD=http://localhost:8090
```

The CLI reads `.env` from the working directory automatically, so you can now drop `-a`:

```bash
a2a card get # uses A2ACLI_AGENT_CARD from .env
a2a config show # confirm the value and where it resolved from
```

```text
SETTING VALUE SOURCE
agent-card http://localhost:8090 local-file
...
```

The `local-file` source means the value came from a `.env` file in the working directory.

## Step 4 — send a message

The echo agent sends your text right back. Every `send` runs as a task, so the CLI prints the task, its status, and the reply in the artifacts:

```bash
a2a send "hello world from A2A"
```

```text
Task: 01a08152-ae99-73ae-98a3-58b82e14bde0
Context: 01a08152-ae99-74bc-bfc7-d6ce8b2c74d2
Status: completed (2026-09-08T14:01:14Z)
Artifacts:
[01a08152-ae99-75cd-891b-cceb14223d58] hello world from A2A
History:
[user] hello world from A2A
```

## Run the whole lesson

`run.sh` does every step above — start the agent, read and export the card, set config, and send a message — then stops the agent:

```bash
bash run.sh
```

## Next

Lesson 3 shows the three ways to [configure the CLI](../03-config/) and lists every setting you can change.

## Learn more

Read the [a2a-cli specification](../../specification/SPEC.md) for the full set of commands and flags.
40 changes: 40 additions & 0 deletions examples/02-card-and-send/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Lesson 2, end to end: start the agent, read its card, set config, send a message.
# Requires the `a2a` CLI on your PATH (go install github.com/a2aproject/a2a-cli@latest).
set -uo pipefail
cd "$(dirname "$0")"

# Use the a2a CLI; if it is installed as a2a-cli, alias it.
shopt -s expand_aliases
type a2a >/dev/null 2>&1 || alias a2a=a2a-cli

PORT=8090
URL="http://localhost:$PORT"

# Start the built-in echo server in the background; stop it on exit.
a2a server --echo --name "Echo Agent" --port "$PORT" --quiet &
SERVER_PID=$!
trap 'kill "$SERVER_PID" 2>/dev/null' EXIT

# Wait for it to accept requests.
for _ in $(seq 1 20); do
a2a card get "$URL" >/dev/null 2>&1 && break
sleep 0.25
done

echo "== card get =="
a2a card get "$URL"

echo; echo "== card get -o json =="
a2a card get "$URL" -o json

echo; echo "== export the card to agent-card.json =="
a2a card get "$URL" -o json > agent-card.json
echo "wrote agent-card.json"

echo; echo "== set the card via .env, then drop -a =="
echo "A2ACLI_AGENT_CARD=$URL" > .env
a2a config show

echo; echo "== send a text message =="
a2a send "hello world from A2A"
8 changes: 8 additions & 0 deletions examples/03-config/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copy to .env so the a2a CLI picks it up automatically from this folder.
# Every A2ACLI_* variable maps to a flag; see the README for the full list.
A2ACLI_AGENT_CARD=http://localhost:8090

# A few more you might set:
# A2ACLI_OUTPUT=json
# A2ACLI_TIMEOUT=60s
# A2ACLI_TRANSPORT=rest,jsonrpc
Loading
Loading