Skip to content

feat(a2a): implement secure acknowledgment and core registry improvements#21404

Closed
alisa-alisa wants to merge 3 commits intomainfrom
grpc-v0-branch4
Closed

feat(a2a): implement secure acknowledgment and core registry improvements#21404
alisa-alisa wants to merge 3 commits intomainfrom
grpc-v0-branch4

Conversation

@alisa-alisa
Copy link
Copy Markdown
Contributor

@alisa-alisa alisa-alisa commented Mar 6, 2026

Summary

Building on the recently submitted Branch 3 infrastructure, this PR enhances the A2A and general agent discovery systems with robust trust validation, improved registry policies, and idempotent client management.

File-based Changes

packages/core/src/agents/

acknowledgedAgents.ts

  • Change: Added isAcknowledgedSync method.
  • Purpose: Enables synchronous trust validation by looking up cached agent hashes. This is needed to prevent startup race conditions where the registry initialization (which is synchronous) might finish before the asynchronous disk-based trust load completes.

registry.ts

  • Change:
    1. Updated loadAgents to register all discovered agents, regardless of trust status.
    2. Implemented ASK_USER policy for unacknowledged agents using isAcknowledgedSync.
    3. Added GEMINI_YOLO_MODE (ApprovalMode.YOLO) check.
  • Purpose: Prevents trusted agents from being incorrectly flagged during startup. Registers untrusted agents as "visible but restricted" (ASK_USER) instead of ignoring them, improving discoverability. YOLO mode allows for automated testing/development by defaulting all policies to ALLOW.

a2a-client-manager.ts

  • Change: Made loadAgent idempotent.
  • Purpose: Instead of throwing an error if an agent is already loaded, it now returns the existing cached card. This is critical for architectures (like the experimental A2A server) that frequently re-initialize their registries but share a single singleton manager. Without this, subsequent tasks within the same server lifecycle would fail to load the agent tool.

packages/a2a-server/src/config/

settings.ts

  • Change: Added experimental.enableAgents to the Settings interface.
  • Purpose: Allows the A2A server to recognize and parse the agent discovery flag from .gemini/settings.json.

config.ts

  • Change: Updated loadConfig to propagate the enableAgents flag from settings to the core registry.
  • Purpose: Enables the experimental A2A server to discover and host custom agents defined in the .gemini/agents directory, rather than being restricted to its internal Coder Agent.

Testing

Automated Tests

  • acknowledgedAgents.test.ts: Added tests for isAcknowledgedSync functionality.
  • registry_acknowledgement.test.ts: Verified that unacknowledged agents are registered with ASK_USER and transitioned to ALLOW once trusted.
  • a2a-client-manager.test.ts: Updated tests to verify that loadAgent is idempotent and correctly returns cached instances.
  • registry.test.ts: Added verification for remote agent registration in YOLO mode.

Manual Test (gRPC V0 Compatibility)

  1. Prepare the Go Server (a2a-go repo)

    • Switch to V0 Handler in examples/helloworld/server/grpc/main.go.
    • Set protocol Version to "0.1" in a2a/core.go.
    • Run: go run examples/helloworld/server/grpc/main.go (listens on port 9001).
  2. Prepare the Gemini CLI

    • Create .gemini/agents/grpc-test-agent.md with URL: http://localhost:9001/.well-known/agent-card.json.
    • Ensure "enableAgents": true in the experimental block of .gemini/settings.json.
  3. Verify

    • Start server:
      export CODER_AGENT_WORKSPACE_PATH="/path/to/your/workspace" && DEBUG=true GEMINI_YOLO_MODE=true npm run start -w @google/gemini-cli-a2a-server
    • Trigger call:
      curl -N -X POST http://localhost:PORT/ \
        -H "Content-Type: application/json" \
        -d '{
          "jsonrpc": "2.0",
          "id": "1",
          "method": "message/stream",
          "params": {
            "message": {
              "kind": "message",
              "role": "user",
              "parts": [{ "kind": "text", "text": "Hello, call the grpc-test-agent tool and say hi" }],
              "messageId": "test-final-attempt"
            }
          }
        }'

Related Issues

Closes #22199

@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli bot commented Mar 6, 2026

Hi @alisa-alisa, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.

We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines.

Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.

Thank you for your understanding and for being a part of our community!

@alisa-alisa
Copy link
Copy Markdown
Contributor Author

This PR is a part of split: #21348

