AIJoule Work

Connect any agent to SAP Joule with A2A (Bring Your Own Agent)

Boidra Expert4 min read

SAP Joule can now talk to agents you build yourself, in any framework and on any stack, through the open Agent2Agent (A2A) protocol. SAP calls this Bring Your Own Agent (BYOA), or code-based agents. This post shows how it works using a real example: our Interface Monitoring agent for SAP AIF, wired into Joule over A2A. The full working reference lives on our boidra-labs/joule-a2a-example repo.

This is the pattern from our SAP Joule guide: build custom now, interoperate over A2A, grow into the SAP standard, with no rebuild and no lock-in.

The idea in one paragraph

Instead of building your agent inside SAP, you build it wherever you like and expose it as an A2A server. Joule discovers your agent through its Agent Card, reaches it through a named SAP BTP Destination so credentials never live in the config, and exchanges tasks over A2A. Your agent becomes just another skill the user can invoke from Joule, grounded in SAP context.

The example: an Interface Monitoring agent

The agent in this walkthrough watches SAP AIF (Application Interface Framework). A user asks Joule something like "which interfaces failed overnight and why?", and the agent queries AIF, diagnoses the failures, and answers in the Joule chat. Everything below is drawn from the real files in the repo.

Step 1: the A2A server (no code needed here)

Your agent is a normal HTTP service that speaks A2A. Two things matter for Joule:

  • It publishes an Agent Card at /.well-known/agent.json, describing the agent (name, URL, capabilities) and its skills. Joule reads the card to discover the agent and its communication URL.
  • It accepts A2A messages via the message/send method (A2A protocol v0.3.0, JSON-RPC over HTTPS) and returns a completed task with the answer.

You can build this in TypeScript or Python with the A2A SDK, and the framework inside (LangGraph, a plain LLM call, deterministic code) is entirely your choice. The runnable server is in the repo, so we will not repeat it here.

Step 2: the BTP Destination

This is the step people forget, and it is how Joule reaches your agent securely. Create a Destination in your BTP subaccount (Connectivity, then Destinations) pointing at the agent's URL. The credentials are resolved here at request time, never in the Joule configuration:

Name=AIF_ANALYSIS_AGENT
Type=HTTP
URL=https://aif-agent.cfapps.eu10.hana.ondemand.com
ProxyType=Internet
Authentication=OAuth2ClientCredentials
tokenServiceURL=https://your-auth/oauth/token
clientId=<client-id>
clientSecret=<client-secret>

The destination name (AIF_ANALYSIS_AGENT here) is what you reference from Joule in the next step. The secret lives in the Destination, not in Joule.

Step 3: add the destination to Joule and call it from a dialog function

Joule extensibility is authored as YAML Design Time Artifacts (DTA), deployed to a Joule instance. The hierarchy is: Digital Assistant (da.sapdas.yaml), then Capability (capability.sapdas.yaml), then Scenario, Function, Action Group, Action.

To call your agent, you add a dialog function with an action of type agent-request that points at the destination. Here is the actual function from the repo that calls the AIF agent (illustrative of the shape; see the repo for the current version):

parameters:
  - name: agent_context_id
    optional: true
  - name: agent_task_id
    optional: true
  - name: agent_state
    optional: true
 
action_groups:
 
  - actions:
      - type: status-update
        message: "Querying AIF monitoring..."
 
  - actions:
      - type: agent-request
        agent_type: remote
        system_alias: AIF_ANALYSIS_AGENT      # the BTP destination name
        body:
          contextId: <? agent_context_id ?>    # carry context across turns
        result_variable: _agent_response
 
  # The agent returns { message, intent, data } as the A2A data part of the
  # completed artifact. Pull out the context id, task id, state and markdown.
  - actions:
      - type: set-variables
        scripting_type: spel
        variables:
          - name: _context_id
            value: "<? _agent_response.body.contextId ?>"
          - name: _task_id
            value: "<? _agent_response.body.id ?>"
          - name: _state
            value: "<? _agent_response.body.status.state ?>"
          - name: _markdown
            value: "<? _agent_response.body.artifacts[0].parts[1].data.message ?>"
 
  - actions:
      - type: message
        message:
          type: text
          content: "<? _markdown ?>"
          markdown: true
 
result:
  agent_context_id: <? _context_id ?>
  agent_task_id: <? _task_id ?>
  agent_state: <? _state ?>

A few things worth calling out:

  • system_alias is the BTP destination name from step 2. That is the whole link between Joule and your agent.
  • status-update streams a "working on it" message to the user while the agent runs. Joule expects the agent's response within a 60 second window (with async callbacks for longer tasks).
  • The agent's answer arrives as an A2A artifact. The function reads the markdown from the data part and renders it in the chat with markdown: true.

Context id: how multi-turn conversations work

Without context, your agent forgets everything after each turn, so follow-up questions do not work. A2A solves this with a context id.

Look at how it flows in the YAML above:

  1. Joule passes the current agent_context_id into the request body as contextId.
  2. The agent replies with a context id (a new one on the first turn, the same one on later turns).
  3. The function stores it back out in result.agent_context_id, so Joule holds it for the next turn.

On the first message the context id is empty and the agent creates one. On every follow-up, Joule sends it back, and the agent uses it to recall the conversation, for example "and which of those are payment interfaces?" still knows which interfaces "those" refers to. The task_id and state work the same way, tracking a specific long-running task and whether it is completed, working, or needs input.

Why this pattern is the right bet

  • Build now, no lock-in. Ship the agent your business needs today, in your stack, and connect it to Joule when it fits.
  • Open protocol. A2A is a vendor-neutral standard (a2a-protocol.org), so the same agent can also talk to other A2A hosts.
  • Governed and observable. Credentials stay in the Destination, and every step the agent takes can be instrumented, which is exactly what Boidra Platform does inside S/4HANA.

Try it

The full working reference, the A2A server, the Agent Card, the BTP destination, and the Joule dialog function above, is on our labs repo:

Go to github.com/boidra-labs/joule-a2a-example

If you want a custom Joule agent built and wired into your landscape, that is what we do. Tell us the use case.

ShareXLinkedIn

Keep reading