a2aproject / a2aproject/A2A

RFC: skill-direct-invoke — a concrete, measured instance of client-directed skill invocation

Abierto
#2,178 0 comentarios 1 reacción 0 asignados Ver en GitHub
Lenguaje dominante
Shell
Estrellas
25.7k
Forks
2.6k
Merge medio
3 d 6 h
PR fusionados (30 d)
16

Descripción

# RFC: skill-direct-invoke — a concrete, measured instance of client-directed skill invocation

> **Author:** LI-Jinyang
> **Related discussions:** #1989 (Client-directed skill selection), #2008 (Generalized capability descriptors & standard invocation interface), #1716 (Authorization layer for AgentSkill invocations), #153 (Confused deputy problem)

---

## Summary

A2A's `AgentSkill` lets a server *advertise* capabilities, but there is currently no standard way for a client that already knows `skillId + arguments` to express a **structured, schema-constrained** invocation and have the server honor it deterministically — without going through the server's model/executor planning step.

This RFC proposes a small, purely additive **extension** (`skill-direct-invoke`) that gives a client that precise path, while:

- re-using the existing `AgentSkill`, `AgentCapabilities.extensions`, `message.extensions` and `message.metadata` primitives (no new RPC method, no new proto field);
- staying **opt-in** — a server that does not declare the extension, and a client that does not activate it, are completely unaffected;
- remaining **honest about its limits**: deterministic invocation shifts cost from the server's model to the client, and it *sharpens* (not solves) the authorization and "agents shouldn't become RPC" questions that the community is already debating.

We have a **working experimental implementation plus measurements** (latency, token crossover, context-privacy) in the Python SDK, described at the end, to ground the design in evidence rather than speculation.

---

## 1. Problem

Today, when a client already knows it wants `skill X` with arguments `{...}`, it still has to send natural language and let the server's executor (often a model) do intent recognition + parameter extraction + tool selection before the right backend runs. Consequences:

- **Implicit routing** — a multi-skill agent must infer the intended skill from free text. This is exactly the gap #1989 identifies ("an Agent Card can advertise multiple skills, a client has no standard way to indicate which it intends to invoke").
- **Latency & cost** — an extra model round-trip on the server side for a call whose arguments were already structured. This is the cost #2008 alludes to when it asks how capability discovery can lead to "optional invocation routing."
- **Non-determinism / untestable** — the same structured input can be routed differently by the model each time.
- **Unclear permission boundary** — because the server "decides," a caller may invoke a skill it was never meant to reach directly. This is the concern that #1716 and #153 raise.

`skill-direct-invoke` does **not** claim to solve routing or authorization at the protocol level. It addresses one concrete, well-bounded sub-problem: *when the caller genuinely has a `skillId + arguments`, give it a standard, schema-checked, bypassable activation path.*

---

## 2. Non-goals (deliberate)

We are **not** proposing to turn `AgentSkill` into a strict method endpoint, and we are **not** proposing a general capability-dispatch interface. Both are explicitly cautioned against in #2008 ("the goal is not to turn A2A into RPC"). This extension is one narrow opt-in mode within that design space, and it is explicitly designed to be a **peer to the existing agentic flow**, not a replacement for it.

We are **not** defining an authorization policy. As #1716 notes, there is still no standard mechanism to say "which callers may invoke which skills with which parameters." `skill-direct-invoke` requires authentication-by-default (below) but the policy for *which* callers may use a given skill remains an open protocol question.

---

## 3. Design overview

### 3.1 Identity

- Extension URI: `https://example.com/a2a/extensions/skill-direct-invoke/v1`
- Activation header / gRPC metadata: **`A2A-Extensions`** (the existing protocol extension header)
- The URI is also the **single metadata namespace key** on the message.

### 3.2 Three-layer activation

A direct-invoke request is a standard A2A `message/send` (or `message/stream`) with three co-operating signals:

1. **Transport activation** — `A2A-Extensions: ` on the HTTP header / gRPC metadata. Without it, the server treats the message as a normal message. This is the outer switch.
2. **Message usage declaration** — `message.extensions` contains the extension URI. If the header is present but the message does not declare the extension, the server returns `InvalidParamsError` (the two must agree).
3. **Payload** — `message.metadata[EXTENSION_URI]` carries the invocation parameters. This is the only authoritative input to execution.

### 3.3 Payload shape (`message.metadata[EXTENSION_URI]`)

Allowed v1 fields:

```jsonc
{
"callId": "direct-call-1",
"skillId": "calculate_sum",
"arguments": {"a": 12, "b": 30},
"bypassModel": true,
"traceId": "…", // optional
"idempotencyKey": "…", // optional
"expectedOutputMode": "application/json" // optional
}
```

`parts` only carries a non-authoritative text summary (for history/logging/debug surfaces); it plays no role in routing or validation.

### 3.4 Agent Card capability declaration