@alisa-alisa alisa-alisa marked this pull request as ready for review March 6, 2026 14:37
@alisa-alisa alisa-alisa requested a review from a team as a code owner March 6, 2026 14:37
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the AgentRegistry by introducing a more robust and secure acknowledgement mechanism for remote agents and improving the accuracy of agent indexing. The changes aim to safeguard against indirect prompt injection by using content-based hashing for remote agent cards and ensure that agents, including those with overridden names, are correctly discovered and managed within the system. Additionally, it streamlines the handling of unacknowledged project agents, making them visible to the LLM while maintaining necessary user approval gates.

Highlights

  • Content-Based Agent Acknowledgement: Remote agents are now acknowledged using a SHA-256 hash of their AgentCard content, rather than just the URL, to prevent indirect prompt injection.
  • Proactive Registration of Unacknowledged Agents: Project agents that are not yet acknowledged are now registered with an ASK_USER policy, making them visible to the LLM while still requiring user approval for execution.
  • Correct Agent Indexing for Overrides: The registry now indexes agents by their potentially overridden name, ensuring proper lookup by the LLM even when names are customized.
  • Name Collision Prevention: Explicit validation has been added to prevent name overrides from causing accidental collisions with existing agents during registration.
Changelog
  • packages/core/src/agents/registry.test.ts
    • Imported crypto and sdkClient for testing purposes.
    • Mocked DefaultAgentCardResolver to control remote agent resolution in tests.
    • Updated a test description to reflect content-based hashing for remote agent acknowledgement.
    • Added a test to verify safeFetch is used by DefaultAgentCardResolver during initialization.
    • Added a test to confirm canonical name registration is maintained.
  • packages/core/src/agents/registry.ts
    • Added imports for DefaultAgentCardResolver, safeFetch, crypto, and splitAgentCardUrl.
    • Modified the remote agent processing logic to fetch the AgentCard content, compute a SHA-256 hash, and use a combined URL and content hash for acknowledgement.
    • Changed the handling of unacknowledged project agents to register them proactively with an ASK_USER policy instead of omitting them.
    • Implemented a check during agent registration to prevent name collisions when an agent's name is overridden.
  • packages/core/src/agents/registry_acknowledgement.test.ts
    • Updated the test case for unacknowledged project agents to assert that they are now registered (with an ASK_USER policy) rather than being undefined.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a more secure, content-based acknowledgement for remote agents and fixes agent indexing.

@gemini-cli gemini-cli bot added area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Mar 6, 2026
@alisa-alisa alisa-alisa force-pushed the grpc-v0-branch3 branch 2 times, most recently from 489fa40 to 9b131e2 Compare March 6, 2026 15:41
@alisa-alisa alisa-alisa force-pushed the grpc-v0-branch4 branch 2 times, most recently from 1b9ccf8 to 6453c5f Compare March 6, 2026 22:05
@alisa-alisa alisa-alisa force-pushed the grpc-v0-branch3 branch 2 times, most recently from 1817c62 to c61ed08 Compare March 9, 2026 18:45
@alisa-alisa alisa-alisa force-pushed the grpc-v0-branch4 branch 2 times, most recently from a3ef0a5 to aca4e49 Compare March 9, 2026 21:19
@alisa-alisa alisa-alisa force-pushed the grpc-v0-branch4 branch 2 times, most recently from 0abe06e to f40f810 Compare March 11, 2026 16:23
Base automatically changed from grpc-v0-branch3 to main March 12, 2026 21:49
@alisa-alisa alisa-alisa changed the title feat(a2a): implement secure acknowledgement and correct agent indexing feat(a2a): implement secure acknowledgment and core registry improvements Mar 12, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 12, 2026

Size Change: +568 B (0%)

Total Size: 26.1 MB

Filename Size Change
./bundle/chunk-AOSI3W2D.js 0 B -13.4 MB (removed) 🏆
./bundle/chunk-CDU4MBKN.js 0 B -3.62 MB (removed) 🏆
./bundle/core-BLMKAPEN.js 0 B -40.1 kB (removed) 🏆
./bundle/devtoolsService-AOUIIDAQ.js 0 B -27.7 kB (removed) 🏆
./bundle/interactiveCli-S3ZY5IHK.js 0 B -1.59 MB (removed) 🏆
./bundle/oauth2-provider-JI45FIV2.js 0 B -9.19 kB (removed) 🏆
./bundle/chunk-IBZQMNIZ.js 13.4 MB +13.4 MB (new file) 🆕
./bundle/chunk-NMESNU6G.js 3.62 MB +3.62 MB (new file) 🆕
./bundle/core-LJBUBQZD.js 40.1 kB +40.1 kB (new file) 🆕
./bundle/devtoolsService-N2LEVN4Y.js 27.7 kB +27.7 kB (new file) 🆕
./bundle/interactiveCli-2WX5IGY2.js 1.59 MB +1.59 MB (new file) 🆕
./bundle/oauth2-provider-J7GZKIST.js 9.19 kB +9.19 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-37ZTTFQF.js 966 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-FXR7IA3Q.js 1.95 MB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-RJTRUG2J.js 39.8 kB 0 B
./bundle/devtools-36NN55EP.js 696 kB 0 B
./bundle/dist-T73EYRDX.js 356 B 0 B
./bundle/gemini.js 689 kB 0 B
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/keychain-token-storage-WWPK7JMJ.js 0 B -518 B (removed) 🏆
./bundle/memoryDiscovery-URXPIMBI.js 922 B 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 221 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 227 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 11.5 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B 0 B
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B
./bundle/src-QVCVGIUX.js 47 kB 0 B
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/undici-4X2YZID5.js 360 B 0 B
./bundle/keychain-token-storage-IHIQ2KSI.js 518 B +518 B (new file) 🆕

