# Scaling agent systems with Apache Kafka and A2A (Part 5): The Clockwork Agent &#8211; A Java A2A example

[Blog](/blog/)&gt;[Technology](/blog/category/technical/)&gt;Scaling agent systems with Apache Kafka and A2A (Part 5): The Clockwork Agent – A Java A2A example 

Scaling agent systems with Apache Kafka and A2A (Part 5): The Clockwork Agent – A Java A2A example
==================================================================================================

September 09, 2026 | By [ Paul Brebner](https://www.instaclustr.com/blog/author/paul-brebner/)

 

 

 

 



   [ ](https://x.com/intent/tweet?text=Scaling%20agent%20systems%20with%20Apache%20Kafka%20and%20A2A%20(Part%205):%20The%20Clockwork%20Agent%20%E2%80%93%20A%20Java%20A2A%20example&url=https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-a2a-part-5-the-clockwork-agent-a-java-a2a-example/) [ ](https://www.linkedin.com/shareArticle?mini=true&url=https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-a2a-part-5-the-clockwork-agent-a-java-a2a-example/&title=&summary=Scaling%20agent%20systems%20with%20Apache%20Kafka%20and%20A2A%20(Part%205):%20The%20Clockwork%20Agent%20%E2%80%93%20A%20Java%20A2A%20example&source=) 

In the first four posts in this blog series, we introduced the Agent2Agent protocol in stages; the motivation for Agent systems and A2A in [Part 1](https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-agent2agent-part-1-the-evolution-of-agent-systems/), core A2A objects in [Part 2](https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-agent2agent-part-2-understanding-the-a2a-object-model/), A2A runtime behavior in [Part 3](https://www.instaclustr.com/blog/scaling-agent-systems-with-kafka-and-a2a-part-3-agent2agent-protocol-explained/) then A2A diagrams in [Part 4](https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-a2a-part-4-visualizing-the-agent2agent-protocol/) to make it all understandable and visible without encountering any code yet.

Theory is useful. Running code is better.

This post walks through a practical Java A2A style demo that demonstrates how the Agent2Agent protocol works. The Clockwork Agent, a minimal A2A style demo available in [this repository](https://github.com/instaclustr/code-samples/tree/main/AI/A2A), is deliberately simple: no LLM, no framework SDK, no model inference, and no hidden orchestration. Just `<span lang="EN-AU">HttpServer</span>`, `<span lang="EN-AU">HttpClient</span>`, Jackson, and JSON-RPC envelopes shaped like the protocol. The goal is not to ship production agent infrastructure; it is to **ground the diagrams in something you can clone, run, break, and evolve.**

With the Clockwork Agent, we’ll explore agent discovery, Agent Cards, SendMessage, task lifecycles, polling patterns, and multi-turn interactions. This example also explores the boarder scaling question behind the series: once agent interactions become long-running, stateful, distributed, and high volume, how does infrastructure such as Apache Kafka becomes important for durability, coordination, and operational scale.

If you have not read the diagram post yet, keep the runtime sequence open while you read [Part 4 (Figure 4).](https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-a2a-part-4-visualizing-the-agent2agent-protocol/) Each of the three examples exercises a different part of that A2A diagram.

What the Clockwork Agent is and isn’t
-------------------------------------

The Clockwork Agent is a deterministic Java server that pattern-matches on user text such as time, count down N seconds, and confirm. It is mechanical, not intelligent, by design. That makes it easier to observe the protocol without model non-determinism obscuring the request and response shapes.

 It is:**

- An example harness for A2A **request shapes**, **response forks**, and **task lifecycle** semantics
- Runnable in two terminals with Maven (A2aDemoServer + A2aDemoClient)
- Accompanied by [captured traces](https://github.com/instaclustr/code-samples/blob/main/AI/A2A/docs/examples/trace.md)

**But it is not:**

- An AI agent — there is no model inference, no tool calling, no MCP, no RAG
- The official [a2a-java SDK](https://github.com/a2aproject/a2a-java) (use that for production)
- A complete protocol implementation — several operations and transport paths are deliberately omitted

The Clockwork Agent is a ***deterministic* Java server** that pattern-matches on user text (time, count down N seconds, confirm). Mechanical, not intelligent — this is by design: you can see protocol mechanics without model non-determinism obscuring the wire format.

Introducing the Clockwork Agent examples
----------------------------------------

The sequence diagram in the previous blog motivates three different Clockwork Agent examples:

1. Discover an agent
2. Call `SendMessage`
3. Follow one of three runtime paths 
    1. Receive an immediate answer
    2. Follow a task as it evolves
    3. Continue a task after input branches for 
        1. polling,
        2. streaming, and
        3. multi-turn input.

Here’s a concrete sequence diagram illustrating the examples:

[![agent2agent clockwork agent diagram examples]()](https://www.instaclustr.com/wp-content/uploads/clockwork-agent-examples-diagram.png)

The figure above is our starting point. **A2aDemoClient** fetches an **Agent Card**, then calls **SendMessage**. From there the flow splits into three examples — each one a different branch of the A2A runtime we sketched in part 4:

- **Example 1: Time** `"What time?"` → immediate **`Message`** (one round trip, done).
- **Example 2: Countdown** `"Count down 60s"` → **`Task`**, **`GetTask`** poll loop, final **`Artifact`**.
- **Example 3: Confirm** `"Count down + confirm"` → **`input-required`**, then **`SendMessage`** again with **`taskId`** → **`working`** → **`completed`**.

Architecture at a glance
------------------------

Before exploring the examples, it helps to map the Clockwork Agent onto the component diagram from [Part 4 (Figure 1).](https://www.instaclustr.com/blog/scaling-agent-systems-with-apache-kafka-and-a2a-part-4-visualizing-the-agent2agent-protocol/)

A2A components: client side, remote server side, bindings, security boundary.

Diagram componentIn this demoAgent Card endpoint`GET /.well-known/agent.json`A2A API surface`POST /rpc — SendMessage, GetTask, CancelTask`Task manager`CountdownTask` + in-memory `TASKS` mapArtifact storeArtifacts attached inline on the Task when completePush/stream notifier**Not implemented**Client SDK / bindingHand-rolled `HttpClient` + JSON-RPCJSON-RPC bindingYes — REST and gRPC exist in the spec but are not used hereSecurity boundary**Not implemented** — no auth on card or RPCThe demo collapses several server-side boxes into one class (`A2aDemoServer.java`). This keeps the story readable. A production service would split card serving, RPC routing, task orchestration, artifact persistence, and notification delivery into separate modules — exactly as the components diagram suggests.

Step zero: discovery
--------------------

Every client run starts the same way: fetch the Agent Card:

























Java





HttpRequest req = HttpRequest.newBuilder( URI.create(baseUrl + "/.well-known/agent.json")).GET().build();

   1

2



  HttpRequest req = HttpRequest.newBuilder(

 URI.create(baseUrl + "/.well-known/agent.json")).GET().build();



   

 

 This corresponds to the top of the runtime sequence diagram: **Client → Agent Card endpoint → AgentCard**.

The card advertises three skills — `current-time`, `countdown`, and `countdown-confirm` — plus capabilities declaring `streaming: false` and `pushNotifications: false`. That honesty matters later: the client will ***only* *poll***, not subscribe to SSE or webhooks.

**Simplification:** The spec recommends `/.well-known/agent-card.json`; this demo uses `agent.json` on the same well-known path pattern. This is the same idea, just a shorter filename for a local demo.

Example 1: Synchronous time — the immediate Message path
--------------------------------------------------------

**Prompt:** *What is the current time?*

**What it does:** One `SendMessage` call. The server matches “time” in the user text and returns `result.message` — an agent-role Message with a text Part. Done.

**What it illustrates:**

ConceptDiagram referenceDiscovery before workRuntime sequence, steps 1–4`SendMessage<strong> → </strong><strong>Message</strong>` (not Task)Runtime sequence, **alt: Immediate/short operation**; Part 3 — left branch`Message` + `Part` compositionC.f. The Simple Object Model — Dialogue box only; no Task or Artifact**What it does** ***not*** **illustrate:**

- Task lifecycle — there is no `Task`, no `GetTask`, no terminal state
- Artifacts — the reply is a `Message`, not an `Artifact` on a task (a common first-read confusion from Part 2)
- Streaming, push, or multi-turn dialogue

**Why it matters:** Not every agent interaction should become a long-running task. Example 1 is the fast path — the fork where the server has enough information to answer in one round trip. If your client assumes every SendMessage returns a Task, this example is the corrective.

**Trace excerpt** (full version [here](https://github.com/instaclustr/code-samples/blob/main/AI/A2A/docs/examples/01-time-service.md)):

























JavaScript





{ "result": { "message": { "kind": "message", "role": "agent", "parts": \[{ "kind": "text", "text": "The current time is 2026-06-17 14:21:25 AEST." }\] } } }

   1

2

3

4

5

6

7

8

9



  {

 "result": {

 "message": {

 "kind": "message",

 "role": "agent",

 "parts": \[{ "kind": "text", "text": "The current time is 2026-06-17 14:21:25 AEST." }\]

 }

 }

}



   

 

 **Code pointers:** `A2aDemoServer.handleSendMessage` → `immediateMessage(...); A2aDemoClient.runSynchronousTimeExample(...)`.

Example 2: Async countdown — long-running Task + polling
--------------------------------------------------------

**Prompt:** *Count down 60 seconds*

**What it does:**

1. `SendMessage` creates a countdown `Task` and returns it immediately `(state: working)`. The constructor may set submitted internally, but `createCountdownTask()` calls `start()` before responding — so the first client-visible snapshot is already working.
2. A background `ScheduledExecutorService` decrements the timer every 10 seconds.
3. The client loops on `GetTask(taskId)` every 5 seconds until a terminal state.
4. On completion, the task carries a final **`Artifact`** with the completion text.

**What it illustrates:**

ConceptDiagram reference`SendMessage<strong> → </strong><strong>Task</strong>`Runtime sequence, **else: Long-running operation**; Part 3— right branchPolling fallbackRuntime sequence, **loop: `GetTask`**; Part 3— **Poll** lane only`working → completed`Task lifecycle diagramProgress via `Task.status.message`Object model — Task owns nested `Message` for statusFinal output via `Task.artifacts[]`Object model — `Task` → `Artifact` → `Part`**What it does** ***not*** **illustrate:**

- **SSE / SendStreamingMessage** — the sequence diagram’s parallel **Streaming/push updates** branch is absent. The Agent Card says so upfront.
- **Push webhooks** — no `CreateTaskPushNotificationConfig`, no server-initiated HTTP callback to the client
- **`TaskStatusUpdateEvent` objects** — the spec’s event types appear in the detailed object model, but this demo does not emit discrete events. The client pulls **full task snapshots** via `GetTask` and diffs them implicitly by re-printing status text
- **failed, rejected, canceled** terminal paths — happy path only in the default client (though `CancelTask` exists on the server)

**Why it matters:** This is the core asynchronous pattern behind most real agent workloads: accept work quickly, execute in the background, expose mutable state, deliver durable outputs at the end. Polling is chatty but always valid — and it is the easiest way to learn the task contract before adding streaming complexity.

**Worth noting:** The runtime sequence (Figure 4, TODO) shows streaming/push and polling as parallel ways to track a task; the Clockwork Agent implements **polling only**. The diagram shows every option; our demo implements only the simplest one on purpose.

**Code pointers:** `createCountdownTask(...)`, `CountdownTask.start(), handleGetTask(...), runAsyncCountdownExample(...)`.

Example 3: Input-required countdown — multi-turn on the same task
-----------------------------------------------------------------

**Prompts:**

1. *Count down 20 seconds with confirm*
2. `confirm` (second `SendMessage`, same `taskId`)

**What it does:**

1. First `SendMessage` returns a `Task` already in input-required with a prompt in `status.message`. `createConfirmRequiredCountdownTask()` sets that state in the constructor and does not call `start()` first — so the client never sees a prior working snapshot.
2. Second `SendMessage` includes `params.taskId` plus the user’s confirmation text.
3. Server calls `confirmAndStart()`, transitions to working, runs the same countdown logic as Example 2.
4. Client polls `GetTask` until `completed` + artifact.

**What it illustrates:**

ConceptDiagram reference**input-required state**Task lifecycle — client-visible path is input-required → working → completed (not working first)Follow-up input on existing taskRuntime sequence, **opt: Input required** — second `SendMessage`Same task context across turnsDetailed object model — `SendMessageRequest` with optional `taskId`**What it does *not* illustrate:**

- A task entering input-required from working mid-flight — this demo creates the task **directly** in input-required; the first client response is already there
- Rich conversational history — the demo does not populate a full `Task.history` array; it only needs enough state to gate the countdown
- Client-side UX for gathering input — the client blindly sends “confirm”; a real app would render the agent’s prompt and collect structured input
- Cancellation mid input-required — supported by lifecycle diagram and server handler, not exercised in the default client run

**Why it matters:** Many agent workflows need clarification before execution continues — policy confirmation, missing parameters, disambiguation. Example 3 shows the protocol-native pattern: **stay on the same `taskId`**, create or reach **input-required**, then resume when the client sends follow-up input. This is cleaner than starting a new task and trying to correlate IDs in application code.

**Code pointers:** `createConfirmRequiredCountdownTask(...), sendMessageForTask(...) (client), CountdownTask.confirmAndStart()`.

Running the Clockwork Agent Examples
------------------------------------

Run it in two terminals from the repository root:

**Server:**































&lt;span lang="EN-AU"&gt;mvn -q compile exec:java -Dexec.mainClass=local.a2a.examples.A2aDemoServer&lt;/span&gt;

   1



  &lt;span lang="EN-AU"&gt;mvn -q compile exec:java -Dexec.mainClass=local.a2a.examples.A2aDemoServer&lt;/span&gt;



   

 

 **Client:**































&lt;span lang="EN-AU"&gt;mvn -q compile exec:java -Dexec.mainClass=local.a2a.examples.A2aDemoClient&lt;/span&gt;

   1



  &lt;span lang="EN-AU"&gt;mvn -q compile exec:java -Dexec.mainClass=local.a2a.examples.A2aDemoClient&lt;/span&gt;



   

 

 The client prints the Agent Card summary, then runs all three examples in order. Override the base URL with `<span lang="EN-AU">A2A_DEMO_BASE_URL</span>` if needed.

Further [walkthrough notes](https://github.com/instaclustr/code-samples/tree/main/AI/A2A/docs/examples).

How the three examples map to A2A runtime paths
-----------------------------------------------

In the following recap table, each row ties a protocol concept from the earlier posts to what the Clockwork Agent actually does:

Example 1Example 2Example 3Response type`Message``Task``Task`Lifecycle states—`working` → `completed``input-required` → `working` → `completed`Client trackingNonePoll `GetTask`Poll after resumeArtifactsNoYes, at completionYes, at completionRuntime sequence branchImmediate altLong-running + poll loopLong-running + input-required optMessage vs Task path`Message``Task` → Poll`Task` → Poll (after input)Together they cover the two top-level outcomes of `SendMessage` and two of the three client update strategies from Part 3 — **poll yes, stream no, push no**.

What is simplified or missing overall
-------------------------------------

So, have we seen a definitive set of A2A examples? No, not yet. Everything below is deliberately left out to keep the Clockwork Agent small and readable for a first pass (and to leave interesting things for later).

### No AI

There is no LLM, embedding model, or tool loop. Intent routing is `String.contains(...)`. That keeps traces stable and makes the post about **protocol behavior**, not prompt engineering.

### No official SDK

The [official Java SDK](https://github.com/a2aproject/a2a-java) handles bindings, schema validation, and spec drift. This demo inlines JSON shapes so you can see every field. Migration to the SDK is next.

### Transport and operations

The Clockwork Agent uses **JSON-RPC over HTTP** only — no REST or gRPC bindings, no SSE (`SendStreamingMessage`), no push webhooks, and no extra operations like `GetExtendedAgentCard` or `ListTasks`. `CancelTask` exists on the server, but the default client run skips it.

### Persistence and scale

- Tasks live in an in-memory `ConcurrentHashMap` — restart loses state
- Single JVM, single node — no shared task store for horizontal scale
- No retention or cleanup policy for completed tasks
- Polling interval is fixed; no backoff or subscription handoff

### Security

No authentication on the Agent Card or RPC endpoint. Production agents need the security boundary from the components diagram — AuthN/AuthZ, policy, and likely mTLS or token validation before `SendMessage` is accepted.

### Spec fidelity

- Minimal validation of incoming envelopes
- Agent Card path simplified (`agent.json`)
- Status progress is embedded in `Task.status.message` rather than emitted as standalone `TaskStatusUpdateEvent` messages

From demo to production: Reading the diagrams again
---------------------------------------------------

If you squint at the Component diagram after running the demo, there are some obvious improvements we can make in the future:

1. **Extract** task management and artifact storage from the monolithic server class
2. **Add** a push/stream notifier so clients are not poll-bound (re-enable the parallel branch in the runtime sequence)
3. **Swap** hand-rolled HTTP for the official SDK and a real binding
4. **Insert** AuthN/AuthZ at the security boundary
5. **Optionally** back task events with a durable log — the kind of pattern this series will connect to Kafka down the track

The diagrams were the map. These three examples are the first walk along the path: deliberately short, deterministic, and AI-free so the A2A protocol stays in focus.

Key takeaways
-------------

1. `<b><span lang="EN-AU">SendMessage</span></b>` **forks** into two outcomes: an immediate `<span lang="EN-AU">Message</span>` (Example 1) or a long-running `<span lang="EN-AU">Task</span>` (Examples 2 and 3). Client code must handle both.
2. **Polling is a first-class baseline** for the A2A protocol. Example 2 implements the sequence diagram’s poll loop; streaming and push are optional upgrades, not prerequisites for learning A2A.
3. `<b><span lang="EN-AU">input-required</span></b>` is a state, not a new task.** Example 3 shows continuation via `<span lang="EN-AU">taskId</span>` on a follow-up `<span lang="EN-AU">SendMessage</span>`.
4. **Artifacts belong to tasks. C**ompletion output appears in `<span lang="EN-AU">task.artifacts[]</span>`, not as a bare top-level response.
5. **Diagrams describe the available runtime paths;** implementations may choose only the paths they need. This A2A demo intentionally focuses on polling to keep the protocol behavior easy to follow

*The Clockwork Agent is not intelligent, but it keeps time. That’s the point. This practical Java A2A example strips away AI inference and framework complexity so the Agent2Agent protocol can be observed directly in code. One agent, three runtime paths, and the same predictable behavior every time.*

Series Recap
------------

Part 1 framed the challenge of scaling agent systems and how Kafka fits. Part 2 introduced A2A’s object model. Part 3 covered runtime behavior, including discovery, SendMessage, tasks, and updates. Part 4 added diagrams. Part 5 brought those concepts together through a runnable Java A2A example and A2A demo that shows how the Agent2Agent protocol works in practice.

Scaling agent systems eventually needs durable events, state, search and gateways — workloads [Instaclustr](https://www.instaclustr.com/platform/) already operates as managed Kafka, OpenSearch, PostgreSQL, Cassandra, ClickHouse, Cadence, and a new MCP gateway. A2A defines how agents communicate and hand work to one another; Apache Kafka and related infrastructure become increasingly important as those interactions grow in volume, complexity, and operational scope.

 

### About the author

**[Paul Brebner](https://www.instaclustr.com/blog/author/paul-brebner/)** | Technology Evangelist at Instaclustr

Paul has extensive R&amp;D and consulting experience in distributed systems, technology innovation, software architecture, and engineering, software performance and scalability, grid and cloud computing, and data analytics and machine learning.

 

 [ Add Instaclustr as a preferred source on Google ](https://google.com/preferences/source?q=instaclustr.com)



 

 ![mail icon]()#### Get the latest articles for open sourceIn your inbox

 <a class="btn btn-primary btn-popup text-dark" href="">Sign up now</a> 

 

 

 

  ### Related content

 [ Zero Downtime Migration to Instaclustr 

 

 Yes, we can migrate existing Cassandra clusters to Instaclustr without any downtime. Here's what to expect from the process... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/zero-downtime-migration-to-instaclustr/) 

 [ Workflow Comparison: Uber Cadence vs Netflix Conductor 

 

 When choosing what’s right for your company’s opensource workflow needs it is important to know the difference and similarities ... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/workflow-comparison-uber-cadence-vs-netflix-conductor/) 

 [ Will Your Cassandra Database Project Succeed?: The New Stack 

 

 Open source Apache Cassandra® continues to stand out as an enterprise-proven solution for organizations seeking high availability... 

 

 

 

 

 

 

 ](https://www.instaclustr.com/blog/will-your-cassandra-database-project-succeed-the-new-stack/) 

 

  <a class="close-modal" href="">×</a>Sign upto ourNewsletter
-----------------------
