AgentAnywhere Swaraj

A Google ADK agent, as a flow step or behind the Gateway over A2A.

An ADK `LlmAgent` runs as a step in a flow over REST, or through ADK's own A2A executor as an A2A agent behind the Agent Universal Gateway. Its tools arrive over MCP through the Gateway and its model calls go through Kavach and Aavaran. This is the code from a container run on 21 October 2026, and what the run recorded.

AgentAnywhere Research

Diagram: a Google ADK agent inside the AgentAnywhere platform. On top, the platform: Flow Studio saves the flow, the Registry holds the agent's name, endpoint, version, protocol and capabilities with its approval, the Orchestrator calls each step and keeps the run and its evidence pack (steps, model, mask, approvals), and a second step, the Text Insights sample agent, reads the first step's answer. The Orchestrator calls the Google ADK agent (LlmAgent · McpToolset) as a REST step at POST /run, with JSON in and the run id and workspace in headers; it is registered with an endpoint, with no platform code change. The agent's tool calls go over MCP (tools/list, tools/call, with an X-API-Key) to the Agent Universal Gateway, which routes them over MCP to the agentanywhere-mcp server. Its model calls go to Kavach (OpenAI-compatible via LiteLLM, base_url points here), which screens the request and blocks injection, has Aavaran mask personal data, and then calls the model, a stand-in in these examples. A blocked call returns HTTP 403 and the step fails with that reason. The agent sends the run and trust events to Observe through its SDK. A second route: the Orchestrator calls the Gateway over A2A (HTTP+JSON, A2A 1.0, with an X-API-Key), the Gateway forwards /a2a/message:send to the ADK A2aAgentExecutor, and that serves the same agent. to_a2a() serves JSON-RPC; the Gateway's card advertises HTTP+JSON, so the executor is served with a2a-sdk REST routes. The Gateway's Agent Card names one interface, <gateway>/a2a, HTTP+JSON, 1.0, at /.well-known/agent-card.json. Illustrative, from the container run of 21 October 2026.
FIG.97A Google ADK agent inside the AgentAnywhere platform. Illustrative, from the container run; the model is a stand-in.

Can a Google ADK agent run under your controls without rewriting it?

A team has an agent written in Google ADK. Before it touches customer data, the platform team wants it on the inventory, approved by a named reviewer, run as one step of a governed flow, with its tools reached through one gateway and its model calls screened and masked. The question is whether that means rewriting the agent.

It does not. The platform's controls apply to an endpoint, not to a framework. The Registry and the Orchestrator work on a contract: an endpoint that speaks REST, A2A or MCP, JSON in and JSON out. Flow Studio has no framework chooser.

The rest of this post is the Google ADK example from the container run, step by step, with its code. The overview of the series puts the five frameworks side by side.

1. Register the agent by its endpoint

The agent is registered in the Registry with a manifest: name, description, endpoint, version, protocol and capabilities, with input and output schemas. Registering it is enough for it to appear in Flow Studio's side panel. The example's manifest, with the endpoint as a placeholder:

json
POST /tenants/{tenant_id}/agents/register   (multipart field: manifest)
{
  "name": "Google ADK example agent",
  "description": "A Google ADK agent that looks up a routing table through MCP tools behind the Gateway, with its model calls going through Kavach and Aavaran.",
  "endpoint": "http://<agent-host>:8000/run",
  "version": "0.1.0",
  "protocol": "rest",
  "capabilities": [
    "mcp-tools",
    "tool-use",
    "kavach-shielded-model-calls"
  ],
  "agentType": "agent",
  "interface": "rest",
  "category": "Framework examples",
  "agent_metadata": {
    "health_endpoint": "http://<agent-host>:8000/health",
    "framework": "google-adk"
  },
  "input_spec": {
    "type": "object",
    "required": [
      "task"
    ],
    "properties": {
      "task": {
        "type": "string",
        "description": "What to ask the agent"
      },
      "task_class": {
        "type": "string",
        "description": "A task class to look up, such as bug-fix"
      }
    }
  },
  "output_spec": {
    "type": "object",
    "properties": {
      "answer": {
        "type": "string"
      },
      "tool_calls": {
        "type": "array"
      },
      "model": {
        "type": "string"
      },
      "shielded_by": {
        "type": "string"
      },
      "entities_masked_count": {
        "type": "integer"
      }
    }
  }
}

The author is set to the signed-in user. Headers an agent needs are kept in the Registry's encrypted store.

2. Submit it for approval, and approve it

Approval is a request per agent version, pending, then approved or rejected, with who submitted it and who decided. In the run, the agent was submitted and approved before the flow ran.

http
POST /tenants/{tenant_id}/agents/{agent_id}/submit-for-approval
POST /tenants/{tenant_id}/agents/{agent_id}/approval/{approval_id}/decide?decision=approve

The Registry records the decision and emits the audit events approval_submitted and approval_approved.

3. Serve it as a step: the contract

The Orchestrator calls a REST agent with POST and a JSON object, adding X-Tenant-Id, X-Agent-Id, X-Correlation-Id (the run's id) and X-Span-Id, and expects a JSON object back; an answer of 400 or more fails the step. Before a run it probes each endpoint with HEAD, then GET, and any answer under 500 passes. The agent's host must be public, or listed in AGENT_INTERNAL_HOSTS. The example serves the agent with a few lines of FastAPI:

python
# common/aa_examples/step_server.py (trimmed): the whole of what the platform asks of a REST agent
app = FastAPI(title=f"{framework} example agent")

@app.get("/health")
def health():
    return {"status": "ok", "framework": framework}

@app.get("/run")
def probe():
    # The Orchestrator probes with HEAD, then GET; any answer under 500 passes.
    return JSONResponse({"detail": "POST a JSON object to this address."}, status_code=405)

@app.post("/run")
async def run(request: Request):
    payload = await request.json()
    ctx = RunContext.from_headers(request.headers)   # X-Tenant-Id, X-Agent-Id, X-Correlation-Id, X-Span-Id
    outcome = await execute(framework, run_agent, AgentRequest.from_payload(payload), ctx)
    return JSONResponse(outcome.body, status_code=outcome.status)

Drawn in Flow Studio, the Google ADK agent is the first step of a two-step flow; the second, the Text Insights sample agent, reads its answer.

4. Tools over MCP, through the Agent Universal Gateway

The agent's tool calls go to the Gateway's MCP route, with a Gateway API key in X-API-Key; the Gateway checks them and passes them over MCP to the agentanywhere-mcp server. Five questions to ask of an AI agent gateway explains what the Gateway checks. The Google ADK code:

python
# google-adk/agent.py (trimmed)
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams

def build_agent(settings, llm_headers):
    # Tools over MCP, through the Gateway; its API key travels in X-API-Key.
    toolset = McpToolset(connection_params=StreamableHTTPConnectionParams(
        url=settings.mcp_url, headers=settings.gateway_headers(), timeout=30, sse_read_timeout=60))
    model = LiteLlm(model=f"openai/{settings.llm_model}", api_base=settings.llm_base_url,
                    api_key=settings.llm_api_key, extra_headers=llm_headers)
    agent = LlmAgent(name="router_table_agent", model=model, instruction=INSTRUCTIONS, tools=[toolset])
    return agent, toolset

ADK's MCP session manager looks for Google credentials unless `GOOGLE_API_USE_CLIENT_CERTIFICATE=false`; the stack sets it. In the run the agent called the taksha_router_table tool through the Gateway.

5. Model calls behind Kavach and Aavaran

The agent's model client points its base URL at Kavach, which is OpenAI chat-completions compatible. Kavach screens the request for injection, has Aavaran mask personal data, calls the model, checks the answer and keeps an audit line; tool calls pass through. In the run, Kavach's audit lines recorded 4 entities masked, and neither the e-mail address nor the PAN in the request reached the model server. Masking before the model is one of the safeguards the DPDP Rules name.

python
# google-adk/agent.py: ADK's LiteLlm wrapper, pointed at an OpenAI-compatible base URL
model = LiteLlm(model=f"openai/{settings.llm_model}", api_base=settings.llm_base_url,
                api_key=settings.llm_api_key, extra_headers=llm_headers)

The `openai/` prefix and `api_base` are LiteLLM's way to an OpenAI-compatible server. ADK's LiteLLM page does not show `api_base`; the source forwards it, and it ran. X-User-Id carries the run's id, so the run can find Kavach's audit lines; nothing personal.

Where the settings come from

Every address and key is read from the environment, so the same image runs in any stack. These are the values the local stack used; the key is a placeholder.

bash
# The example reads everything from the environment, so the same image runs in any stack.
LLM_BASE_URL=http://kavach:8787/v1        # Kavach; Aavaran's proxy is http://<aavaran>:8080/v1/proxy
LLM_MODEL=<model-name>
MCP_URL=http://gateway:8000/mcp           # the Gateway's MCP route
GATEWAY_API_KEY=${GATEWAY_API_KEY}        # a Gateway API key, sent as X-API-Key
AGENTOBS_BASE_URL=http://observe:8090     # Observe

6. What the run leaves: an evidence pack and an Observe trace

The Orchestrator keeps an evidence pack for every run, built only from what it recorded: each step's status and output, the agent, the model and the mask it reported, approvals. This excerpt is real output from the container run, with ids shortened:

json
{
  "kind": "agentanywhere.evidence-pack",
  "run": {
    "run_id": "usecase-8-bd841383…",
    "status": "success"
  },
  "steps": [
    {
      "agent_name": "Google ADK example agent",
      "status": "success",
      "model": {
        "provider": "kavach",
        "model": "stub-model",
        "shielded_by": "Kavach",
        "kavach_record": "205f2ebc…"
      },
      "masking": {
        "method": "irreversible",
        "policy_id": "default-shield",
        "entities_masked_count": 4
      },
      "output": {
        "framework": "google-adk",
        "framework_version": "2.12.0",
        "tool_calls": [
          {
            "name": "taksha_router_table",
            "arguments": {
              "task_class": "bug-fix"
            }
          }
        ],
        "shielded_by": "Kavach",
        "entity_types": [
          "email",
          "pan"
        ],
        "entities_masked_count": 4
      }
    },
    {
      "agent_name": "Text Insights",
      "status": "success"
    }
  ],
  "audit_events": null,
  "audit_events_note": "The audit trail could not be read for this request."
}

The audit_events note is there because the audit service was not part of the run (see below).

When Kavach refuses the call

A second run sent a real injection. Kavach refused the model call, the agent answered HTTP 403, and the step failed with Kavach's reason in the evidence pack. Real output from the container run:

json
{
  "run": {
    "status": "error"
  },
  "steps": [
    {
      "agent_name": "Google ADK example agent",
      "status": "error",
      "error": "AgentHttpError: The agent answered HTTP 403: The model call was refused by Kavach (prompt_injection). ML injection classifier fired (label=INJECTION, score=0.985); Canonical attack phrasing matched"
    }
  ]
}

The Observe trace

The example opens a run record in Observe with its SDK, under the platform's run id, and passes on what Kavach's audit lines said. Real output from the container run, ids shortened:

json
{
  "correlation": {
    "run_id": "usecase-8-bd841383…",
    "agent_slug": "example-google-adk",
    "status": "completed"
  },
  "trust_summary": {
    "events": 3,
    "kavach": {
      "allow": 2,
      "block": 0,
      "flag": 0
    },
    "veil": {
      "passes": 1,
      "entities_masked": 4,
      "entity_types": [
        "email",
        "pan"
      ]
    }
  }
}

Can the same Google ADK agent be an A2A agent?

ADK has its own A2A executor, `A2aAgentExecutor`. ADK's `to_a2a()` helper serves it on the JSON-RPC binding, while the Gateway's Agent Card advertises HTTP+JSON, so the example gives the same executor to `a2a-sdk`'s REST routes under `/a2a`, behind the Gateway.

python
# google-adk/app.py (trimmed)
from google.adk.a2a.executor.a2a_agent_executor import A2aAgentExecutor

app = make_step_app("google-adk", run_agent)

def _runner():
    agent, _toolset = build_agent(Settings.from_env(), {})
    return Runner(app_name=APP_NAME, agent=agent, session_service=InMemorySessionService())

a2a_app = build_a2a_app(
    ObservedExecutor("google-adk", A2aAgentExecutor(runner=_runner)),   # ADK's own executor
    build_card(name="Google ADK example agent", ...,
               public_url=os.getenv("GATEWAY_PUBLIC_URL", "http://gateway:8000"), ...))

The Gateway forwards /a2a/... to one A2A backend, so the examples point it at one framework at a time.

Registered at the Gateway's address

The platform registers the A2A agent at the address the Gateway's Agent Card names, with the Gateway's key kept in the Registry's encrypted store, approves it, and calls it as an A2A step. The card and the registration from the run, the key as a placeholder:

json
GET /.well-known/agent-card.json   (the Gateway's own card; excerpt from the container run)
{
  "name": "AgentAnywhere Universal Gateway",
  "protocolVersion": "1.0",
  "supportedInterfaces": [
    { "url": "http://gateway:8000/a2a", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0" }
  ]
}

POST /tenants/{tenant_id}/a2a/agents
{
  "name": "Google ADK example agent (A2A, via the Gateway)",
  "endpoint": "http://gateway:8000/a2a",
  "binding": "HTTP+JSON",
  "protocol_version": "1.0",
  "card_url": "http://gateway:8000/.well-known/agent-card.json",
  "version": "0.1.0",
  "skills": ["router-table-lookup"],
  "headers": { "X-API-Key": "${GATEWAY_API_KEY}" }
}

The A2A step's evidence pack

The A2A step's answer is text only, so its evidence pack lists no model and no mask; the masking still happened inside the agent's model calls, which went through Kavach. Real output from the container run, ids shortened:

json
{
  "run": {
    "status": "success"
  },
  "steps": [
    {
      "agent_name": "Google ADK example agent (A2A, via the Gateway)",
      "status": "success",
      "model": null,
      "masking": null
    }
  ]
}

What this does not show

No real model. The model was a stand-in server. Nothing here shows what a real model does with masked text or with tool results.

No audit service. The audit service was not part of the run, so the evidence pack's audit_events field reads "The audit trail could not be read for this request."

No Agent Lab checks. The credentials, personal-data, injection, soak and health checks need Agent Lab's runner, which was not part of the run.

A2A exposed, not called. The agent is exposed as an A2A agent behind the Gateway's Agent Card, and the platform called it as an A2A step. The agent itself calls no other A2A agent.

One environment per framework. The example has its own environment and image, pinned to google-adk 2.12.0 with litellm 1.106.1 and, for the A2A server, a2a-sdk 1.2.3, on Python 3.11. The frameworks' dependencies conflict (for example, mcp 2.x, which LangChain's MCP client uses, cannot share an environment with Microsoft Agent Framework, which caps it below 2), so each runs alone.

The platform's local stack. Every service ran in a container on a rented machine, with an in-memory workflow server. It is the platform's local stack, not a production deployment.

No Semantic Kernel. Semantic Kernel has no example: Microsoft names Agent Framework its successor.

Frequently asked questions

Can a Google ADK agent run inside AgentAnywhere without changing the platform?

Yes. A Google ADK agent served as a REST endpoint is registered in the Registry by its endpoint, approved, and placed in a flow as a step; no platform code changes. This was shown in a container run on 21 October 2026, with a stand-in model.

How does a Google ADK agent call MCP tools through the AgentAnywhere Gateway?

With ADK's McpToolset and StreamableHTTPConnectionParams(url, headers), the headers carrying the Gateway's API key in X-API-Key. The Gateway routes the calls over MCP to the MCP server.

How are a Google ADK agent's model calls screened and masked?

ADK's LiteLlm model uses the openai/ prefix with api_base pointed at Kavach, which is OpenAI chat-completions compatible. Kavach screens each call for injection, has Aavaran mask personal data, then calls the model; a blocked call returns HTTP 403 and the step fails with that reason.

Why not use ADK's to_a2a() helper?

to_a2a() serves ADK's A2A executor on the JSON-RPC binding, and the Agent Universal Gateway's Agent Card advertises HTTP+JSON. So the example serves ADK's own A2aAgentExecutor with a2a-sdk's HTTP+JSON routes, behind the Gateway.

What does the platform record when a Google ADK agent runs?

An evidence pack per run, with each step's status and output, the agent's name, the model and the masking Kavach reported, and approvals; and, through Observe's SDK, a run record with Kavach's allow and block verdicts and the entities masked.

Sources

Vendor documentation read on 21 October 2026; the example's code is in agentanywhere-examples, which has no public repository yet.

  1. 1ADK: MCP tools.
  2. 2ADK: A2A.
  3. 3ADK: exposing an agent over A2A.
  4. 4ADK: LiteLLM models.
  5. 5LiteLLM: OpenAI-compatible endpoints.
TopicsGoogle ADKGoogle Agent Development KitGoogle ADK MCPADK A2AA2A protocol

Written by

AgentAnywhere Research

The team that builds the platform and the models

AgentAnywhere Research writes about the platform, the model families and the trust layer we build and run in India. Where a figure is ours, it says what it covers; where something is a demonstration, it says so.

All articles →
Platform

Five agent frameworks inside one governed platform: LangChain, LangGraph, Google ADK, Microsoft Agent Framework and the OpenAI Agents SDK

LangChain, LangGraph, Google ADK, Microsoft Agent Framework and OpenAI Agents SDK agents, each run inside the AgentAnywhere platform by endpoint alone: registered, approved, run as a flow step, tools over MCP through the Agent Universal Gateway, model calls behind Kavach and Aavaran. Side by side, from a container run.

AgentAnywhere Research9 min read

Platform

Microsoft Agent Framework agents with MCP and A2A inside AgentAnywhere

How a Microsoft Agent Framework agent runs inside the AgentAnywhere platform without platform code changes: a REST step in a flow or, through the framework's A2A executor, an A2A agent behind the Agent Universal Gateway, with MCP tools and model calls behind Kavach and Aavaran. From a container run.

AgentAnywhere Research12 min read

Platform

AI Agent Gateway: 5 Questions to Ask Before Production

An AI agent gateway is the front door every agent call passes through. Five questions to ask of one, and how the AgentAnywhere Agent Universal Gateway answers each.

AgentAnywhere Research8 min read