compressed-size-action

@gemini-cli gemini-cli bot added the area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality label Mar 12, 2026
Copy link
Copy Markdown
Contributor

@adamfweidman adamfweidman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YOLO mode already works when using gemini-cli. If this doesn't work with the a2a server, the a2a-server itself probably doesn't have the policyEngine correctly configured. Can we make changes there rather than here?

): Promise<AgentCard> {
if (this.clients.has(name) && this.agentCards.has(name)) {
throw new Error(`Agent with name '${name}' is already loaded.`);
const existingCard = this.agentCards.get(name);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if multiple agents try to get registered with the same name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They will clash. See my reply above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right. I think the fix here is just to check name and url and only throw if both don't overlap

hash: string,
): Promise<boolean> {
await this.load();
return this.isAcknowledgedSync(projectPath, agentName, hash);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unnecessary helper function

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, Unnecessary not used. It's defined and called in one place right above it (function is only 2 lines) so stylistically I think it's not necessary. Wdyt.

if (isAcknowledged) {
agentsToRegister.push(agent);
} else {
agentsToRegister.push(agent);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we only push if this isAcknowledged first? Without acknowldgment we allow arbitrary execution of code to get the necessary token on startup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We change it in this PR.

With the old logic, I can't use an agent until I trust it, but I can't trust it (via the UI) until the system shows it to me. For a CLI user, this is fine because they see a "New agents discovered" notification. But for curl or the A2A server, the tool just appears to be missing.

We need to work around that somehow, the PR is my workaround that. Do you have another preference for the flow?

Summary:

New Logic: "I see a new agent. It’s untrusted, so I'll show it to you but ask for permission before running it." -> my example works.

Old Logic: "I see a new agent. It’s untrusted, so I'll pretend it doesn't exist until you go into the UI and manually approve it." -> my example fails with 'Tool Not Found'.

const isYolo = this.config.getApprovalMode() === ApprovalMode.YOLO;
const isAcknowledged =
definition.kind === 'local' &&
(!definition.metadata?.hash ||
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is definition.metadata?.hash. Lets rely on previous logic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the old logic, if an agent wasn't trusted, it wasn't even added to the tool list. It was invisible until we acknowledged it. This is the new logic that checks the new stuff.

hash is just additional layer to verify the version of the agent that was trusted (fingerprint of the file content) meaning "I trust this agent" ="I trust this specific version of the agent.".

I can remove it and just trust w/e is there? Even if the file is changed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to refactor can be:

const isAcknowledged = definition.kind === 'local' &&
ackService.isAcknowledgedSync(project, definition.name, definition.metadata?.hash);

(move the check). wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current behavior is correct — unacknowledged agents shouldn't be registered at all, which is already handled in loadAgents(). This check in addAgentPolicy() is only needed because the PR changes the upstream filtering to register unacknowledged agents, which we shouldn't do (it triggers network requests to untrusted URLs for remote agents). Let's revert the loadAgents() change and drop this block.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please help me to understand how to make it work in a2a without any changes. Because the existing approach will not work.

'TestAgent',
'http://test.agent/card',
);
const card2 = await manager.loadAgent(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should throw, people could have agents defined across different projects with names that clash.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The A2A server uses a singleton A2AClientManager (to reuse gRPC/HTTP connections across the whole process), but it creates a fresh AgentRegistry for every single task to ensure context isolation.

Basically...it will throw non-stop
We can make the manager URL-aware? In addition to name? Will this work?

@alisa-alisa
Copy link
Copy Markdown
Contributor Author

Brand new idea: #22389

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality area/platform Issues related to Build infra, Release mgmt, Testing, Eval infra, Capacity, Quota mgmt 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants