Blog

Your Elastic agent, Google's ADK, and zero custom APIs: building “Lucky Planet” over A2A

Elastic Agent Builder's native A2A endpoint lets Google's ADK orchestrate a remote agent, with no custom REST API. Watch it work in 'Lucky Planet,' a random-exoplanet game built end-to-end.

Elastic Agent Builder exposes a native Agent2Agent (A2A) endpoint: any A2A-compliant framework can orchestrate your Elastic agents without writing a single custom REST route. To prove it, we built Lucky Planet: a multiplayer game where Google's Agent Development Kit (ADK) drives a remote Elastic agent over A2A to pick random exoplanets from a live NASA dataset and declare the winner. The Elastic agent owns all the data retrieval and game logic. The ADK and Flask front-end never touch a custom API. Here's how to build the whole thing from scratch.

Screenshot of a web interface titled “Lucky Planet,” showing three players (Socrates, Plato, and Aristotle), each assigned an exoplanet. Below, a galactic map displays the Sun at the center of the Orion Spur with labeled points and connecting lines marking the planets’ positions and distances in kiloparsecs. Source credit: NASA/JPL‑Caltech, spitzer.caltech.edu.

Prerequisites

To follow along with the steps in this blog post, here’s what you’ll need:

Create an Elasticsearch Serverless project

To build an agent with Agent Builder, we need an Elastic deployment or project to use, so let’s create one. Elastic Serverless is the easiest way to get started. Simply visit Elastic Cloud and sign up. Then create an Elasticsearch project.

Animated tour of the Elastic Cloud home dashboard in dark mode. It follows steps from beginning a project, confirming project settings, launching the project, confirming that it's ready, and opening it.

Import a CSV file to create an Elasticsearch index

To begin, we need some data about planets. The NASA Exoplanet Archive is the ideal source for data about planets that have been discovered outside of our solar system within the Milky Way galaxy. View the NASA planets data in a browser. Save the planet data with the file name planets.csv on your local computer.

Back in Elastic Cloud, on the Getting Started page, click Add data and select Upload a file.

Specify the Index name as ‘planets-raw’. Click the Select or drag and drop files link, and then select the previously saved planet.csv file. Click Import.

Animated tour of the “Getting started” interface for Elasticsearch in dark mode, showing how to add data.

Agent Builder chat with your data

Select Agents from the top level navigation menu. Agent Builder has a default agent named “Elastic AI Agent” that you can use to chat with your data. Enter the prompt, like “What data do I have? Provide details.” You should see that you have an index named ‘planets-raw’, along with a list of field names, including their data type and description.

Animated tour of the “Add data” interface in Elasticsearch, showing the upload process for a file named planet.csv and a chat with Elastic AI Agent for data details.

Notice that the sy_dist field description is “Distance to the star system (likely in parsecs)”.

Screenshot of the Elastic AI Agent interface in dark mode, showing a user query “what data do I have?” and the system’s response describing an index named planets‑raw. The focus is on the sy_dist row. The left sidebar includes navigation options, such as Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management.

We want our game’s planet distances to be in light-years instead of parsecs. Using light-years as the unit for planet’s distance data is preferable because it’s the easiest way to comprehend the massive distances involved, since it’s based on the length of a year and the speed of light. For a planet 4.2 light-years away, it would take 4.2 years to fly there, if you could travel at the speed of light.

Additionally, more descriptive field names would be better. We can do that by creating a new index. Click Developer Tools from the top-level navigation menu.

Screenshot of the “Getting started” page in Elasticsearch, showing connection setup options for deployments and API keys. The interface includes panels for building in an IDE or with the Elasticsearch Agent, sample data upload buttons, and a dark sidebar with navigation items. The focus is on "Developer Tools."

Copy the following _reindex request, which populates the planets index with updated field names and a new field for 'light years distance from Earth'. Since the index doesn't exist, one will be auto-created.

POST _reindex
{
  "source": { "index": "planets-raw" },
  "dest": { "index": "planets" },
  "script": {
    "source": """
      ctx._id = ctx._source.pl_name;
      ctx._source.planet_name = ctx._source.remove('pl_name');
      if (ctx._source.sy_dist != null) {
        ctx._source.light_years_distance_from_earth = Math.round(ctx._source.sy_dist * 3.26156 * 100) / 100;
      }
      ctx._source.remove('sy_dist');
    """
  }
}

Paste the request into Developer Tools, and click the Click to send request button to send the POST _reindex request. You should see a 200 OK confirmation message to confirm that the new planets index has been created.

Screenshot of the Elasticsearch Developer Tools console, showing a JSON script for reindexing data. The left panel contains the code editor, and the right panel displays a message area for responses. The focus is on the “Click to send request” button used to execute the script.

Alright! We’ve got some data in Elastic ready to be put to use. Now let’s build an agent that can use it. Select Agents from the top-level navigation to open Agent Builder.

Screenshot of the Elasticsearch Developer Tools console, showing a reindexing script transferring data. The left panel contains the JSON script, and the right panel displays the operation’s output confirming 6,033 documents created with no errors. The focus is on the “Agents” icon in the left sidebar.

Create a tool in Elastic Agent Builder

We’ll start the process of creating an agent by creating a Model Context Protocol (MCP) Tool. Agent Builder supports creating different tool types that can do index searches, execute Elasticsearch Query Language (ES|QL) queries, run workflows, and call other MCP tools. Every tool you build in Agent Builder is exposed as a hosted MCP server, callable by any external MCP client. In Agent Builder, select Tools.

Screenshot of the Elastic AI Agent interface in dark mode, showing the left sidebar with navigation options, such as Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. Under the Elastic AI Agent section, menu items include Customize, Overview, Skills, Tools, Chats, and User Data Inquiry. The focus is on the “Tools” option. The main workspace displays a chat panel with the prompt “How can I help you?” and the input “Ask anything.”

Click Manage all tools to see the list of current tools.

Screenshot of the Elastic AI Agent interface in dark mode showing the “Tools” page. The left sidebar lists navigation options such as Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The main panel displays a list of platform tools. The focus is on the “Manage all tools” button at the top right of the interface.

Click + New tool.

Screenshot of the Elastic AI Agent “Tools library” interface in dark mode, showing a list of modular Elasticsearch operations. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the “+ New tool” button at the top right.

In the Create Tool form, select the ES|QL as the tool Type and enter the following values.

For the ES|QL Query text area, enter the following query that will select a random planet from our planets index:

FROM planets 
| EVAL rand_key = hash("md5", CONCAT(planet_name, TO_STRING((NOW())))) 
| SORT rand_key ASC | KEEP planet_name, light_years_distance_from_earth, ra, dec
| LIMIT 1

For Tool ID:

get_random_planet

For Description:

Get a random planet.

The completed Create tool form should look something like the form pictured below. Click Save to create the tool.

Screenshot of the Elastic AI Agent “Create a new tool” interface in dark mode showing configuration fields for an ES|QL query. The code editor contains a query that selects a random planet from the planets dataset. Below the editor are fields for defining tool details and parameter options, with sections for Type and Details. The left sidebar includes navigation links for Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management.

Create an agent and assign it a tool

Agent Builder agents are simple to create. Especially since the MCP tool we just created (that our agent will be using) is also hosted on Elastic, we can easily assign it to the agent.

Click Manage agents

Screenshot of the Elastic AI Agent “Tools library” interface in dark mode, showing a list of modular Elasticsearch operations. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the “Manage agents” button at the top right of the interface.

Click + New agent.

Screenshot of the Elastic AI Agent “Agents” interface in dark mode, showing a table listing existing agents with columns for Name, Visibility, and Labels. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the “+ New agent” button at the top right of the interface.

Enter the following information into the New Agent form.

For Agent ID, enter the text below:

lucky_planet_agent

In the Custom Instructions text area, enter the following instructions. As you can see, instead of making deterministic REST calls with Query DSL or ES|QL, we can provide simple instructions to the agent and rely on its data access via MCP and its context and reasoning abilities. The agent will manage adapting the game to support multiple players, and it will also provide a relevant and entertaining summary of the game outcome.