The server does **not** modify `AgentSkill`. It adds one `AgentExtension` to `AgentCapabilities.extensions`, whose `params.skills.` binds an existing `AgentSkill.id` to direct-invoke configuration — `inputSchema`, `outputSchema`, `directInvokeSupported`, `bypassModel`, a non-sensitive public `backend` summary, and a `fallback` policy. The real backend binding lives **only** in the server-side registry; the Agent Card advertises a public summary for interop and consistency checks.

A client reads the card, confirms (a) the extension is declared, (b) the target `skillId` exists in `params.skills`, (c) `directInvokeSupported == true`, and (d) the same `id` exists in `skills[]` — and only then issues a direct request.

### 3.5 Server-side dispatch (wrapper, not new routing)

The implementation does not add a JSON-RPC method or teach the dispatcher about two handlers. It wraps the existing `RequestHandler`: on every `message/send`/`message/stream` it checks whether the extension is in the request context; if not, it delegates to the wrapped handler unchanged (the normal agentic path); if activated, it validates and invokes the trusted backend, then synthesizes a standard A2A `Task` / stream events. This is a **strict delegation wrapper**.

### 3.6 Response

A direct ON `message/send` returns a completed `Task` with the result in `artifacts[0].parts[0].data`, plus direct-invoke metadata under the extension namespace including metrics (`serverDispatchLatencyMs`, `backendLatencyMs`, `llmPlanningLatencyMs`, `inputTokens`, `outputTokens`, `totalTokens`). Streaming returns the standard `submitted` / artifact-update / `completed` events.

---

## 4. Backend binding & security

The server-side model is deliberately trust-constrained:

- The **registry is the only source of trusted backend bindings** (`DirectInvokeRegistry`).
- The caller **cannot** specify a backend URL, tool name, command, credential, or local path. The Agent Card backend field is a public summary; a mismatch between the card summary and the registered backend is rejected.
- **Authentication by default**: a backend is invocable only if the caller is authenticated, unless the backend explicitly opts out (`allow_unauthenticated`); otherwise the call returns `SKILL_DIRECT_INVOKE_UNAUTHORIZED`.
- Input and output are validated against the card-declared schemas (a local JSON-Schema subset in v1, no new required dependency) before and after backend execution.
- Direct invocation bypasses **model planning**, not the schema, permission, or backend-binding checks.

---

## 5. Error codes

Structured codes surfaced via `data.code` on `InvalidParamsError`, e.g.:

`SKILL_DIRECT_INVOKE_METADATA_MISSING`, `INVALID_METADATA`, `SKILL_NOT_FOUND`, `SKILL_NOT_BOUND`, `INVALID_ARGUMENTS`, `UNAUTHORIZED`, `FORBIDDEN`, `BACKEND_NOT_FOUND`, `BACKEND_UNAVAILABLE`, `BACKEND_FAILED`, `OUTPUT_INVALID`, `IDEMPOTENCY_CONFLICT`.

---

## 6. Backward compatibility & additive guarantees

- No new RPC method; no change to generated proto/schema; no field added to `AgentSkill`.
- A server that does not declare the extension is identical to today (the wrapper fall-through).
- A client that does not activate the extension is identical to today.
- Existing single-skill agents and existing clients are unaffected.

---

## 7. Explicitly open questions (we want the community's input)

1. **Authorization policy** — this extension gates on *authentication*, but does not express *which* callers may invoke *which* skill (the core of #1716, and the confused-deputy concern in #153). Should direct invoke be coupled to a capability/authorization layer before it can be considered production-safe?
2. **Scope creep toward RPC** — is a schema-constrained, bypassable invocation interface acceptable under the "don't turn A2A into RPC" boundary in #2008, or should it stay explicitly subordinated to the agentic model? We would argue it is a peer mode, not a replacement — but this is a design call for TSC.
3. **Cost honesty** — direct invoke makes the *server* model's token cost zero, but pushes a one-time routing prefix cost to the *client* (reading the card's `params.skills`). Net token savings only appear after several turns (see evidence below). Should the extension advertise this trade-off explicitly so adopters don't assume a pure win?

---

## 8. Measured evidence

> **Models**: DeepSeek `deepseek-chat`, temperature 0, 30 rounds per case.

We ran the client-directed path (ON) against the normal agentic path (OFF) across **five business demos** (calculator, child-safe video, resume-playback, meal delivery, group-dining), each with a noisy local context containing unrelated numbers, PII, and stale data to test whether the planner corrects and does **not** leak.

### 8.1 End-to-end latency (30-round averages)

| Demo | ON total ms | OFF total ms | OFF/ON | OFF server only |
| --- | ---: | ---: | ---: | ---: |
| Calculator | 1476 | 1953 | 1.32× | 773 |
| Child-safe video | 1608 | 3938 | **2.45×** | 1922 |
| Resume playback | 1282 | 2808 | 2.19× | 948 |
| Meal delivery | 1555 | 2893 | 1.86× | 877 |
| Group dining | 1643 | 2769 | 1.69× | 912 |

ON total = one client-planner call (1.3–1.6 s), no server call. OFF total = client planner (1.2–2.0 s) **plus** server executor (0.7–1.9 s), summed. The latency win comes from **removing the serial server-side round-trip**, not from the client being faster — the client planner is comparable in both modes.

### 8.2 Server model tokens

ON server token = **0** (no model call). OFF server token = 58–725/turn. This confirms the "callee model cost goes to zero" point precisely.

### 8.3 Token is a transfer, not a net short-term saving

- The client prompt grows because it must read the Agent Card's extension declarations to know a skill is direct-invocable and what its schema is. Across five demos this added ~**2143 tokens** to `AgentCards` (plus a ~1800-token deterministic-invocation instruction in the system prompt).
- Net effect **per turn**: ON totals are **higher** than OFF by about **17–29%** (server token savings, at most 725, are far smaller than the client's ~2143).
- **Multi-turn reversal:** the ~2143 tokens are a **one-time prefix** (KV-cache-reusable), while the server's per-turn inference (~521 tokens) repeats every turn. Cumulative ON cost crosses below OFF around turn **5–6**, then the gap widens each turn.
- Per-skill declaration cost is small: roughly ~370 tokens per direct-invocable skill binding, dominated by `inputSchema`/`outputSchema`.

### 8.4 Invocation accuracy

Routing (`planType` + `agentId` + `skillId`) and slot-filling were **≈100%** across 30 rounds per demo, with one exception (resume-playback ON: 1/30 JSON corruption, 96.7%). Even with a constrained planner, an occasional output-format failure can happen, so a client needs a **retry/degrade fallback**.

### 8.5 Framework overhead

The extension mechanism itself is negligible: non-LLM framework-layer latency was ~3.4–3.6 ms (client) and ~1.4–1.8 ms (server) — an order of magnitude below the LLM seconds. The cost is in the model layers, not the extension plumbing.

### 8.6 Privacy (the strongest protocol-level value)

In the normal path, the caller must pack local context into natural language and send it to the callee, which then re-parses it. In the direct path, the caller sends **only the structured fields the callee needs**. For resume-playback, ON sent `contentId / episodeId / startPositionSeconds / targetDevice / skipIntro`; OFF sent the natural-language request **plus** local-context text containing PII (viewing history, device topology, a WeChat excerpt, a broadband/access-card number). The callee could only **guess** on OFF (placeholder episode, conservative timing window) — showing both that context privacy is preserved on ON, and that OFF loses information the caller already had. This is the "understanding happens locally, execution happens remotely" separation, and we believe it is the strongest reason to pursue this direction.

### 8.7 Summary of the evidence

- **Where it clearly wins:** latency (1.32×–2.45×), server-side model cost (0), determinism/accuracy (≈100%), and **context privacy**.
- **Where it does not win:** per-turn total token count is higher; net savings appear only after ~5–6 turns in a multi-turn session.
- **What it does not solve:** authorization at the skill boundary (#1716) — see open question 1.

---

## 9. Relationship to existing proposals

| Discussion | Relationship |
|---|---|
| #1989 Client-directed skill selection | This proposal is a **specific, measured instance** of that epic's acceptance criteria: an optional way for a client to indicate the skill it intends, plus defined semantics. The measurements in §8 are from the (currently unpushed) local implementation. |
| #2008 Capability descriptors & invocation interface | This extension is one **narrow** invocation target (`skillId + arguments` → one backend) within the broader space #2008 describes. We do not propose the general interface; we propose a concrete case with evidence. |
| #1716 Authorization at skill boundary | This extension **requires** the answer #1716 seeks. See open question 1. It does not attempt to pre-empt the authorization design. |
| #153 Confused deputy | Direct invocation makes the confused-deputy risk more salient (a caller reaching a skill it was not scoped to). See open questions 1 and 4. |

---

## 10. What we are asking for

1. **Interest / alignment** — is this specific instance of client-directed skill selection worth pursuing as an extension, or does it exceed the "don't make A2A RPC" boundary?
2. **Authorization direction** — should we shape this to land on top of an agreed authorization mechanism, and if so, which one (e.g., the capability-token approach in #1716)?
3. **Normative signal** — the existing extension channel or a `method`-based signal?

If there is enough alignment, we are prepared to submit the Python implementation as a reference PR to the `a2a-python` repo and to port the semantics to the other language SDKs.

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

This is a design RFC proposing a new protocol extension. The research involves understanding the existing A2A protocol, the Python SDK implementation, and the related discussions (#1989, #2008, #1716, #153). Start by reading the RFC thoroughly, then examine the experimental implementation in the Python SDK. The goal is to understand the design trade-offs, the three-layer activation mechanism, and the measured performance data to contribute to the community discussion.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python, shell
Área
backend-api-design, devtools
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bien especificado
Aptitud para principiantes
30/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.