You are the game master for a game called "Lucky Planet". Start the game when prompted.

# Game Sequence:
1) At the beginning of each game you'll say: 
  "Let's play Lucky Planet! 
  Rolling the cosmic dice..."
2) Select a planet for the "Agent Planet" using the get_random_planet tool.
3) Select a planet for the "Player Planet" using the get_random_planet tool.
4) Ensure each player has a unique planet.
5) Conclude game.

# Game Conclusion:
Display each game player's planet along with its distance (formatted with commas) from Earth and announce the game winner. 
Use this exact output for the first lines of your Game Conclusion response:
'''
-----
Agent Planet: <planet_name> - <light_years_distance_from_earth> light years from Earth (ra: <ra> dec: <dec>)
Player Planet: <planet_name> - <light_years_distance_from_earth> light years from Earth (ra: <ra> dec: <dec>)
The winner is <game winner>!
-----
'''

# Multi-Player Option
If the game is invoked with a prompt containing a list of players, use only those players to play the game by getting a random planet for each player and adapt the Game Conclusion response for multiple players. Sort the list of players ordered by closest planet to furthest. An example multi-player prompt would be: start game Socrates, Plato, and Aristotle.

For the Display name, enter the text below:

Lucky Planet Agent

For the Display description, enter the text below:

Agent that plays the game "Lucky Planet".

Give the agent the custom tool we created previously by clicking the Tools tab.

Screenshot of the Elastic AI Agent “New Agent” interface in dark mode, showing configuration panels for creating a custom agent. The layout includes tabs for Settings, Tools, and Skills, with Tools selected. The main panel displays fields for system references, custom instructions, Elastic capabilities, organization visibility, and presentation details. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the “Tools” tab near the top of the interface.

Select only the get_random_planet tool that we created previously.

Screenshot of the Elastic AI Agent “New Agent” interface in dark mode, showing the Tools tab with a list of available Elasticsearch operations. The layout includes a highlighted message indicating one active tool and a progress indicator. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the selected tool entry in the list "get_random_planet."

Then click Save and chat to save the agent and try it out.

Screenshot of the Elastic AI Agent “New Agent” interface in dark mode, showing the Tools tab with one active tool selected. The layout includes a progress indicator and a table listing the tool entry. The left sidebar includes navigation options, like Discover, Dashboards, Agents, Workflows, Machine Learning, and Data management. The focus is on the “Save and chat” button at the top right of the interface.

Just enter “play” to start the Lucky Planet game.

Animated tour of the Elastic AI Agent “Agent Chat” interface in dark mode, showing a workspace for interacting with a configured agent. The main panel displays a prompt area with the text “How can I help you?” and an input box labeled “Ask anything.” A footer note indicates the model “Anthropic Claude Sonnet 4.6.” The tour concludes with "The winner is the Player."

Kickin! Our efforts have paid off. We have a functional game. Let’s try out the multiplayer option. We just need to prompt the agent with a start command and a list of players, like:

play game Gandalf, Frodo, Strider
Screenshot of the Elastic AI Agent “Agent Chat” interface in dark mode, showing a conversation where the user plays a game with Gandalf, Frodo, and Strider. The chat displays the agent’s completed reasoning, listing each character’s assigned planet with distances from Earth and coordinates. The message announces Frodo as the winner and includes a short narrative celebrating his victory.

Sweet! The game works. Although Lucky Planet is a fun game as it is, it would be much cooler if we could actually visualize where the player’s planets are located in our Milky Way galaxy. There happens to be an open source Python library to do this, called mw-plot. Now we just need an agent orchestration framework written in Python. Since we built our agent in Agent Builder, that means our agent can be used by any agent development tool that supports the A2A Protocol.

Agent Development Kit (ADK)

Rectangular banner with colorful logo that shows a stylized robot face outlined in blue and green with two blue eyes, and below it two red angle brackets forming a coding symbol. To the right, bold black text reads “Agent Development Kit (ADK).”

Turns out, one of the best open source toolkits for building agentic A2A applications is the Agent Development Kit (ADK) developed by Google. It’s available in Python, Go, Java, and TypeScript.

Getting started with the ADK in Python

Let’s fire up a code editor and run some code. On your local computer, open Visual Studio Code and open a new terminal.

Screenshot of the Visual Studio Code interface in dark mode, showing the top menu bar with items Code, File, Edit, Selection, View, Go, Run, Terminal,  Window, and Help. The Terminal menu is open, displaying options for creating and running tasks. A cursor points to the highlighted "New Terminal" option.

In the newly opened terminal, run the following command to clone the Lucky Planet app code from Github.

git clone https://github.com/jsimonweb/lucky-planet

In the terminal, cd to change directory to lucky-planet.

cd lucky-planet

In the terminal, enter the following command to open the current folder in the Visual Studio Code editor.

code -r .

Here’s what the cloned lucky-planet repo should look like in Visual Studio Code.

Screenshot of the Visual Studio Code interface showing a project titled "Elastic Agent Builder." The file explorer lists a folder named lucky planet, with subfolders static and templates, plus files, including gitignore, app.py, Dockerfile, README.md, and requirements.txt.

The basic architecture of the app is a frontend HTML UI (/templates/index.html) that uses a Python Flask back end to invoke the ADK to send and receive responses from the Elastic agent. When a game player has entered player names and clicked the button to start the game, the front end sends a POST request with player names to the following Flask “/api/ask route in the app.py file.

@app.route("/api/ask", methods=["POST"])
def ask():
    """Returns agent text immediately — plot generation is a separate call."""
    try:
        players       = request.get_json(force=True).get("players", [])
        prompt        = "play game " + ", ".join(players) if players else "play game"
        response_text = asyncio.run(_send_a2a(prompt))
        return jsonify({"response": response_text})

Within that route, the method _send_a2a() is called using asyncio.run(_send_a2a(prompt)), which is where the ADK’s collected response from the Elastic agent is stored in response_text and returned to the front end as JSON using the Flask jsonify utility.

Now let’s look at the _send_a2a() method, where the ADK interacts with the Agent Builder agent.

# ── A2A call ─────────────────────────────────────────────────────────────────

async def _send_a2a(prompt: str) -> str:
    auth = {"Authorization": f"ApiKey {ELASTIC_API_KEY}"}

    async with httpx.AsyncClient(headers=auth, timeout=120.0) as http_client:
        resolver = A2ACardResolver(httpx_client=http_client, base_url=ELASTIC_AGENT_URL)
        card = await resolver.get_agent_card(relative_card_path="/lucky_planet_agent.json")

        remote_agent = RemoteA2aAgent(
            name="elastic_agent",
            agent_card=card,
            httpx_client=http_client,
        )

        session_service = InMemorySessionService()
        async with Runner(
            agent=remote_agent,
            app_name=APP_NAME,
            session_service=session_service,
        ) as runner:
            session = await session_service.create_session(
                app_name=APP_NAME,
                user_id="user",
                session_id=str(uuid.uuid4()),
            )

            new_message = genai_types.Content(
                role="user",
                parts=[genai_types.Part(text=prompt)],
            )

            final_text = "(no response)"
            async for event in runner.run_async(
                user_id="user",
                session_id=session.id,
                new_message=new_message,
            ):
                if event.is_final_response() and event.content and event.content.parts:
                    texts = [p.text for p in event.content.parts if p.text]
                    if texts:
                        final_text = "\n".join(texts)

        await remote_agent.cleanup()
    return final_text

The method begins by creating an HTTP client specifying the Elastic API key in the header for the client’s authorization.

auth = {"Authorization": f"ApiKey {ELASTIC_API_KEY}"}

async with httpx.AsyncClient(headers=auth, timeout=120.0) as http_client:

The method then uses the HTTP client to call A2ACardResolver, which fetches the Elastic agent's “card.” An A2A agent card is a JSON manifest describing what the agent can do, which endpoints it exposes, and how to talk to it. A2ACardResolver is performing the A2A handshake, which includes the Elastic agent URL and the relative_card_path of lucky_planet_agent.json corresponding to the lucky_planet_agent Agent ID that we specified when we created the Elastic agent in Agent Builder previously.

resolver = A2ACardResolver(httpx_client=http_client, base_url=ELASTIC_AGENT_URL)
card = await resolver.get_agent_card(relative_card_path="/lucky_planet_agent.json")

The ADK's RemoteA2aAgent takes the agent card and makes the remote Elastic agent look identical to a locally defined ADK agent.

remote_agent = RemoteA2aAgent(
    name="elastic_agent",
    agent_card=card,
    httpx_client=http_client,
)

The ADK then sends the Elastic agent a message, which is just a text prompt, like "play game Lovelace, Shannon, Turing". The runner streams ADK events back. The app waits for is_final_response(), ignoring intermediate thinking and tool-call events, and collects the final plain-English reply.

new_message = genai_types.Content(role="user", parts=[genai_types.Part(text=prompt)])

async for event in runner.run_async(..., new_message=new_message):
    if event.is_final_response() and event.content and event.content.parts:
        final_text = "\n".join(texts)

Finally, the ADK closes the underlying HTTP client used by RemoteA2aAgent, and the

Elastic agent’s response is returned to the frontend UI to be parsed and displayed along with a Milky Way plot of the top three closest planet locations.

    await remote_agent.cleanup()
return final_text

A2A turns the remote Elastic agent into something you can converse with using a standard protocol. The app doesn't call a custom REST endpoint, like POST /api/v1/run-game. It speaks A2A, which means the interface is natural language, not a function signature. The end result is a game, with the complexity of querying data and implementing game mechanics handled by an Elastic agent, while the ADK is used to talk to the agent via A2A to start the game and deliver the agent’s summary of the game outcome.

Setting your agent URL and API Key as environment variables

To connect the Lucky Planet app to your Elastic Agent Builder agent, set two environment variables: the A2A endpoint URL and your Elastic API key. The example app uses a file named .env to store these values.

Make a copy of the env.example file, and name the new file .env.

Screenshot of a code editor window showing a project titled "Elastic Agent Builder." The file explorer lists a folder named lucky planet with subfolders static and templates, and files including env, gitignore, app.py, Dockerfile, README.md, and requirements.txt. The editor displays the env file containing two environment variables labeled ELASTIC_AGENT_URL and ELASTIC_API_KEY with placeholder values.

Go back to the Agent Builder interface to get both of the values that the app requires to communicate with Elastic. Select Tools from Lucky Planet Agent’s customize options.

Screenshot of a dark‑themed interface showing a workspace titled "Lucky Planet Agent." The left sidebar lists sections including Customize, Overview, Skills, and Tools, with the focus on "Tools." Below are options for Chats and Manage components. The main area displays a chat panel with the text “How can I help you?” and a prompt box labeled “Ask anything.”

Click Manage all tools at the top right of the screen.

Screenshot of a dark‑themed interface showing a workspace titled "Lucky Planet Agent." The left sidebar lists sections including Customize, Overview, Skills, and Tools. The upper-right corner shows a button labeled Manage all tools, with the focus on this button. The main panel displays the heading Tools, a description about modular, reusable Elasticsearch operations, a search bar containing get_random_planet, and a box describing that tool with options to edit or remove it.

Click the Manage MCP dropdown at the top of the Tools library page, and select Copy MCP Server URL.

Screenshot of a dark‑themed interface showing a workspace titled "Lucky Planet Agent." The left sidebar lists sections including Customize, Overview, Skills, and Tools. The main panel displays the heading Tools library with a description about modular, reusable Elasticsearch operations. A dropdown menu labeled Manage MCP is open, with the focus on options Copy MCP Server URL, Bulk import MCP tools, and Documentation. A "New tool" button appears to the right of the dropdown.

Paste the MCP Server URL into the .env file, replacing the <YOUR-ELASTIC-AGENT-BUILDER-URL> placeholder value. Agent Builder exposes two endpoints: /mcp serves MCP tools to MCP clients, and /a2a serves the agents to A2A clients. We'll use /a2a. Update the URL to replace the ending text “mcp” with “a2a” because the ADK will be using A2A to communicate with the agent running in Agent Builder.

The edited URL should look something like this:

https://luckyplanet-game-project-12345a.kb.us-central1.gcp.elastic.cloud/api/agent_builder/a2a

Next we need the API key. Click Getting started in the Elastic Cloud top- level navigation.

Screenshot of a dark‑themed interface showing a workspace titled "Lucky Planet Agent." The left sidebar lists sections including Customize, Overview, Skills, and Tools, with the focus on "Getting started." The main panel displays the heading Tools library and a description about modular, reusable Elasticsearch operations. A list of tools appears below. The upper-right corner includes buttons labeled Manage MCP and New tool.

Click the Copy API key button to copy the API key.

Screenshot of a dark‑themed interface showing a workspace titled "Lucky Planet Agent." The main panel displays the heading "Get started with Elasticsearch" and text about connecting a deployment to build modern search for products, docs, and chatbots. The focus is on the Copy API key button.

Jumping back to Visual Studio Code, paste the API key in the .env file to replace the <YOUR-ELASTIC-API-KEY> placeholder text. Your .env file should look something like this:

Screenshot of a code editor window showing a project titled "Elastic Agent Builder." The file explorer lists a folder named lucky planet with the env file open. The editor displays two environment variables: ELASTIC_AGENT_URL with a full Elastic Cloud API endpoint and ELASTIC_API_KEY with a long numeric key ending in double equals signs.

Run the Lucky Planet app

To run the Lucky Planet app:

  1. Create a Python virtual environment with the following command:

python -m venv .venv

2. Depending on your local computer’s operating system, run the following command to activate the virtual environment.

  • MacOS/Linux

source .venv/bin/activate
  • Windows

.venv\Scripts\activate

3. The Lucky Planet app depends on the ADK for agent orchestration, so let’s install it now. Run the following command to install the ADK, along with all of the app’s required Python library dependencies:

pip install -r requirements.txt

4. Okay, it’s time to pop on our crash helmets and run this app. Run it using the following command:

python app.py
Animation of a code editor window showing a project titled "Elastic Agent Builder." The file explorer lists a folder named lucky planet, with subfolders static and templates, and files including app.py, Dockerfile, README.md, and requirements.txt. The editor displays Python code defining an asynchronous function that sends an authenticated request using httpx and initializes a remote agent. The bottom panel shows a terminal open to the lucky planet directory on the main branch.

Running the python app.py command should output a URL, like http://127.0.0.1:5001, that you can open in a browser to see the running Lucky Planet game. Let’s try it out!

Animated diagram showing a dark‑themed interface titled "Lucky Planet." A dialog box near the top invites players to “Enter player names and roll to discover your cosmic destiny.” Two player names, spock and yoda, appear with small remove icons beside them. Below are buttons labeled + Add Player and Roll Cosmic Dice. The background features a cosmic theme.

Build AI apps with the context of your data

As you’ve seen in this blog post, Elastic Agent Builder gives you everything required to quickly start building agents with the context of your own data. That includes a native A2A endpoint, ready for any compatible framework to orchestrate. Try Elastic Cloud to build agents with the context and retrieval layer they need for solving the challenges that matter to you.

Related Content

How Elasticsearch detects multiple change points in time series with 0.99 recall

Thomas Veasey

Your agents have been keeping receipts: turning Elastic Agent Builder's built-in OTel traces into token cost dashboards in Kibana

Meghan Murphy

One prompt, a complete workflow: Elastic's AI agent writes your automation for you

Tinsae Erkailo

AI shopping agents: Why context comes before the query

Matthew Adams

Managing agentic memory with Elasticsearch

Someshwaran Mohankumar

Ready to build state of the art search experiences?

Sufficiently advanced search isn’t achieved with the efforts of one. Elasticsearch is powered by data scientists, ML ops, engineers, and many more who are just as passionate about search as you are. Let’s connect and work together to build the magical search experience that will get you the results you want.

Try it yourself